Thinking of switching from a monolithic structure to a Client-Server structure

Started by
5 comments, last by Alberth 8 years ago

I'm not too far in developing the logic systems of the engine, and I'm also considering switching from Lua to Mono for scripting purposes... because I'm slowly coming to realize how much of a downright ass it can be to develop some reusable systems in Lua. While I will loose some things, I'll gain in others.

  • Serialization - a bonus if I can get it to work correctly
  • Some basic RTTI - Makes constructing the editor easier
  • Asynchronous Logic (?) worth looking into. Being able to split up entities with a unique scheduler is attractive. Especially if I can consider update frequencies.

So outside of that. The contemplation of the switch also made me consider if I should change the setup of the logic. Multiplayer support was planned for later in the lines. And being monolithic would make this a down right pain to do. Basically, the most sane way I can see this is by splitting the application code into two different paths that'd be instantiated at the menu. One for Single player, the other for multiplayer.

Where as the client server mock up seems like it has a one size fits all. The downside is that you have to program for packets, which you'll have to do anyways in multiplayer.

Advertisement
The downside is that you have to program for packets

Not necessarily. Yes, you'll have to design around the idea that the game manager is a service, that the user's view of the game cannot simply reach inside the game manager to get information or make changes, and that the two sets of logic will have to communicate in a highly decoupled manner; but that's actually preferable anyway. :)

Basically, the most sane way I can see this is by splitting the application code into two different paths that'd be instantiated at the menu. One for Single player, the other for multiplayer.

I'd split in server and client, where the single player combines both parts, perhaps even skipping constructing packets.

The downside is that you have to program for packets, which you'll have to do anyways in multiplayer.

Creating and receiving packets is just work, but not significant in game experience.
The big thing that changes in my view is that the server becomes the deciding point, and there is a network between it and the client, which introduces lag, and network failures. There is no such thing as simply deciding which player wins in a fight, grabs a resource, and so on. You have to wait for both players to send intentions to the server, the server decides, and the result is sent back.
Also be aware that what happens internally is typically invisible to players.

Some games have an internal client and internal server, and the only major difference between online and offline play being the physical location of the server.

In a few games I've worked on we needed to implement host migration, where a new host is chosen when the old host disconnects. In this case all game clients also run their own game server, with one of them (the best connected) being considered authoritative.

Either way the key is creating a good interface between the two. Find the important stuff that needs to be transmitted, the less important stuff that doesn't need to be communicated, and the things where occasional updates are useful but not strictly needed. Then it matters less where the server is, local or remote.

Basically, the most sane way I can see this is by splitting the application code into two different paths that'd be instantiated at the menu. One for Single player, the other for multiplayer.

I'd split in server and client, where the single player combines both parts, perhaps even skipping constructing packets.

How so exactly? Pipes are a bit.... shitty to deal with.

Basically, the most sane way I can see this is by splitting the application code into two different paths that'd be instantiated at the menu. One for Single player, the other for multiplayer.

I'd split in server and client, where the single player combines both parts, perhaps even skipping constructing packets.

How so exactly? Pipes are a bit.... shitty to deal with.

You're still thinking of transmitting data from one to the other.
But presumably, you will wrap packet creation with some sort of function call, right? Something like WritePlayerMovement, WritePlayerFire, WritePlayerUseItem; or at least something similar. And on the other end; ParsePacket() will read packet headers and then pass the packet to functions like ParsePlayerMovement, ParsePlayerFire, ParsePlayerUseItem, etc.
Write two classes with these member functions: the multiplayer version constructs a packet, the single player version just copies data into a buffer for use by the "single player server" or "single player client", and then pushes the address of the buffer into some sort of thread-safe queue of buffers (if you want to make them run in separate processes, you could divide some shared memory into slices and use it as a queue implemented over a ring buffer via atomics or named mutexes; I don't think that degree of separation is strictly necessary, just don't ever pass pointers or references between the "client" and "server" and don't include any type definitions between the two, and you should have a suitable degree of separation. This is essentially just enforcing a message passing paradigm.).

the single player version just copies data into a buffer for use by the "single player server" or "single player client", and then pushes the address of the buffer into some sort of thread-safe queue of buffers (if you want to make them run in separate processes, you could divide some shared memory into slices and use it as a queue implemented over a ring buffer via atomics or named mutexes; I don't think that degree of separation is strictly necessary, just don't ever pass pointers or references between the "client" and "server" and don't include any type definitions between the two, and you should have a suitable degree of separation. This is essentially just enforcing a message passing paradigm.).

I was thinking even more straightforward, as in, the receiver has an function that implements the thing it should do, and the sender has a "call_function" implementation that in network context serializes packets, and in single player context just forwards the call directly to the receiver.

In multi-threading context, I agree it's better to make a copy of the data in some memory, and pass that to the other side.

This topic is closed to new replies.

Advertisement