Advertisement

Ways of differentiating behavior using an object pool pattern?

Started by August 06, 2021 04:00 PM
26 comments, last by DevReign 3 years, 3 months ago

@Shaarigan I appreciate the suggestion. I'm focusing more on making a game than an engine, but I was still going to try making an allocator prototype just to see how difficult it would be. If it ended up taking a couple hours and fixes some issues I have with C++ it might be worth it. I'm not set on anything specific, just seeing what my options are to tackle this problem.

Maybe I missed something, but in the link you sent it didn't show if they included allocator.h or did they allocate everything using new?

None

What I have been trying to explain (in vain) is that “future proof things” is a 99.9% wasted effort. It assumes you know what problems will arrive but the fact is, you don't. The only exception here is if you can proof it (ie running a routine every frame that takes 1 second to execute is clearly not going to work!).

You're like me trying to guess where the spot of trouble is going to be in the execution profile. I have been wrong 100% of the time in programs that were known to have a problem!! You're fixing for problems that you don't even know will happen (and since we are very bad at guessing future, most likely wont).

So the better solution is to wait until the future arrives. Then you have more information, and can fix stuff at exactly the spot of trouble. Minimal effort maximal result.

Write the normal solution. Programming language, compiler, runtime libraries, memory allocators, operating system, literally everything is tuned for the normal case. Follow that pattern and you'll get good performance unless you do “non-normal” stuff at runtime. (But also this can be judged much better in the future, since then you actually have the code in all the details and can analyze what is wrong (or what point in the design was misunderstood, more likely).

There are code refactoring tools if you need to move code around, although in my experience with properly used types and a compile-time type checker you can even do this by hand without major trouble. It's not unlikely that this is a faster path than trying to be "flexible" beforehand (again the “future troubles are not where you think they are” thing applies together with the “you cannot be flexible in every possible way”.)

I always aim for the simplest fundamentally correct solution. Partly because this is required as result, but also because it gives you the most clean program you can write. It makes thinking and reasoning about it, and changing and finding trouble spots faster and simpler, since there are no “work-around future fixes” in place that can throw spanners into the system. Any problem is going to be in the solution itself.

We like to think we are good at understanding the problem and are good in programming, but in fact, we can barely remember 7 things at the same time and just can think about 3 lines of code at the same time or so. Any real-life program is bigger than we can manage comfortably. So we need lots of stuff around it to help us remember all the little details at the right moment, and check that we didn't forget something somewhere. The more stuff you have, the higher the chance you miss something. Missing stuff here is often a fundamental mistake that needs lots of work to fix. Simpler is better, less is better. Converting the idea to a real program is really big enough to chew on.

When I have a running program, it's usually “good enough” (ie average stuff usually runs well), and I am done. If not, I start analyzing it, and thinking how to extend the solution to incorporate whatever I missed. Extending with eg an allocator can be done afterwards too usually by just a few code shuffles (namely, all “new” calls need a different line of code).

Advertisement

@Alberth It has not been in vain, I understand what you mean.

Alberth said:

I always aim for the simplest fundamentally correct solution. Partly because this is required as result, but also because it gives you the most clean program you can write. It makes thinking and reasoning about it, and changing and finding trouble spots faster and simpler, since there are no “work-around future fixes” in place that can throw spanners into the system. Any problem is going to be in the solution itself.

It's not clear to me which is the simplest fundamentally correct solution that results in a clean program and accomplishes what I require. I'm currently trying out several methods I've discovered recently, including your example and trying to figure out what fits that criteria.

None

DevReign said:
Maybe I missed something, but in the link you sent it didn't show if they included allocator.h or did they allocate everything using new?

If you're looking for an implementation, there is a project on GitHub I took a look onto also when writing my own classes.

Or you can PM me and I can send ya our engine core files. They aren't be uploaded to GitHub at the moment as we're doing a massive refactor of everything and the engine core will be next to refactor after our UI framework is done. But I still have snapshots of everything ?

@DevReign Regarding the AI, I am using the Finite State Machine (FSM) as described in Programming Game AI by Example. Rather than having a giant switch statement, I have a pointer to a FSM that tells my robot what to do. It's really flexible and fairly easy to manage. Changing a robot's behavior, say from attack to retreat, is as easy as replacing the pointer.

@Shaarigan Thanks for the link. I think I learn more from source code than anything else these days. I'd be interested in checking out your engine sometime, just lmk when it's up on Github.

None

Advertisement

@scott8 I always set up my FSM with a big switch statement lol. I'd be curious to see how you implemented that. Are you able to an example with some source code?

None

@DevReign Maybe an easier example: I wrote a chemical drawing program, which responds differently to mousemove and LMB down messages depending on the tool the user selected. My first draft looked like this:

select (ToolType)

{

case DrawingTool: DrawStuff(); break;

case LassoTool: SelectStuff(); break;

case EraserTool: EraseStuff(); break;

[a ton more case statements]

}

Now, I just set a pointer when the user selects the tool. The whole switch statement gets replaced with

if (pTool≠nullptr)

pTool→DoStuff();

It's a programming pattern, I can't remember the name.

I did the same thing with my ‘Game Stages’. In my game loop, I had another switch statement like:

Select (MyStage)

{

case: GameStage: PlayGame(); break;

case HiScoresStage: ShowHighScores(); break;

case TitleStage: ShowGameTitle(); break;

}

Now, I have a BaseStage, and the GameStage, TitleStage, HiScoreStage, PauseStage,CreditsStage that all inherit from it.

The big switch statement in my game loop is replace by

pCurrentStage→Update(DeltaTime);

If the user hits ‘Escape’ in the GameStage, then pCurrentStage = pGameStage(); and the game is paused. When the user hits escape in the pause stage, then pCurrentStage=pGameStage and the game continues.

You can do the same thing with FSMs. For example if a robot spots the player while in the PatrolState, then pRobotState = pPursueState(pPlayer). If the Robot looses line of sight with the player while in the PursueState, then pRobotState = pSearchState.

It's maybe a little more complicated than it sounds, but it's explained well in that book I mentioned.

@scott8 That looks a lot cleaner than a giant switch statement. I think I understand the general idea of the pattern. I'll have a much better understanding once I try to implement it myself. I added the book to my amazon list, but i'll probably wait to get it until I finish the ones i'm reading or it goes on sale. Thanks for showing me, I appreciate the help.

None

DevReign said:
It's not clear to me which is the simplest fundamentally correct solution that results in a clean program and accomplishes what I require.

Was offline for a few days, so the answer is a bit delayed.

What that means exactly depends on the game. Basically, the goal is to have all cases in the game properly handled including the various edge cases. If you need to collect a key to open a door, then at all times when you have the key, the door should open. If the door has additional conditions, not only the key is needed but also all the other conditions need to be fulfilled. Ie everything should work the way you intend it, without doing more is preferable.

The smaller the program, the simpler it is to check for being correct. You can also use it as a baseline for a performance profile.

This topic is closed to new replies.

Advertisement