Using a state stack instead of switch

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • RE: Using a state stack instead of switch

      If you mean the game state switching in the book - then the reason I use it is twofold:

      1. It is much easier to explain, and learn.
      2. Since the game states themselves are strictly linear, there's no reason to use a stack. By linear I mean that state B always follows state A, and state C always follows state B.

      If your game has more complicated state transitions, by all means use a stack.
      Mr.Mike
      Author, Programmer, Brewer, Patriot
    • Personally, I use a much more robust system than a simple stack. I found out last year, following our first stab at engine design, that I had the need to run concurrent states. For various reasons, this was the case, partially due to the way that we divided up the game logic.

      I didn't, at that time, think of a system like Mike's Process System for just general persistent processes, however. Therefore, the introduction of this system would greatly reduce the need for a stack.

      However, Turing, to perhaps give you a head-start out of a pitfall that I found, try this system:

      Currently, I have two categories of states. Persistent States and Non-persistent States. For the former, I have a std::vector of GameStates on which I, for every game loop iteration, call Update(). These states cannot, in it of themselves, change state. More on this later. For the Non-persistent States, I have a stack of std::vectors into which I add states. These lists can be pushed or popped accordingly.

      That way, you have the power of a stack, the ability to run concurrent states (if you have the need), and the ability to have states running in the background at all times.
      Feel you safe and secure in the protection of your pants . . . but one day, one day there shall be a No Pants Day and that shall be the harbinger of your undoing . . .
    • @MrMike Ok, I assumed that that's the reason, but one can never be sure without asking :)

      That sounds nice. My Idea was a little more complex, I thought about implementing a stack, and every ProcessManager is a state and then I swap the processes of one states and insert the processes from the next state (oop makes that easy, I have to create only an instance of ProcessManager therefore).
      But I was already thinking about common processes between different states. Your solution sounds very nice, I think I'll try it.
    • Hopefully that will help. It's currently looking like it works quite well with the general design of our game. Oh yes, and I forgot to mention how states change state, if you're still interested. The thing is, at times, we'd have menus being pushed over game states, so in order to allow for a few niceties in transition effects and things of this nature, states can change state.

      For instance, if there is a state that is running, and a new GameState gets pushed on top of it, then the OnPause() is called for the state that was just superceded. Once the superceding state is popped off, the OnResume() function is called.

      I'm interested in how you're planning on doing your system, though. I'm not quite sure what you mean by swapping out the processes of one state for another. How would that work?
      Feel you safe and secure in the protection of your pants . . . but one day, one day there shall be a No Pants Day and that shall be the harbinger of your undoing . . .
    • Ok I'll try to explain it with an example:

      Source Code

      1. //Our state-base-class
      2. struct State
      3. {
      4. virtual void onEnter() = 0;
      5. virtual void onUpdate() = 0;
      6. virtual void onLeave() = 0;
      7. virtual ~State(){}
      8. };
      9. struct StateManager
      10. {
      11. void push( boost::shared_pointer< State > state );
      12. void pop();
      13. void process(); //calls onUpdate() for topmost state
      14. std::stack< boost::shared_pointer< State > > stateStack;
      15. };
      16. //process base class
      17. struct Process
      18. {};
      19. //process manager
      20. struct ProcessManager : State
      21. {
      22. std::list< boost::shared_pointer< Process > > processList;
      23. };
      Display All


      And then just push and pop the ProcessManagers as needed for changing the states.
      But your solution is much better. In my idea updating the sound,ai,... needs to be inserted in every state's process list.
    • Looks good, but I do have some constructive criticism. In general, you shouldn't use structs for anything other than data. Having constructors, a destructor, and maybe the assignment (=) operator overloaded is okay, but you've defined virtual functions. Those structs should really be classes.

      In C++, structs and classes are exactly the same, except that structs default to public while classes default to private. Still, it all comes down to readability. If I were a programmer on your team and hovered over the object name somewhere buried in 500k lines of code to see "struct State", I would assume it was all data like an old-school C struct.

      I remember when I was in school and we had the finals for my Microprocessor class. I aced the test except for one thing that the instructor took off 5 points for. In my code, I wrote:

      Source Code

      1. cmp eax, ecx
      2. jz somewhere

      I should have written:

      Source Code

      1. cmp eax, ecx
      2. je somewhere

      I was pissed. I mean, what's the difference? je and jz literally produce the exact same machine code (that is, the same 1's & 0's fly through the CPU). He took the points off because jz means "jump if zero" while je means "jump if equal". I was comparing eax to ecx, so the more logical statement would be "jump if equal.

      The moral of the story: Code is for the programmer, not the computer.

      -Rez
    • Oh I'm sorry. I was using struct only to save some lines for the small example to show you what my idea looks like :)

      Except in small test cases (where I often need access to private elements due to the lack of a full interface) I'm using always classes and a nice interface.
    • I've just changed my mind (maybe because I'm too new in programming a game on my own). I'll use this approach, I think it gives me the same flexibility but is much easier to use:

      Source Code

      1. struct Process
      2. {
      3. virtual void onUpdate() = 0;
      4. };
      5. struct ProcessManager
      6. {
      7. void updateAll();
      8. std::list< Process > processList;
      9. };
      10. struct StateManager;
      11. struct State
      12. {
      13. friend StateManager;
      14. virtual void onUpdate() = 0;
      15. //Interface for inserting processes and so on, maybe even direct access to >processes< (although it's not the best design)
      16. private:
      17. void update()
      18. {
      19. processes.updateAll();
      20. onUpdate();
      21. }
      22. ProcessManager processes;
      23. };
      24. struct StateManager
      25. {
      26. void processTopMost();
      27. std::list< State* > states;
      28. };
      29. //Inside the main loop
      30. ProcessManager permanentProcesses; //will probably be inside the application layer class
      31. StateManager states; //dito
      32. for(;;)
      33. {
      34. states.processTopMost();
      35. permanentProcesses.updateAll();
      36. };
      Display All


      permanentProcesses will contain subsystems like AI, Graphics, Physics, etc.
      and the ProcessManager in the state will contain animation processes, interface elements and so on

      But I'm not sure if this design will work. With animations it will work, but then there are still rendering issues and interface elements need rarely updates, they react to events (through an EventListener) and they get rendered.
      So how shall I put the rendering part in this design. Any ideas?


      /Edit:
      I think I have to face the truth, after browsing once more through the Game Coding Complete source, and sit down for one or two days to develop an overall design how everything shall work :/

      The post was edited 2 times, last by Turing ().

    • I have to agree with you, thinking out your overall design is a great idea. However, you only really learn by doing. So I would say use what you've developed, and then learn from your mistakes and successes. It is true, though, that you learn far more from your mistakes and failures than from what you do correctly.

      Try it, my friend, and see how it works. All of it really depends upon how you define your GameStates, what they do, how they encapsulate the processes. I define a GameState to be a bit more than just a collection of Processes. However, this may or may not be a proper definition for the design that you have layed out for you game.

      So, the best advice I can give you is to just do it, see what you like and don't like about it.

      EDIT: Might I ask what your game design is?
      Feel you safe and secure in the protection of your pants . . . but one day, one day there shall be a No Pants Day and that shall be the harbinger of your undoing . . .

      The post was edited 1 time, last by Tarviathun ().

    • What do you mean with game design (I'm not sure, because I've written above that at the moment I have no design).

      When I'm finished with my (program-)design I'll show it to you.

      If you mean with game-design what the game is, then it's yet the game from the book "Programming Role Playing Games with DirectX", because the source code is awful and lacking basic things like recovering from alt+tab I thought I'll write it myself while reading the book. At the moment it's not my goal to develop a real game, it's more like some practice for my rpg I'm planning to write.
    • Ah, ok. I didn't what kind of game you were planning on making. And thus I was curious.
      Feel you safe and secure in the protection of your pants . . . but one day, one day there shall be a No Pants Day and that shall be the harbinger of your undoing . . .
    • Now my process manager is finished, in the end it was not that easy as I thought at first. Because removing and adding processes when processes can have a successor that is inserted when a process is removed, it gets quite tricky.

      But now all works fine however it's not the fastest way.
      At the moment I'm doing the following:
      attaching a process inserts the process in a queue
      removing a process sets a flag of this process to true
      updating the processes first iterates through the process list and deletes all processes that have the flag set (allowing successor to be inserted in the queue for this update call)
      then alle processes from the queue are inserted into the process list
      and now (finally) the processes get their update call

      As you can see if there are now processes to be deleted there is one useless iteration through the process list.

      I have already a solution for this, but it has some restrictions:
      Updating and Removing happens in one loop, when a removed process has a successor I store it in a temporary list.
      After iterating through all of the processes I iterate through the successor list and update them (and insert them into the process list).
      The "drawback" is that when now a process decides to attach a process in it's onDetach event this process is not updated until the next updateProcesses()-call for the process manager.

      Now I'm hoping someone of you has a better idea, otherwise I'll have to live with my idea.

      Thanks.
    • After standing up this morning the very solution came to my mind.
      All I have to do is:
      -iterate through all processes call update or delete the process if the flag is set
      -now iterate through the process queue update all of them and insert them into the process list

      and that's all


      Sometimes it's just too simple to come up with it.


      Edit:
      Ok, I admit it, it still has one flaw a removing and adding is now delayed to the next update call and not done immediately. But my workaround uncovered another problem: now processes could be inserted into the actual update call and not into the next round of update calls.
      Now I'll stay with the first solution, using the second and avoid this requires another flag in the process class and it's already to bloated with process manager specific things (I wanted to avoid a container class for the process which holds this information).

      The post was edited 1 time, last by Turing ().

    • What's the issue of pushing on the Process dependancy list as you're updating? I don't see anything wrong with it. You find the delete flag, add the dependancy(ies) to the end of the list, remove the process that's dead, continue updating. If it's a list and not a vector, your iterator to the current node won't be invalidated unless it's pointing to the node to be deleted.
      Feel you safe and secure in the protection of your pants . . . but one day, one day there shall be a No Pants Day and that shall be the harbinger of your undoing . . .