Hey, I'm sorry I haven't responded to this yet, I've had a lot going on, but this is at the top of my list for GAE development right now. Also, you didn't properly understand what I was saying and have subsequently given me new ideas to think about! I made a UML class diagram a few days ago, but since then, I gave up on the idea of de-coupling the game state & UI from the Game, so it's already out dated. I usually host images on my own server (*sniff* :'( ), so I finally created a goddam photobucket account (and it tried to get my personal info, I presume to spam me).
Connector, Interface, Proxy? (Local game, vs Network Client or Server)
So I guess the actual design pattern name I'm looking for, that I've called "connector" is really a
Strategy to facilitate the connection between the command endpoints (producers and consumers). The thing is that every host in a game will consume commands generated by every other host, so this is a distribution issue. Thus, commands host A generates will be consumed by hosts B and C, commands host B generates will be consumed by hosts A and B, etc. The same is generally true for chat messages, except that they can be selectively routed (i.e., if you're chatting with your team, you don't send a copy to hosts who aren't playing on your team). I'm not settled on the name "connector". Just like the term "interface", it seems to create confusion.
What is important here is that we have an abstract base class that everybody uses and we de-couple the logic of implementing the game across the network from the rest of the code as much as possible. I'm actually responsible for introducing a lot of this into the UnitUpdater, but I'm not overly happy with the network strategy I was shooting for earlier. What I did manage to preserve with my previous network play implementation is that commands that all hosts give to units are executed on their machines immediately, taking away from some of the feeling of lag. I'm still open to this approach in the future, but I want the new paradigm to work 1st and then we can go back and see what we can optionally turn on and off (from the older paradigm) -- I'm not going to worry about that too much right now. The only thing I might re-use from the older paradigm right now is the way new units are created on clients because the alternative is making GAE tolerant of unit IDs being inconsistent for a period of time and I'm not screwing with that.
So what should we call this layer "local vs network" layer?
Scenarios and Network PlayActually, I've been designing the new network code to work with scenarios from the start, so there should be no distinction in the "network vs local" layer as to rather or not a scenario is present. I've already merged the trunk into the network branch so it has all of the Lua stuff (minus the extensions of course). I'm not going 100% scenario support in network games initially however. This is what it will lack:
- A mechanism for Lua-generated random numbers to be synchronized
- A mechanism for stateful Lua variables to be synchronized
The optimal solution will probably be to provide an interface to Lua for scripts to store and retrieve stateful variables, create random number generators and retrieve random numbers from those generators. Then, when the network code is doing its synchronization stuff (described in the revised network paradigm thread) the client can create hashes for these objects and the server can validate them and possibly send corrections to clients that get out of sync. But something like this will be needed for meaningful mods written in Lua anyway. Specifically, the ability to store custom values globally and at the at the levels of the unit, faction and team (we may discover other scopes that are appropriate as well).
Faction Controllers (Local, Server, Client and AI)What about a player centric approach where you update a player representative and it updates all the players?
Hailstone, I'm not sure what you mean here, but I need to draw a terminology distinction. You may have noticed in the map editor that I changed the word "Player" to "Faction" everywhere. This is because, starting with the network re-write, multiple players can control a single faction. So
Faction will be the concept we formerly considered a player, they belong to a team, they get starting units and a start location on the map, they have their own resources, etc. I've been using the term
Player to describe somebody who is controlling a faction. So now, when you build a GameSettings object, you first add Teams, then you add Factions to those Teams, then you add Players to those Factions. Here's a UML diagram of how it looks now:
Should they be called FactionControllers instead, as Silnarm suggests? It would seem to clarify things. However, at this level, if they are a human player, I don't care if they are the server or the client -- each will have identical control of their respective factions. This code also doesn't currently distinguish between local and remote "players" (or "controllers"). However, when playing a local game, the networkInfo is never populated. Interestingly, the class Host derives from NetworkInfo and, in a network game, the HumanPlayer's networkInfo data member is actually populated with a pointer to the Host object (which is also abstract and is actually one of several different sub-types).
EDIT: Clarification! Also what's different here is that these classes are more data-centric than behavioral. Both in original Glest & GAE, there isn't anything special from one faction/player/controller to the next as to where there commands can go to, since each command has a UnitReference embedded in it and there is always a one to one relationship between the Command object the the Unit that's commanded. When you have multiple units selected and you issue an order, it creates a single command for each unit you have selected. The member function Commander::giveCommand() is where these commands are actually issued (locally). I'm guessing, at this point, that this is OK, but I'm certainly open.GameProxyActually, I wasn't thinking about making this an abstract class, so I'll have to consider this possibility. I like the idea of having it own the Game (possibly a NULL pointer). That way, the same object can be brought into existence as soon as the user enters a menu that can lead to game play and stay in existence until the game is over and the Battle Ending screen is exited. While we're at it, this class can hold the Stats as well, which the Battle Ending screen also needs and I've been shuffling around in a tedious fashion.
Now that I think about it more, what I don't like about making this class abstract and sub-classing for local, network client and network server roles is that it will kill our ability to transform those roles dynamically. I mean, it's true we can be a network server and if a client disconnects and the player wants to finish the game out with the AI playing the former player's role, he can just be playing as a server with no connections (like it does now actually). But this wont work if they are a client, we would have to transform them into a "local" game (or maybe a server with nobody connected to give the other player a chance to re-connect). But the main reason for this functionality is to dynamically migrate the game to a different server.
So here's a revised diagram (still using the "Connector") name.
And I don't like the name GameRoot either.
Below is roughly what the "connector" would look like:
class GameConnector : private Uncopyable {
public:
virtual ~GameConnector() {}
virtual void postCommand(Command *command) = 0;
virtual void postTextMessage(const string &text, int teamId) = 0;
};
Note that the Command class will probably have "targetFrame" added to it. If not, then it will be a parameter of the postCommand() function.
So the menu will create the "GameRoot" object to setup the game (connections, settings, load saved game and scenario if any). Then, when it launches, the GameRoot can manage creating the actual Game object and such. Maybe we can call this GameManager and give it all of the responsibilities for "managing" the game play state? I don't mean to derive it from ProgramState, just from a behavioural perspective.
AI ControllerI should note here that in the original Glest as well as current and planned GAE networking, AI code is executed on all hosts of the game. Martiño's original plan to keep this in sync is to use a custom psudo-random number generator so that (in theory) each host would generate the same numbers when seeded with the same seed. However, inconsistencies with networking can cause this to get out of sync. So part of my "client sync" I described in the networking thread is to make sure that each AI's random number generator is at the same value across hosts. If everything else is in sync, they should behave exactly the same across hosts.
ProxiesFinally, this is another old diagram of the new networking classes. I posted this in the other thread, but with codemonger.org down, it wont show, so here's the photobucket copy. I don't have time to re-do it right now, but just know that I nixed RemoteHost and replaced it with RemotePeerInterface directly, instead of having RemotePeerInterface inherit from RemoteHost. I also made Host derive from NetworkInfo, so all of those data members are now in the base class. Finally, I renamed {Game,Server,Client}Interface to {Game,Server,Client}Connector and will probably rename it again.
I think that what I previously called RemotePeerInterface is best called something-Proxy. Ditto for each of the subclasses. Here, "peer" is an abstraction of client or server, but can also represent a real "peer", which is the connection between two clients. I think there's still room for improvement here, but I don't have time to get into that at the moment.
Please let me know what you think and thanks for all of the feedback, it's been exceptionally helpful!!!!