TIGSource Forums

Developer => Technical => Topic started by: soryy708 on October 29, 2013, 07:45:42 PM



Title: Software design, client-server architecture, and game state
Post by: soryy708 on October 29, 2013, 07:45:42 PM
Greetings fellows.
I've been trying to "un-fuck" a fuck-up I've gotten myself into by having a container for all game state related things. Unfortunately, it needs to know too much and I end up with C/C++ header nightmare. I need to re-think my strategy.

My failed attempt was to have a State class which simply contains all game objects, the player, input (keyboard and mouse state), yadda yadda, and then pass it around.
Need to render? Pass it a state object. The renderer uses get() methods to get the information it needs. Need to do logic? Pass it a state object and it will iterate over it, calling callbacks. And most importantly:
Need to be dictated by the server? Pass a reference to the state object to the networking class, and it will modify the right values to synchronize with the server, in addition to getting the information it needs to upload to the server.
Unfortunately, it doesn't work out too well the way I do it.

How do you guys do it?
How do you keep track of the game state (and make sure that the correct modules can get all information they need) plus have it friendly to networking using client-server architecture?


Title: Re: Software design, client-server architecture, and game state
Post by: _matt_ on October 29, 2013, 09:28:41 PM
This is a good question, and very similar to a post I opened a few days ago.  I am very interested to hear the responses!

My opinion, having read about this for the past few days is just to focus on making your objects as modular as possible.

Now that being said, you seem fairly advanced and will likely not be satisfied with that answer.  What I came across was the model-view-controller approach, which might suit you.  You basically put the game state in the model.  You have controllers (and sub-controllers when things get complex) for AI, physics, user input, etc.  There are views to render to the screen or to provide feedback over the network.  The controllers can update the view and the model (or you can use a subscription system which might be overkill), the view can pull from the model and the model knows nothing about the view or the controller.  Anyways, just learning so perhaps I am just confusing things by posting but thought I'd pass on what I've found.




Title: Re: Software design, client-server architecture, and game state
Post by: George Michaels on October 30, 2013, 12:55:21 AM
I do it by doing the exact opposite of what youve got op... Instead of everything querying the state class, make the state class call and set things in the other components.

How you put it: Need to render? Give the render module what it needs to know. Need to do logic? Call the update function in your state.

Its also a better idea to limit your state to just initialize, update and terminate functions. Set up your rendering, networking, entity storage, etc in the initialize function, run your game logic and update your modules (eg update the positions of shapes in your renderer) then push the results (draw to thr screen) in thr update function, and free all your data and terminate your modules in the terminate function.

Basically, instead of having everything calling the state, have each state subclass call everything else.


Title: Re: Software design, client-server architecture, and game state
Post by: soryy708 on October 30, 2013, 03:54:18 AM
Quote
Basically, instead of having everything calling the state, have each state subclass call everything else.
I don't see how that approach fixes the problem.


Title: Re: Software design, client-server architecture, and game state
Post by: Average Software on October 30, 2013, 04:27:01 AM
It's difficult to answer this question in detail, but in a broad sense it sounds like you're over object-orienting things.

When it comes to networking, one of the things I do is to make the game always behave as if it were networked.  Even in the offline modes, the logic operates by sending messages to a server and receiving responses.  When it comes time to actually be networked, all you have to do is change where the messages are going.

This is difficult to do unless you plan for it from the very beginning.


Title: Re: Software design, client-server architecture, and game state
Post by: soryy708 on October 30, 2013, 04:48:33 AM
Yes, I realize that. This is why I find it so very important to get right.
Do a bad design choice early on, regret it later. My choice: Experiment, rewrite, repeat.


Title: Re: Software design, client-server architecture, and game state
Post by: George Michaels on October 30, 2013, 01:34:20 PM
Quote
Basically, instead of having everything calling the state, have each state subclass call everything else.
I don't see how that approach fixes the problem.
Because your parent state class doesnt need to know anything at all, just your child state classes and they only need to know the things they need to.

The system I gave you is only an extremely simple version. The way my engine works is that I have state stack that runs states in in FILO. So the first state I push controls rendering, input and event dispatching by initializing those modules and running them. The next state controls entities, what to render, handles most events. The last state controls the players input, sends its info to the server and runs collision detection. Each state only knows a little bit and only controls a little bit. Each state object is just initialize, tick and terminate functions pointers to the next and previous states in the state stack, nothing else.


Title: Re: Software design, client-server architecture, and game state
Post by: hatu on October 31, 2013, 04:04:38 AM
It's difficult to answer this question in detail, but in a broad sense it sounds like you're over object-orienting things.

When it comes to networking, one of the things I do is to make the game always behave as if it were networked.  Even in the offline modes, the logic operates by sending messages to a server and receiving responses.  When it comes time to actually be networked, all you have to do is change where the messages are going.

This is difficult to do unless you plan for it from the very beginning.

That's definitely the way to create multiplayer logic but I disagree on over-object orienting. If anything it's not enough - you basically just have one behemoth class that controls everything (State).

I guess what you can try to do is try to think of logical responsibilities for different classes and keep them separated. The game state doesn't need to be involved in everything.

Network master class has these responsibilities:
*Connect to the server
*Receive position data and send it to the local NetworkPlayer instances.
*Send position data of your player.
*Receive game events (like round ended) and apply them to the GameState instance.

So NetworkPlayer can be controlled by Network class the same way LocalPlayer class is controlled by Input class.
Input calls localPlayer.shoot();
Network calls networkPlayer1.shoot();


Title: Re: Software design, client-server architecture, and game state
Post by: dewitters on October 31, 2013, 06:21:39 AM
Game architecture is indeed something you need to consider when writing games. I created a few articles on this topic that might be helpful:

http://www.koonsolo.com/news/model-view-controller-for-games/
http://www.koonsolo.com/news/dewitters-gameloop/

You should also use Component Based Game Engine Design, for which you can get a quick introduction at http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy and more detailed at http://stackoverflow.com/questions/1901251/component-based-game-engine-design.

Remarkt that this last one goes hand in hand with the model view controller.


Title: Re: Software design, client-server architecture, and game state
Post by: soryy708 on October 31, 2013, 11:37:23 AM
I guess I can see how component based design solves the issue of blob GameObjects (which I haven't brought up but is quite annoying). Last time I tried to implement it, I had issues with synchronization (some components depend on others, and writing new entities seems to require a lot of boilerplate just to describe the interaction between the components).

I guess that what you offer is: instead of querying the "State" object I described, query a container of the specific components that the module in question works on. So instead of asking the State object for all renderables, query a container of all renderable components. Yes, I guess I can see how this works.

This makes me raise an other question:
Who is the container?
I can think of two approaches:
- either have the component itself have a private static container of all objects of this component alive and a static get function
- or have a factory for creating components. The factory will have a private container of all components it constructs, and a getter for it.
The former approach is simpler and perhaps more convenient to use (just new() or use RAII instead of asking some factory object that you have to have a reference to). The later avoids the former's design smell (statics), but is more boilerplate and perhaps more cumbersome.

Personally, I like RAII so I'd appreciate doing something like
Code:
class Player
{
private:
    PositionComponent position;
    InputMovementComponent movement;
    RenderableComponent renderable;
    SpriteSheetComponent spritesheet;
    NetworkSendComponent network;

public:
    Player() :
            movement(position),
            spritesheet(renderable),
            network(position)
    {
    }
};
To those unfamiliar with RAII, this basically means that the component's lifetime is bound to the object's. All of the components are automatically allocated when the object is constructed, and deallocated when destructed without any additional effort on my part.

I can see how this would work with the first approach I've listed, but I don't see how I could use the second approach here. If I'd use a factory, I'd have to replace all these beautiful objects with pointers, and then handle their lifetime by myself. Oh, and I'd have to pass the factory to the player's constructor.

Also, the most convenient way to use the second approach (a components factory) would be to have a singleton. It seems like all it does is get rid of one static by making an other, in addition to over-complexifying everything.

Has anybody got any reasons to use a factory? Or is the first approach the way to go?
Alternatively, have you got a third proposition?


Title: Re: Software design, client-server architecture, and game state
Post by: Xienen on October 31, 2013, 12:50:07 PM
There's all this stigma around static members and global variables, but I think that's a whole bunch of PhD hoopla.  There are often good reasons to use statics and even globals...if it makes good sense to you and you believe you are competent, then any other competent programmer can pick up your code and maintain it.  I've been working with the Cube 2 engine recently and it uses a ton of globals(it's mostly C code), and I've had no problem learning how to work with it.  Would I have used as many globals as they did? Probably not, but their architecture makes sense, so it really doesn't matter.

It sounds like the static variable system makes sense and will be quite easy for you to work with.


Title: Re: Software design, client-server architecture, and game state
Post by: soryy708 on October 31, 2013, 02:46:28 PM
So.

We have a networking module. When it's updated, it does a NetworkSendComponent::getAll() to know what to send to the server.
Problem: The order of the components. I can think of a reason for a networking protocol to demand a very specific order for the components to be sent to the server (and received) in.

Now comes an other problem. NetworkReceiveComponent. I guess I can require the network module upon construction so that it could bargain an unique identifier for every receive component so that one could know what does the data in the packet refer to. The bigger issue is when the server asks the client to create a new GameObject.
It's possible that, for instance, a player joined the game & the server broadcasted that to everyone already connected. Now they need to construct a new actor GameObject. Who does that?
It doesn't make sense to do it in the networking module (partially because it doesn't even have all the necessary information). I guess it could enqueue a job into an other object, but whom?


Title: Re: Software design, client-server architecture, and game state
Post by: Gregg Williams on October 31, 2013, 03:05:14 PM
It's possible that, for instance, a player joined the game & the server broadcasted that to everyone already connected. Now they need to construct a new actor GameObject. Who does that?
It doesn't make sense to do it in the networking module (partially because it doesn't even have all the necessary information). I guess it could enqueue a job into an other object, but whom?
With whoever registered to handle that specific packet.


Title: Re: Software design, client-server architecture, and game state
Post by: soryy708 on October 31, 2013, 03:16:35 PM
Code:
With whoever registered to handle that specific packet.
Sorry, what? That's a bit too abstract.


Title: Re: Software design, client-server architecture, and game state
Post by: Gregg Williams on October 31, 2013, 03:39:07 PM
Talking about game network architecture, I've almost always done and seen it done the following way.

1. You have packets with IDs. AddUnit, RemoveUnit, UpdateUnit, AsciiMessage, etc.
2. You have packet handlers, which are setup to handle a specific packet ID. Usually via callback or event notification.

So like a move unit request from a client would have a handler on the server. When the packet arrives its passed to the handler to decode/process. Using a unique I'd for the unit sent with the packet or possibly IP address (depends on game design) the handler would look up the unit, ensure its valid, ensure the client has ownership, etc, and tell it to move. Acknowledgment or rejections would get sent back as needed.


Title: Re: Software design, client-server architecture, and game state
Post by: _matt_ on October 31, 2013, 07:53:57 PM
Quote
If I'd use a factory, I'd have to replace all these beautiful objects with pointers, and then handle their lifetime by myself. Oh, and I'd have to pass the factory to the player's constructor.

No matter how you organize it, there are going to be cases where a given piece of code needs access to nearly everything.  AI seems to have this global need for data.   However, with the component model you can minimize yourself to just working on the components that are relevant to the given controller.  So your AI could at least exclude the network, rendering & sprite components in your example.


Title: Re: Software design, client-server architecture, and game state
Post by: InfiniteStateMachine on October 31, 2013, 08:42:33 PM
There's all this stigma around static members and global variables, but I think that's a whole bunch of PhD hoopla.  There are often good reasons to use statics and even globals...if it makes good sense to you and you believe you are competent, then any other competent programmer can pick up your code and maintain it.  I've been working with the Cube 2 engine recently and it uses a ton of globals(it's mostly C code), and I've had no problem learning how to work with it.  Would I have used as many globals as they did? Probably not, but their architecture makes sense, so it really doesn't matter.

It sounds like the static variable system makes sense and will be quite easy for you to work with.

I agree. It's not always 100% a bad thing to do. I'm always skeptical when something is presented as a tautology in the programming world.