Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>The theme I find is that networking code is not really unit testable, and should be isolated from the code you do wish to test (the Player object, for example). To me it seems you have tightly coupled a Player to a ClientConnection, which might make it less testable. I also think that coupling a Player with his connection is probably violating the SRP, since they have very distinct responsibilities.</p> <p>I've written a system which seems similar to what you wish to acheive, and I done so by linking a player to a connection only via delegates and interfaces. I have a separate World and Server class library, and a third project, Application, which references the two, and creates an instance of Server and World. My Player object is in the World, and a Connection in the Server - these two projects do not reference one another, so you can consider Player without any terms of networking.</p> <p>The method I use is to make SocketHandler abstract, and implement a concrete handler in the Application project. The handler has a reference to the world object, which can be passed to it on creation. (I do so by means of factory classes - eg, ClientConnectionFactory (in Application), where the Server knows only of abstract Connections and Factories.)</p> <pre><code>public class ClientConnectionFactory : IConnectionFactory { private readonly World world; public ClientConnectionFactory(World world) { this.world = world; } Connection IConnectionFactory.Create(Socket socket) { return new ClientConnection(socket, world); } } </code></pre> <p>The ClientConnection can then forward the World reference to it's handler, along with any information the handler requires about the Connection itself, such as the Send methods in particular. Here I just pass the Connection object itself to the Handler.</p> <pre><code>public ClientConnection(Socket socket, World world) : base(socket) { this.handler = new ClientHandler(this, world); ... } </code></pre> <p>Most of the coordination between the game logic and the networking is then contained inside the ClientHandler, and perhaps any other sister objects it references.</p> <pre><code>class ClientHandler : SocketHandler { private readonly Connection connection; private readonly World world; public ClientHandler(Connection connection, World world) { this.connection = connection; this.world = world; ... } //overriden method from SocketHandler public override void HandleMessage(Byte[] data) { ... } } </code></pre> <p>The rest involves the ClientHandler creating it's Player object, assigning delegates or interfaces to update actions, and then adding the player to the pool of players in World. Initially I done this with a list of events in Player, which I subscribe to with methods in the ClientHandler (which knows of the Connection), but it turned out that there were dozens of events in the Player object which became a mess to maintain.</p> <p>The option I went for was to use abstract notifiers in the World project on the player. For example, for movement I would have an IMovementNotifier object in Player. In Application, I would create a ClientNotifier which implements this interface and does the relevant sending of data to the client. The ClientHandler would create the ClientNotifier and pass it to it's Player object.</p> <pre><code>class ClientNotifier : IMovementNotifier //, ..., + bunch of other notifiers { private readonly Connection connection; public ClientHandler(Connection connection) { this.connection = connection; } void IMovementNotifier.Move(Player player, Location destination) { ... connection.Send(new MoveMessage(...)); } } </code></pre> <p>The ClientHandler ctor can be changed to instantiate this notifier.</p> <pre><code>public ClientHandler(Connection connection, World world) { this.connection = connection; this.world = world; ... var notifier = new ClientNotifier(this); this.player = new Player(notifier); this.world.Players.Add(player); } </code></pre> <p>So the resulting system is one where ClientHandler is responsible for all incoming messages and events, and ClientNotifier handles all outgoing stuff. These two classes are quite difficult to test as they contain a lot of other crap. Networking can't really be unit tested anyway, but the Connection object in these two classes can be mocked. The World library is entirely unit testable without any consideration of networking components, which is what I wanted to acheive in my design anyway.</p> <p>It's a big system, and I've not covered much of it here, but hope this can give you some hints. Ask me if you'd like any more specific information on it.</p> <p>If I could give any more advice, it would be to avoid any static or singletons - they'll come back to bite you. Favour increasing complexity if you need to as an alternative, but document where you need to. The system I have might seem complex to a maintainer, but is well documented. To a user it's particularly simple to use. Main is essentially</p> <pre><code>var world = new World(); var connectionFactory = new ClientConnectionFactory(world); var server = new Server(settings.LocalIP, settings.LocalPort, connectionFactory); </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload