I'm developing a small multiplayer game. It'll be served by one websockets server and consumed by multiple consumers. As such I need to be cautious about concurrency errors.
The general software architecture I've come up with is: Encapsulate all game state into the highest-level Game class and don't allow other threads to access gamestate objects. Instead, clients (websocket threads on the server) send commands to the Game class where they're added to a threadsafe list and later processed by the Game.Tick method which is called in a separate thread. For example:
Game.AddCommand assigns a command to the list of incoming commands:
public void AddCommand(Guid id, ICommand command)
{
lock (_commandsLock)
{
_commands[id] = command;
}
}
Game.Tick processes each command and clears the list:
public void Tick()
{
IList<ICommand> commands;
lock (_commandsLock)
{
commands = _commands.Values.ToList();
_commands = new Dictionary<Guid, ICommand>();
}
foreach (var command in commands)
{
command.Resolve();
Observer.Update(_map);
}
}
The issue is this: To resolve themselves each Command needs access to the internals of Game. I intend to use a number of services such that a Resolve method can be very few or even a single line, such as movementService.Move(entity, target). If I passed these services into the Resolve method, each Command would become bloated with things that it doesn't need.
Instead, I'd like to use DI. But since each Command is created by a client, they can't access the services that belong to Game. These services need to be exposed, and if the client does something wrong (ie call Resolve themselves) they could change gamestate and cause concurrency errors.
I tried to resolve this by creating the notion of a CommandResolver, in which case a Command would be a simple model with some parameters and nothing more, resolved by an internal CommandResolver which has access to gamestate. But this required elaborate mapping (eg map MoveCommand to MoveCommandResolver- each command is a different type with different dependencies) which defies the Open/Closed principle- you have to modify the mapping, likely a long awkward switch statement, rather than simply extending the functionality with a new Command & CommandResolver.
Now I'm scrapping the resolver and considering one of two possibilities: a) Expose from Game a CommandFactory that can be used to generate commands, prehaps with a syntax like game.GetCommandFor(entity).Move(target). The commands then have access to gamestate and resolve themselves. b) Commands are constructed by clients, but before being resolved an Initialise(IUnityContainer) method is called that gives the Command access to gamestate. This way it only gains access to gamestate once the command is ready to be issued. Of course, the client could still technically hold onto the command and call it at a later date.
Any thoughts on what the best solution is? My primary concerns are easy extensibility while ensuring the integrity of Game's internal state.