Preventing code duplication

Started by
6 comments, last by wodinoneeye 9 years, 11 months ago

I'm working on a multiplayer game using C++. I've also worked on multiplayer games in the past, but one of the thinks I like to prevent is code duplication... But it seems that I still haven't found a good approach for that and I couldn't find much about it on the internet. As the server I'm writing is fully-authorative but the clients still need to interpolate, they both need most of the data. Except for the drawing obviously; the server should not have vertex data or textures in memory, and even better would be not to even have a draw method at all on the server.

One of the possibilities I could think of was just creating similar but different classes on the server and client. There is however much code duplication involved here. The other possibility I could think of was using the same classes but using macro's to disable the drawing-related code on the server. This may even be a worse approach because it's hacky, ugly, hard to maintain and a lot of other things.

I'm interested in how more experienced people would tackle this problem, or is it just an ugly part of network programming?

Advertisement
The short (i.e. rude) answer is to keep practicing OO ideas, such as SOLID, as this would mean the logic and drawing code would be more easily separated ;-)

How is your drawing and update logic currently handled? How do they communicate? Do you have something like the classic -
class Entity { void Update(); void Draw(); }; ?

Are there any cases where you need some of the graphics data on the server, such as animated skeletons with bounding/hit boxes or attahment points?

The short (i.e. rude) answer is to keep practicing OO ideas, such as SOLID, as this would mean the logic and drawing code would be more easily separated ;-)

How is your drawing and update logic currently handled? How do they communicate? Do you have something like the classic -
class Entity { void Update(); void Draw(); }; ?

I'm currently doing something like that, mainly because I prefer keeping it simple and because the players are the only dynamic entities for now. Do you have any concrete ideas about separating the update and drawing code here? I thought of ECS but isn't that a bit overkill or is that the way to go in this scenario? Does anyone know another preferbly OO way of dealing with this problem?

Are there any cases where you need some of the graphics data on the server, such as animated skeletons with bounding/hit boxes or attahment points?

No, I don't need any of the graphics data like that on the server.

Actually, OO in itself is not always the right solution. You could just as well express the same functionality as free functions working on typed data structures, and if you do that, the linker is better at stripping out the parts you don't use.

If you have to use object oriented classes, then you can decompose your objects into multiple smaller classes, and use aggregation to include the "bits and pieces" that you need on server versus client. Thus, you'd have separate "entity factories" on client and server, where the client-side factory created the player object with user-derived input, and created all objects with rendering components, and created all non-player objects with network-derived input. On the server, all objects are created with network-received input, no rendering, and authoritative data output over the network.

In general, if you stick to OO, an object/component structure will let you re-use the parts that make sense between client and server, while attaching other bits and pieces that only make sense on one or the other. As long as you keep a strict interface (pure abstract base class) between the objects/components and their dependencies, this will work out well.

I'd highly recommend the libraries-with-data-structures approach, though. This lets you still re-use code, by calling the appropriate functions on the appropriate data in the appropriate context.
enum Bool { True, False, FileNotFound };

You could just as well express the same functionality as free functions working on typed data structures, and if you do that, the linker is better at stripping out the parts you don't use.

By the time it gets to the linker, it's exactly the same either way (assuming the equivalent code is written in both styles)...

Yeah there's some counter-examples, such as if you use virtual functions, they'll not be discarded... but this is a straw-man, because the same would occur with free functions if you wrote the equivalent code that manually created a table of pointers to functions (i.e. the function's who's addresses you capture will also no longer be discarded).

I thought of ECS but isn't that a bit overkill or is that the way to go in this scenario? Does anyone know another preferbly OO way of dealing with this problem?

The core of ECS is "composition" -- the core of OO is also "composition" (if you look up "inheritance vs composition" you'll find that OO teaches that you should default to using composition, and only use inheritance where necessary, which is not often). Under both those paradigms, you break your code up into small pieces which each only solve a single problem at a time, and then build complex parts by composing simple parts together.

Many ECS articles compare themselves against the "incorrect" OO styles, usually "deep inheritance" entity hierarchies, where inheritance has been completely over-used, and then offer ECS as a solution to that problem. I've never seen an ECS article that compares ECS against proper composition-based OO though laugh.png

Do you have any concrete ideas about separating the update and drawing code here?

This is pretty vague, but move all the drawing code out into classes that only do drawing stuff. Don't have game logic and graphics structures intertwined.
You can actually have two completely different worlds/scenes/collections of objects -- one list of game objects, and another list of graphics objects. The update part of your game loop can do stuff to the first list, and the draw part of your game loop can do stuff to the second list. The server can just not create or use the second list. On the client, the game objects obviously need to do stuff to the graphics objects (such as move them around, etc), so the items in the first list can contain pointers into their 'partners' in the second list, but on the server these pointers can just be NULL.

Thank you both for your answers! I think I will go with the ECS because of how dynamic it is, but I am aware that it's actually some kind of dynamic composition.

the same would occur with free functions if you wrote the equivalent code that manually created a table of pointers to functions


The point is that, in most languages, the resistance to creating arrays-of-function-pointers is high, but the resistance to creating a virtual member function is low, so the different styles tend to lead to different outcomes in practice. I kind-of like the idea of Haskell here: If you use something polymorphically, the compiler will figure it out and make it that way for you. If you don't, it won't :-)

I've never seen an ECS article that compares ECS against proper composition-based OO though


Another way to slice that: An ECS is one way of properly decomposing an OO design.

Something I've found with components is that each kind of component has its own little "world" or "scene graph." The physics components end up going into the physics system; the graphics components end up going into the graphics system; the networking components end up going in the networking system; etc. This means that you'll likely have multiple spatial indices in your game, because the kind of kd-tree/octree/cell-portal-graph/quadtree/boundary-metric-tree/hash-grid used for one of those use cases is often not well tuned for the other use cases.

In the end, an "entity" just becomes a loose bundle of separate "components" that each belong to a separate subsystem, and only some very basic information is promoted to the entity level (object id, world position, set of components.)
enum Bool { True, False, FileNotFound };

You have to estimate how much the code really overlaps, how intertwined it would be with non-overlapping code.

Add to that the likelyhood of it (code) having to be later modified.

I found that for the type of game operations I worked on the overlap often was relatively minor/simple (even the collision stuff) and could be procedurized into a common library file (#include files), or was just inlined and commented to make it obvious it had a similar functional block on the other program (client vs frontend server vs AI node) . Trying to do tricks with mutating code in the same file (via #ifdef macros) just obscures/complicates things

--------------------------------------------[size="1"]Ratings are Opinion, not Fact

This topic is closed to new replies.

Advertisement