Code architecture and design for resource constrained environments

camelCase

The Case of the Mysterious Camel.
Reaction score
362
So, I'm in school now where I can't access Facebook or Stack Overflow so I can't ask anyone else. There was a dispute between my lecturer and I regarding code architecture and design when developing apps for resource constrained environments (like mobile phones).

My game has 112 classes. 70 for the game engine and 42 for the actual game (the answer to life!). The lecturer slammed me and told me that it was "bad design" because the overhead would kill mobile phones. I didn't get it because my phone's 1GHz CPU and 256MB RAM runs the game at 62FPS.

She said that this project will never encounter the issue but if I was maintaining the code for WoW, such "over-engineering", as she called it, would kill the game's performance because 112 is really too many classes.

I still didn't understand (and still don't). For something like, WoW, I would expect that a clean design be even more important and that the overhead (if any, is there even overhead from having too many classes? I thought stuff compiled into assembly strips every notion of a class?) would be negligible.

My game has 9 state classes (excluding the base class):
StateClouds
StateCredits
StateGameover
StateGameplay
StateLoadAssets (preloader)
StateMainMenu
StatePause
StateScreenFade
StateSplashScreen

She said that they should all be thrown into one gigantic monolithic class. Which would result in a total of 127+47+152+202+124+160+84+121+92 = Whatever the fuck, it's a huge number anyway.
That's excluding the resource manager class I have for each state (like, each state has some resources like sound, music and bitmaps that it allocs and deallocs and whatnot; AssetsClouds, AssetsCredits, etc.).

So, my argument was that I'm not particularly smart and that each class should have ONE SINGLE responsibility, if possible (I understand how hard it can be to achieve that with the pressure of deadlines in the real world). This separation of responsibilities makes it easier to deal with code (maintain, extend, debug, etc.) because a lot of the other stuff it is abstracted away and can be assumed to magically work when you're not dealing with them.

She says that 112 classes is still too much and it was akin to splitting up the Android "Canvas" class to "CanvasCircleDrawer", "CanvasRectDrawer", etc. Basically, she was going to the extreme end of converting every function into a class, which I felt was particularly dumb.

I told her that if, somehow, overhead for the classes ever got too high, the profiler would alert me and I'd optimize it away, then because my classes, nicely separated as they are, can easily be copy-pasted into one monolithic class and made to work with minimal changes. WHICH IS THE WHOLE DAMN BEAUTY AND POINT OF SPLITTING CLASSES UP BY RESPONSIBILITY!

But she turned it away and said that it was "dumb" because I should "foresee" that "classes introduce a lot of overhead" and would kill the CPU or w/e. And I should be as reserved as possible when splitting classes up.

She gave up in the end and said it was her two cents for having worked in the industry for many years. I don't want to believe she's being a fucktard because someone who's worked for so long and is qualified enough to be a lecturer should have an inkling of what she's talking about.

Could someone shed some light on all this? Especially the overhead regarding classes? Like, I always felt it was a non-issue.

I told her that if I ever had to optimize anything, it would be the graphics because it's taking up 70% of the CPU time.

Looking for valuable insights. Like, your experiences with "real-world projects" and how finely split your classes are and whatnot. I duno'.
 

Accname

2D-Graphics enthusiast
Reaction score
1,462
Its pretty bad practice what she is talking about. First of all, clean object oriented code is always preferable to performance. If you want performance just code in assembler straight away and do your goto-labels stuff and pointers and processor cache work.

I always say, you can never have too many classes. Devices will never get worse over time, they will only get better and better.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
As Accname said, every programmer worth his salt trades performance for cleanliness where it's necessary.

However, when working in a restricted environment, you might actually hit a wall at some point.

But that's not because you have so many classes, but because you might be doing too much object creation (if, say, you're working in Java).

But how can you have too many classes? Brain. Does. Not. Compute.

In C++, classes are literally non-existing. They are a high-level abstraction. They disappear when compiled into .ASM and leave only memory blocks, pointers and offsets behind.

In Java you have some memory footprint because classes have to be loaded and kept in memory. But (if I remember correctly) the footprint mostly consists of the class' data members and methods (and bookkeeping for virtual methods and reflection).
I don't know how much you actually save by pulling several classes together, but -looking at the Java Standard Library- not much probably.

I don't see a huge difference between creating two Shape objects, or a Box and a Ball object.

In the end, all work that has to be done has to be done. No matter how many classes you split it up into.

But while we're at it..
I currently have the Doom 3 source code in front of me. A quick search reveals 429 .h files. So I expect about this many classes. 600k lines of code.
This game is a decade old. I expect our mobile devices to be able to run Doom 3 in another few years' time.

WoW doesn't even use a normal object-oriented design for most of the game content (I'm fairly certain).
Instead they use a composition approach.
But not because of performance reasons, but because of flexibility.

Of course, that doesn't mean that you're not overengineering. We can't judge that without information. It could be that you're splitting classes where it's not useful, and introducing a lot of complexity where there is no need to.
For example: A resource manager class for each state sounds a bit suspicious. I'd expect a single manager class that is powerful enough to be used for all states.

But to fuck up your code in the name of performance is something that people did 20 years ago.

And even if there was valid concern about performance:
A) Performance needs to be tested. One cannot just say "oh this is too slow" without trying it out. If your performance tests show no problem, then there is no problem.

B) Performance is always secondary. First you make it work, then you make it fast.

C) You're there to learn. If you finish your project and you see that it's too slow, then you'll learn how to make it faster. If it turns out that your program architecture is so messed up that improving performance is difficult, you'll learn to keep it more simple next time.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Instead they use a composition approach.

I think that's what I'm using right now.
Like, I have a tonne of helper classes that do little bits of logic and larger classes that have instances of these helper classes and do "something" by controlling where and when they activate and whatnot.

Also, my lecturer didn't even look at any of my code before making the judgment that it was badly engineered. She just heard that I had 112 classes and 9 for states and exploded. Also, another one of my lecturers agreed with her a few hours later (also without looking at the code).

Regarding composition..
For the gameplay, I have something like (omitting some classes):
LevelTiler (generates the level randomly; has helper functions for collision detection and resolution with tiles and spikes)
Ball (Physics stuff and whatnot)
Button (An instance of a button that pauses)
Lives (Handles live calculation)
Tutorial (Handles tutorial text display)

Then, in the update() and draw() methods of the gameplay state, I just decide if and when they should update and draw or not.
My update() method has..
1 variable declaration, 4 if-statements and 6 method calls.
My draw() method has..
1 if-statement, 7 method calls.

If that's composition, then I stumbled across it on my own =P
I was coding that little 2D brawler game and I noticed a lot of the skill-code was repeating in similar ways but with different conditions and I factored the code many times until I came across it by accident and loved it to bits because of how god-damn flexible it was <3 I think it's probably changed the way I code.

Also, regarding that multiple AssetsXXX classes, I, too, think that's over kill but my original rationale was this:
These AssetsXXX classes will have to load stuff (buttons, sprites, sounds, a pool of tiles and spikes, etc.) specific to a StateXXX class. And the initialization calculations for one state, say, XXX, has nothing at all to do with the initialization calculations for another state, say, YYY.

Putting the initialization code for all states into one huge resources class would make it more difficult to change the layout of the buttons and sizes of other game objects and whatnot (because my mind gets hazy, looking at stuff from other states in one class; I have a crappy brain) and clutter the class' namespace (because there's quite a bit to load); even if I have a function loadResourcesXXX() for every state.

If StateXXX and StateYYY need the same resource, I pull those out even more. Like, AssetsSounds (which includes music, too).

I duno, I guess I could have each state's resources initialize within the state's own constructor and it would make sense but I find it easier to think about it by separating the pre-loading stuff and run-time stuff.

Or am I just being too lazy here? o.0 Of course, the lecturer didn't bother seeing my code or listening, though. Bah.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
If you want performance just code in assembler straight away and do your goto-labels stuff and pointers and processor cache work.
I agree but she doesn't =/

In C++, classes are literally non-existing. They are a high-level abstraction. They disappear when compiled into .ASM and leave only memory blocks, pointers and offsets behind.
Thanks for finally confirming my suspicions for me :)

Also, her whole argument was about the classes thing. She spoke with such conviction, it was crazy. The rest of the class just kept quiet because they didn't know anything.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Lol, I wanted to edit my post and add one more thing but you replied before then =(
Anyway, she also said something like, "What the hell? 112 classes!? I'm going to have to mark this and then you make me read through all of this? Do you know how long it'll take me to mark all your code?"
And I replied, "That's not the student's problem. I only care about finishing the assignment, it's your job to grade it."
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Composition isn't the right word.
Rather, component-orientated design.. I can't for the life of it remember the actual name.

Code:
class Animation{
    //Contains info about current animation state, etc
    void run(Object* o){
        //update animation of o
    }
}
 
class Model{
    //Contains 3d model data
}
 
class Renderer{
    void run(Object* o){
        render( o->getComponent(CID_MODEL), o->getComponent(CID_ANIMATION) );
    }
}
 
class Object{
    void addComponent(Component*, ComponentID){...}
    void getComponent(ComponentID){...}
}
 
void main(){
    Object* obj = new Object();
    Model* m = new Model(...);
    Animation anim = new Animation();
    Renderer* r = new Renderer();
 
    obj->addComponent(m, CID_MODEL);
    obj->addComponent(anim, CID_ANIMATION);
    obj->addComponent(renderer, CID_RENDER);
}

Basically you shift the entire workload of a class into components. You end up having a great variety without inheritance and tons of function overloading.
For example, I just have to switch out the Model component to give an object an entirely new look. Or switch out a "Mover" class to turn a ground-moving unit to a flyer.


Regarding the AssetsXXX classes - from the sound of it I'd group them together. A ResourceManager has the responsibility to loading requested resources, managing their memory, keeping track of their usage, etc.
In my eyes, it should not be concerned about what has to be loaded.

Each class having a single responsibility is good,
but you also have to watch out that you don't end up with several classes having the same single responsibility.

Code:
class ResourceManager{
    void loadResource(String);
    Resource* getResource(String);
...
}
ResourceManager globalResManager; //A single resource manager/cache
 
class MainMenuGameState{
 
 
    void preloadResources(){
        globalResManager.loadResource("ButtonA.png");
        globalResManager.loadResource("ButtonB.png");
        globalResManager.loadResource("SoundC.png");
        globalResManager.loadResource("MusicD.png");
    }
 
    MainMenuGameState(){
        preloadResources();
        doStuff();
    }
 
}

If you're worried about wasted memory (e.g. the MainMenu loads ButtonA.png, uses it, but it won't be removed once the MainMenu is closed, taking up space even if you'll never see the MainMenu again)
then you can implement automatic memory management: track when the resource was used last, if it hasn't been used in the last, say, 1 minute, you deallocate it.

Or you can have a destructor in every GameState that releases all resources that have been loaded.
A "release" doesn't equal a "remove", however. Release means that this GameState does not require this resource to be loaded anymore. If there is a different GameState which still requires the resource, it will not be removed.
That is very easy to implement, via a count variable, that increments everytime someone calls loadResource("x"), and decrements everytime someone calls releaseResource("x").
If count == 0, we can delete the resource.

Alternatively you can throw the entire preloading over board and do lazy loading (load a resource upon use).
I don't know how well this works on mobile devices, but for desktop computers that is more than enough. You don't even have to keep a list of resources-to-be-loaded around, because it works fully automagical.

I think a good rule of thumb is:
If you have to create several new classes to add a new GameState (or any other major class), then something is suboptimal.

Maybe you can also look at the other students.
Is there anyone who has a game of similar complexity, but only 50% of your lines of code? That would smell like a lot of duplicate code.
 

Accname

2D-Graphics enthusiast
Reaction score
1,462
Thats the way my games architecture is designed... Stupid java with its stupid lack of stupid multiple inheritance...
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Oh, component-based like Unity? (Looks totally like it.)
Yeah, guess you're right. I'll shift the code around a bit =)

I kinda' like Unity's component based system.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
I'm usually one of the first to jump on the bandwagon of "stupid Java stupid" but I rather like the omission of multiple inheritance in Java.

Interfaces and single inheritance seem be enough for most uses.

And I never liked inheritance much. I've always avoided anything more than a flat inheritance design.

Code:
class Object;
class Unit : public Object;
class Living : public Unit;
class GroundWalker : public Living, Mover, GroundCollider;
class GroundWalkerNonLiving : public Mover, GroundCollider:
class HumVee : public GroundWalkerNonLiving, Motorized, PersonContainer;
class Programmer : public GroundWalkerLiving, HomoSapiens, SometimesStupid, EatsPopcorn, HowManyRoadsDoesAManHaveToWalk;
Derp derp.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Don't like their implementation of the observer pattern, though =/
Kind of.. Verbose.
There is one minor benefit to it; you won't accidentally add a "button" event listener to a "mouse" event, even if they have the same method signature because their interface types will be totally different.

I have complained about the lack of multiple inheritance before.. I think I might still complain in future (but I do rarely use it; still would be nice to have).
 

Accname

2D-Graphics enthusiast
Reaction score
1,462
I'm usually one of the first to jump on the bandwagon of "stupid Java stupid" but I rather like the omission of multiple inheritance in Java.

Interfaces and single inheritance seem be enough for most uses.

And I never liked inheritance much. I've always avoided anything more than a flat inheritance design.
Its not about using it everyday but having the ability to use it in the rare occasion that you really want to.
There havent been many moments when i really wanted to use it, but when they were there i was really angry i couldnt do it.
Interfaces are stupid. You have to re-write the entire code in each class. Thats bad coding style, very bad coding style.
Its in the core of my own graphics engine. I just want to have a certain behaviour assured for a certain number of classes. But as it happens, some of these classes also need a different kind of behaviour as well. I cant just inherit both. Using interfaces is bs at this point because who knows how often this critical behaviour might change in the future of developement. And i have to go through all of those classes and copy+paste the code? BS.

Now i have to add even more interfaces and classes to the structure to implement delegation. Have to create the doubled amount of objects for the same task and completely mess up the architecture.
Damnit!

Don't like their implementation of the observer pattern, though =/
Kind of.. Verbose.[...]
Completely agree. Its ridiculous in my opinion. Way more complicated and blown up then needed.

By the way Mr.Camel, where exactly are you studying to get such teachers? What you are telling is the exact opposite of what every computer science teacher i ever met was talking about.
They were always like "screw performance, what counts is good design and readability." and all Object-Oriented happy.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Singapore. Temasek Polytechnic. The students are eccentric and so are the lecturers, I guess.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Whut? They have an implementation of the observer pattern? I always wrote them myself..

But yea, I guess it's good to have MI if you need it .. :3
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top