6
\$\begingroup\$

I am building a software system (game engine with networking support ) that is made up of (roughly) these layers:

  • Game Layer
  • Messaging Layer
  • Networking Layer

Game related data is passed to the messaging layer (this could be anything that is game specific), where they are to be converted to network specific messages (which are then serialized to byte arrays).

I'm looking for a way to be able to convert "game" data into "network" data, such that no strong coupling between these layers will exist.

As it looks now, the Messaging layer sits between both layers (game and network) and "knows" both of them (it contains Converter objects that know how to translate between data objects of both layers back and forth).

I am not sure this is the best solution.

Is there a good design for passing objects between layers? I'd like to learn more about the different options.

\$\endgroup\$
4
  • \$\begingroup\$ This sounds like a question with a lot of potential answers. I think there is no one best way, but one way would be to implement an interface for the serialization in game objects where the networking layer can expect to get an interface on the objects that will work. Something like "GetBytes()". How you implement this is really up to you. I don't know which language you're using but say you're using C#, you could use attributes to define the fields that will be serialized and automatize the process to some extent. However, in this approach you should pay attention to how much overhead making \$\endgroup\$
    – Muhwu
    Aug 7, 2013 at 16:45
  • \$\begingroup\$ This is more of a general programming question than anything games related. Read up on "object serialization" and how regular programs in your language of choice handle networking objects around. Bonus points if you run across JSON/BSON and dig deeper into those since they are very common. \$\endgroup\$ Aug 7, 2013 at 17:28
  • 3
    \$\begingroup\$ Why do you think you need complete decoupling? Games, especially game logic, often requires you to give up on a lot of academic purity. Focus on making your game itself, and writing foundation code that is easy to modify, maintain, and improve, not on adding a bazillion layers of abstraction for no end-gain to the actual game itself. Not saying you want zero abstractions, or even that what you're asking is wrong, but just that you should have a strong why case besides "coupling is bad," which you have not presented. \$\endgroup\$ Aug 8, 2013 at 17:16
  • \$\begingroup\$ Sorry Sean, but you've contradicted yourself here. Decoupling's entire purpose is creating code that is easy to maintain. \$\endgroup\$
    – Engineer
    Feb 2, 2014 at 21:23

4 Answers 4

2
\$\begingroup\$

Inversion of Control can prevent changes to the Game Layer or Network Layer when something changes in one of the other layers. First let's consider a Message class.

Ditch your existing messaging layer for now, and instead lock down your generalised Message format / class inasmuch as possible. Messages are not only going to contain data members; they will also carry functionality. In this way, we prevent any changes to the message format from affecting Game or Network layer classes, because it is the messages that take action, based on their own data. This is Inversion of Control. What messages are, and what they do, is bound into one class (per message type).

Polymorphism can help us further. I know it's trite, but building to interfaces is your friend. You might have different subtypes of Message. Any external class, then, may receive a generalised Message, and not know what it's most derived type actually is; so the external class will not know how to process it... nor need it do so: For if the Message itself is equipped to act based on it's type (as per Strategy Pattern / functors), it could modify your data model directly once it reaches the Game Layer, as follows:

class SomeGameLogicClass implements IMessageQueueKeeper
{
   List<Message> messages;
   {
      for each (message in messages)
         message.process();
         //Irrespective of whether a KillMessage or a SpawnMessage,
         //the Message has now written to the game's data model --
         //which your GameLogic classes may interface with directly.
         //(Assume all Messages are given access to the data model.)
   }
}

In fact, your Messages could simply tie into your pre-existing messaging layer classes for their functionality; client classes (Network, Game) would be none the wiser.

The generalised type Message enables all of this.

Furthermore, how do we populate the message queue? Well, what we don't want is for the Network classes to have type-specific references to the Game classes. What we can instead do is allow them to maintain generalised references to one another, via the example interface IMessageQueueKeeper. This means you could take the Network layer and plug it into a completely different game, provided that interface were implemented by that other game. Now you can let your Network Layer reference the above class, so that it may feed it Messages. You can do similar, in reverse, allowing your Game layer to notify some vague and generalised Network class of new messages to be boiled down and sent onto the wire.

Both Game and Network layer are producers and consumers of Messages (of various derived types). Let the interfaces that you build for them, reflect that.

And voilà! No more tight coupling. :)

\$\endgroup\$
1
\$\begingroup\$

I have done two things in the past, the first using converters like you mentioned (AutoMapper), the second is to have another layer which both projects have a reference to. The second layer would contain the contract between the the objects you want map. For example:

DomainApi Project:

namespace Project.DomainApi
{
    public interface IMonsterData
    {
        string Name { get; }

        int Life { get; }
    }
}

Domain (Game) Project:

namespace Project.Domain
{
    public class Monster : IMonsterData
    {
        public string Name { get; protected set;}

        public int Life { get; protected set;}
    }
}

Message Project:

namespace Project.Messages
{
    public class NewMonsterMessage : IMonsterData
    {
        public string Name { get; set;}

        public int Life { get; set;}
    }
}

Your Networking project would then send/receive IMonsterData types. No one outside of Domain (Game) would have access to the Monster class.

\$\endgroup\$
0
\$\begingroup\$

One way to implement this is to go the route of the data driven design/data driven game engine.

In a data driven engine you don't have hard coded classes which represent your states of the objects, you basically do have only a list/hashtable with names of the variables, and types and the data for the variable itself, maybe with metainformation too (for serilization).

A good looking document after some minutes of googling for an introduction is this.

A implementation of the network serilisation/deserilisation with this approach seems easy.

\$\endgroup\$
0
\$\begingroup\$

Typically we just allow some level of coupling. Academic purity is really, really far down on the list priorities worth caring about out in the real world, especially in cases where none of the theoretical down-sides are actually coming up in practice (what exactly would said coupling prevent or make harder to maintain than the contortions it imposes do?).

It's pretty easy to just add a WriteNetworkUpdate method that writes out all the data you need to replicate and a corresponding read method. This isn't much different than your saving/loading code (it might even be the same set of methods for simpler games).

A more advanced way to handle things is to use reflection (especially easy in C#). You can write some network-specific reflection attributes to attach to class fields that the network layer uses to generate and decode update or event packets. We do this in C++ via a custom reflection system and most other big engines I'm familiar with do something similar.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .