INCLUDE_DATA

Applied design patterns: C4 Engine Messages

July 15, 2008 – 5:00 pm

In my previous article about the C4 engine, I highlighted what was, in my opinion, the biggest downside of the C4 engine: its demo game. Or more precisely, the code and design of the demo game. Because I had to get points for university and didn’t have time to dive into said code and refactor it all, I wrote a refactoring proposal (found here: Office ‘97-’03 / Office ‘07) and put it on the C4 Engine forums. After that, and after the project, I still had to get some points, so I executed some of the refactorings.

So far, most refactorings I’ve done involve moving functionality from one central Game class to different smaller classes - resource and game management so far. One refactoring however is one I’m pretty proud of (in all decency), and is described in the refactoring proposal as the Composite Message System.

In this article, I’ll (attemt to) explain the process of this particular refactoring: explain the old situation, what the problem was with that situation, how it was analyzed and how, eventually (and with the help of my trusty Design Patterns book) I came up with a replacement system of that situation, one that was a lot more flexible and reusable, and one that would accellerate the speed at which programmers could produce results.

For those that are interested, I’ll be discussing the Composite, Visitor, Factory Method and (to a lesser degree) Builder design patterns in this article.

Ye Olde slash Current Situation

While I’m not officially allowed to post any real code from the C4 engine or demo game, I’m pretty sure there’s no restrictions to discussing said code / design.

In the current situation, you can send messages to other clients through C4’s MessageMgr class, accessible through a global (shudder) variable, TheMessageMgr. The MessageMgr has a SendMessage method, which takes an address (in the form of the key of the recipient, managed by said MessageMgr) and a Message. This Message is a subclass of C4’s Message class, a class that contains some management functions (message ID number and such), but mainly defines the behavior of subclasses.

In a C4-made game, a subclass of Message has to be created for each message type. For example, say you want to send a chat message to a certain player. In C4, you’ll have to create a new class ChatMessage, which extends Message. ChatMessage would contain two variables: Sender, a long value referring to the player who sent the message, and Message, a String (or character array) containing the actual message.

C4’s Message interface defines that any Message should also implement a Compress() and a Decompress() method, two methods that basically pass a Compressor and/or a Decompressor object to the Message object, on which methods can be called so that the Message can add and extract its own data from a byte stream (or whatever, I don’t know / don’t need to know how the data is actually represented inside the Compressor / Decompressor objects). So you’ll have the following methods:

void ChatMessage::Compress(Compressor& data)
{
	data << sender;
	data << message;
}

bool ChatMessage::Decompress(Decompressor& data)
{
	data >> sender;
	data >> message;
}

Basically, the Compressor and Decompressor implement the << and >> operators, which add and extract the data from their internal representation. This actually adheres to the Visitor design pattern already, where a client sends an object to another object, where this other object (a Message subclass in this case) calls methods on the visiting object to, in this case, add data to it.

So, in total, to create a new Message type, you have to create a new class (in a header file) containing the class itself, the fields, two constructors, accessors for the fields, and a Compress and Decompress method. Next, implement the methods defined in the header file in a .cpp file (as well as message sending and receiving functionality, but that’s something for another day.) Sounds straightforward enough, and to be honest, it is.

However.

Say you want to create another message that sends an announcement to all players, say, a server message that appears in the middle of each player’s screen. Simple enough, create a new Message class that contains a string and done. Rinse and repeat the above operations of new files, new class, constructors, compress / decompress, accessors, and implementation. Next, you want to send a notification that playey X has left the game. Again, simple enough, just create a new message type PlayerLeftMessage, which contains just one long value (the player key), constructors, accessors, compress/decompress, implement… wait a minute, didn’t we do that earlier?

The observant reader will notice that in the above example, three full-on message types were created, each about 50 lines in total (no comments, with whitespace), and each containing roughly the same methods and functionality. The only actual difference between the three is the data they contain / send, and an internal key to uniquely identify the message type on reception. Not very much. And yet, you have to create a full class for it.

Let’s break it down a bit more. The types of data that can be sent over a network is limited by what the Compressor and Decompressor classes can handle in their << and >> operators. These types include:

  • Booleans
  • Floating point numbers
  • Chars / Unsigned chars
  • Character arrays (Strings)
  • Shorts / Unsigned shorts
  • Longs / Unsigned longs
  • Long64 / Ulong64 (64 bits integers)

All messages, one way or the other, send these and only these values. More advanced data types, such as for example the Vector3D type, are broken down into components and sent. The Vector3D is broken down into 3 floating point numbers (floats), for example.

What does this mean? It means that, one way or the other, every message is composed of the above data types. Every message type converts the data they have to send to these types. They all do, no exceptions.

Solution

As I came to realize this, I figured there had to be a better, more flexibe way to define new message types, using the components described above. I read the chapter in the Design Patterns book on the Composite design pattern (since all message types were ‘composed’ of the above types), and attempted to work out a solution, as described in the refactoring proposal document.

The solution involved defining a class structure that allows a client to dynamically assemble (’compose’) a message type from both simple and complex ‘modules’, building blocks so to speak. There’s basically two types of building blocks: ‘Leaf’-blocks and ‘Composed’ blocks. Leaf blocks are the lowest point in the imaginary Composite Message tree, and contain one type of data - corresponding to the above list of Compressor-supported data types. It contains just one field, and implements both the Compress and Decompress methods defined by C4’s Message class.

The Composed block is a bit more advanced. It contains an internal list of other blocks (which can be either other Composed objects or leaves), and methods for traversing that list. It also implements the Compress and Decompress method, but instead of calling the Compressor / Decompressor’s << and >> operators, it passes the Compressor / Decompressor objects on to its subnodes’ own Compress / Decompress methods, so they can perform whatever action they need to do on it. If one of the subnodes is a leaf node, it’ll add or extract its own locally stored data to the Compressor / Decompressor object. If it’s another Composed node, it’ll pass the Compressor / Decompressor on to its children.

The figure below illustrates.

Composite Message diagram

In this diagram, SomeMessage and Vector3DMessage are Composed Messages, while StringMessage, LongMessage and FloatMessage are leaf messages. All message nodes can be treated equally - they all, directly or indirectly, inherit from C4’s Message class. They all implement a Compress / Decompress method. They’re also all subclasses of a custom CompositeMessage type, which defines methods for traversing the tree.

To construct a CompositeMessage, which now is a means of defining a new Message type as described earlier, all a programmer has to do now is a handful of code statements:

CompositeMessage * message = new ComposedMessage();
message->AddSubnode(new StringMessage("some string here"));
message->AddSubnode(new LongMessage(10));

TheMessageMgr->SendMessage(message);

Three lines to create a new message type that would otherwise take 50. Quite an improvement, if I say so myself. Of course, there’s more to this than the above, as I realized after I had thought out this design.

You now have a tree of CompositeMessage objects, which are either ComposedMessage or LeafMessage types. Which one? No clue - as it should be. Proper OO programming involves not knowing or needing to know what specific implementation of a class you’re working with. So in the above example, you could see the entire tree as a tree of CompositeMessage objects.

Extracting data

However. How are you supposed to get the data stored in that from it now? This was the problem I had as well, and which I discussed with some people on the internets. From those, I gathered the Visitor design pattern is usually the means of traversing a tree and getting data from it.

Earlier in this article, we briefly passed this Visitor design pattern - the Compress and Decompress methods in the Message subclasses are both Visitor methods. The Compressor and Decompressor objects passed to those are Visitor objects. Such an approach could also be used in the Composite Message type.

In order to extract the data from a Composed message, I thought up a Visitor object that contained a list of sorts for each supported data type, as well as accessors for those. For each data type, there’s an AddXValue() and an GetXValue() method, that in turn adds a value of type X to the Visitor and get a value of type X from the Visitor. GetXValue will return the first X value when it’s called the first time, the second when called the second time, etcetera - according to a first in, first out pattern.

In the CompositeMessage type, a Visit method was added for each subclass to implement, which passes a Visitor object to the object. A Leaf subclass would implement it by calling Visitor->AddXValue(), passing its locally stored value to the Visitor. A Composed subclass would implement it by passing the Visitor to each of its subnodes.

Once the Visitor has traversed all the nodes of the Composite message, it’s filled with data, which can be extracted by the client again using the GetXValue() methods. An example:

CompositeMessage * message = new ComposedMessage();
message->AddSubnode(new StringMessage("some string here"));
message->AddSubnode(new LongMessage(10));

Visitor * visitor = new Visitor();
message->Visit(visitor);
String * someString = message->GetStringValue();
long someLong = message->GetLongValue();

Looks straightforward enough. The only thing to keep in mind is that the data should be extracted in the order the composite message was assembled. This method can also be used to fill the CompositeMessage with data, by defining a FillVisit method for each CompositeMessage subclass. Instead of calling Visitor->SetXValue() however, the GetXValue() method is called and its resulting value is set as the object’s local data.

Speaking of assembling, it’s best if a CompositeMessage type is assembled from only one point in the program, so that the exact same structure is maintained each time the CompositeMessage is composed (or ‘built’). To accomplish this, we’ll use the Factory Method design pattern. For each Composite message type, we define a construction method that takes the initial values of the various components of the Composite Message. Here’s an example:

CompositeMessage * CreateSomeMessage(Vector3D someVector, long someLong, float someFloat)
{
	CompositeMessage * message = new CompositeMessage();
	message->addSubnode(new Vector3DMessage(someVector));
	message->addSubnode(new LongMessage(someLong));
	message->addSubnode(new FloatMessage(someFloat));

	return message;
}

Simple and straightforward enough.

Consequences

This structure has both up- and downsides.

Using this structure makes it a lot easier to quickly create new message types. The process of new files, new class, fields, constructors, accessors, compress/decompress methods and all that is no longer needed, instead a programmer now assembles a message in three lines that earlier took 50, not counting documentation. Chucking it into a Factory Method as defined above adds some lines, but still nowhere near 50. This leads to increased production speed, and allows the programmer to concentrate on his task of solving problems, instead of the tedious business of writing the same class over and over again, with only slight differences.

Dynamic. With this structure, it’s possible to define message types at runtime, based on various factors. As such, the complexity of a message can be adjusted to the requirements. You could create a large, complex composite message for a player’s initial data, then adjust it to a much smaller / simpler one that only contains the data that has been updated.

Compatible with existing Message system. There’s no need whatsoever to change anything in the engine’s network code, since as far as the engine’s concerned, the CompositeMessage is just yet another Message. So this system can easily be used alongside existing messages, so that a transferrence from or to this system can be executed gradually, one message type at at time, without breaking the rest of the application.

There are however also some disadvantages to this system:

For one, it’s not as straightforward anymore. In the old system, all you had to do was call message->GetSender() (or something) to get the sender of a chat message. Now, you first have to create a Visitor object, pass it to the composite message, and call GetLongValue() from the Visitor. It’s a few extra steps, and it’s a lot more ambiguous - who says the long value received from the Visitor is really a player’s key? It’s uncertain, which means that in order to use this system, you have to keep track of the message type, and the order of data.

Also, it’s slower than the existing. The existing method was just a class with some message. Sending a new message involved creating a new instance of this class, and done. Creating a new CompositeMessage instance involves creating an X-amount of new objects, depending on the amount of data to be sent, and calling several methods on said objects before it can be sent. And that’s not all, for when it’s received again, the entire tree has to be traversed twice - once by the Decompressor object to fill it with data, and once again by the Visitor object to extract the data. How much slower this is, I don’t know - if in fact it is measureable - but it requires more instructions than the existing system. I’m guessing the performance decrease is negligible though, considering that at the same time messages are received and such, C4’s engine calculates loads of matrix transforms and whatnot.

Finally, this system can be used alongside the existing message system. Didn’t I already say that before? I did, since this point can be seen as both an advantage and a disadvantage. It’s in this case disadvantageous because the two message systems can become confused with each other - when one message is a class and another is a CompositeMessage, you’ll end up with maintaining two different systems. It’s best to do either one or the other, not both. But I guess that’s also personal.

Code

Anyway, for points I went and implemented the system (or a version thereof) explained in the refactoring proposal document. I’ll post the code below, but first, a warning: I haven’t properly tested this code. I did translate a few messages to the system, but nothing really complex, and nowhere enough to say this code is safe. If you’re planning on using this code, don’t assume it’ll work. I won’t give any guarantees.

Here’s the code. Hope you don’t mind the massive amount of code (relatively), and that Wordpress messed up the indentation.

CompositeMessage.h

#ifndef CompositeMessage_h
#define CompositeMessage_h

#include "C4Messages.h"
#include "C4Engine.h"

namespace C4
{

/*
The Visitor class is a class representing an object that is able to traverse a
Complex Message structure to gather data.
*/
class Visitor
{
private:

// For each data types, two fields have to be defined:
// * The array (or whatever) used to store the data
// * A counter keeping track of the last data returned.

// Long values
Array longValues;
long currentLongValue;

Array unsignedLongValues;
long currentUnsignedLongValue;

// Boolean values
Array boolValues;
long currentBoolValue;

// Float values
Array floatValues;
long currentFloatValue;

// String values
Array stringValues;
long currentStringValue;

// Char values
Array charValues;
long currentCharValue;

Array unsignedCharValues;
long currentUnsignedCharValue;

// Short values
Array shortValues;
long currentShortValue;

Array unsignedShortValues;
long currentUnsignedShortValue;

// long64 values
Array long64Values;
long currentLong64Value;

Array
unsignedLong64Values;
long currentUnsignedLong64Value;

public:
Visitor();
~Visitor();

// Long values
void AddLongValue(long value);
long GetLongValue(void);

void AddUnsignedLongValue(unsigned long value);
unsigned long GetUnsignedLongValue(void);

// Bool values
void AddBoolValue(bool value);
bool GetBoolValue(void);

// Float values
void AddFloatValue(float value);
float GetFloatValue(void);

// String values
void AddStringValue(const char * value);
const char * GetStringValue(void);

// Char values
void AddCharValue(char value);
char GetCharValue(void);

void AddUnsignedCharValue(unsigned char value);
unsigned char GetUnsignedCharValue(void);

// Short values
void AddShortValue(short value);
short GetShortValue(void);

void AddUnsignedShortValue(unsigned short value);
unsigned short GetUnsignedShortValue(void);

// Long64 values
void AddLong64Value(long64 value);
long64 GetLong64Value(void);

void AddUnsignedLong64Value(ulong64 value);
ulong64 GetUnsignedLong64Value(void);
};

/*
The CompositeMessage class acts as the interface to all CompositeMessage subclasses,
and defines the basic behavior of all subclasses. The main two subclasses to this class
are LeafMessage and ComposedMessage - see those for more information.

*/
class CompositeMessage : public Message
{
public:
// Constructor, used to set an identifier for the Message.
// Passes type to the Message constructor.
CompositeMessage(MessageType type);
~CompositeMessage(void);

// The Compress method is called right before a Message is sent.
virtual void Compress(Compressor& data) const;

// The Decompress method is called after the type of a Message has
// been determined.
virtual bool Decompress(Decompressor& data);

// The Add method adds a subnode to a CompositeMessage.
virtual void Add(CompositeMessage * subnode);

// The Visit method should, when implemented by a leaf class,
// pass its data to the Visitor object. When implemented by a Composed
// class, it should pass the Visitor to its subnodes and, when appropriate,
// extract the data from the visitor, assemble its complex object, and
// pass that to the Visitor again.
virtual void Visit(Visitor * visitor) const;

// The FillVisit method works the same as the Visit method, except should
// be used to extract data from the Visitor and assign that data to the local
// fields. Once again, Composed message types should pass the Visitor on to
// subclasses.
virtual void FillVisit(Visitor * visitor);

// The GetData method is implemented by the CompositeMessage itself,
// and will create a Visitor object, send it through its own structure
// (regardless of whether it's a tree or just a single node), and return
// the filled Visitor to the calling client.
virtual Visitor * GetData(void) const;
};

/*
The LeafMessage class is the parent class to all Composite Message nodes
that do not have any subnodes of themselves. Therefore, the Add method
will remain unimplemented (i.e. will do nothing) for Leaf nodes.

Subclasses usually contain one basic type value, so you get
BoolMessage, LongMessage etc subclasses to this class.

Note that LeafMessage is an abstract class, and does not implement
any functions itself.
*/
class LeafMessage : public CompositeMessage
{
public:
LeafMessage(MessageType type);
~LeafMessage(void);

// Compresses the local data into a Compressor.
virtual void Compress(Compressor& data) const;

// Extracts data from the Decompressor and stores it in a local value
virtual bool Decompress(Decompressor& data);

// Stores locally stored data into the given Visitor object.
virtual void Visit(Visitor * visitor) const;

// Extracts data from the Visitor object and stores it locally.
virtual void FillVisit(Visitor * visitor);
};

/*
The ComposedMessage class is a class that can be used
to compose more comples messages. It implements the Add,
Compress, Decompress, Visit and FillVisit methods,
each of which will traverse the array of subnodes
also defined in this class.

Subclasses that implement these four classes should all,
at one point, call this class's implementations. This can
be done either before or after doing their own thing.
*/
class ComposedMessage : public CompositeMessage
{
private:
Array subnodes;
public:
ComposedMessage(MessageType type);
~ComposedMessage(void);

// Adds the passed CompositeMessage, which can either be a leaf or another ComposedMessage,
// to the list of subnodes.
void Add(CompositeMessage * subnode);

// Will iterate through all subnodes and pass the Compressor object to each
// subnode's Compress method.
void Compress(Compressor& data) const;

// Will iterate through all subnodes and pass the Decompressor object to each
// subnode's Deompress method.
bool Decompress(Decompressor& data);

// Will iterate through all subnodes and pass the Visitor object to each
// subnode's Visit method.
void Visit(Visitor * visitor) const;

// Will iterate through all subnodes and pass the Visitor object to each
// subnode's FillVisit method.
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a boolean value.
*/
class BoolMessage : public LeafMessage
{
private:
bool value;
public:
BoolMessage(MessageType type, bool val = false);
BoolMessage(bool val = false);
~BoolMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor* visitor);
};

/*
Leaf Message type used to store a long value.
*/
class LongMessage : public LeafMessage
{
private:
long value;
public:
LongMessage(long val = 0L);
LongMessage(MessageType type, long val = 0L);
~LongMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store an unsigned long value.
*/
class UnsignedLongMessage : public LeafMessage
{
private:
unsigned long value;
public:
UnsignedLongMessage(unsigned long val = 0L);
UnsignedLongMessage(MessageType type, unsigned long val);
~UnsignedLongMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a long64 value. (see C4Types.h for long64 info)
*/
class Long64Message : public LeafMessage
{
private:
long64 value;
public:
Long64Message(long64 val = 0L);
Long64Message(MessageType type, long64 val = 0L);
~Long64Message(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store an ulong64 (unsigned long64) value. (see C4Types.h for ulong64 info)
*/
class UnsignedLong64Message : public LeafMessage
{
private:
ulong64 value;
public:
UnsignedLong64Message(ulong64 val = 0L);
UnsignedLong64Message(MessageType type, ulong64 val = 0L);
~UnsignedLong64Message(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a char value.
*/
class CharMessage : public LeafMessage
{
private:
char value;
public:
CharMessage(char val = ' ');
CharMessage(MessageType type, char val);
~CharMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store an unsigned char value.
*/
class UnsignedCharMessage : public LeafMessage
{
private:
unsigned char value;
public:
UnsignedCharMessage(unsigned char val = ' ');
UnsignedCharMessage(MessageType type, unsigned char val = ' ');
~UnsignedCharMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a string (or char pointer / array) value.
See also C4String.h.
*/
class StringMessage : public LeafMessage
{
private:
String value;
public:
StringMessage(const char * val = "");
StringMessage(MessageType type, const char * val = "");
~StringMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a short value.
*/
class ShortMessage : public LeafMessage
{
private:
short value;
public:
ShortMessage(short val = 0);
ShortMessage(MessageType type, short val = 0);
~ShortMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store an unsigned short value.
*/
class UnsignedShortMessage : public LeafMessage
{
private:
unsigned short value;
public:
UnsignedShortMessage(unsigned short val = 0);
UnsignedShortMessage(MessageType type, unsigned short val = 0);
~UnsignedShortMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
Leaf Message type used to store a float value.
*/
class FloatMessage : public LeafMessage
{
private:
float value;
public:
FloatMessage(float val = 0.0F);
FloatMessage(MessageType type, float val = 0.0F);
~FloatMessage(void);
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
void Visit(Visitor * visitor) const;
void FillVisit(Visitor * visitor);
};

/*
The MessageTypeManager class is a supportive class for the Composite Message system,
and defines / can define a list of factory methods that assemble a Composite Message.

Optionally, parameters can be passed to the factory method of each message type,
which contain the data to be assigned to the nodes of the composed message.
However, keep in mind that empty composed messages should also be created,
for when a Message is received but not decompressed yet.

I've only put a few examples in this class, cba to do all current message types.
There's a description of each message type in their implementations, see the .cpp file
for information and usage etc.

In the examples put into those comments, pay particular attention to the lack of casts
to a specific message type. All messages, when received, are cast into a CompositeMessage
type - none of the underlying classes are ever directly used by a client.

In a system where all Messages have been replaced by CompositeMessages, a ReceiveMessage
method would only have to do a single cast at the top of the method, from C4's Message
to this CompositeMessage type. A massive reduction in casts is a result, and a result from
that is more reliable and type-safe code.
*/
class MessageTypeManager
{
public:
static CompositeMessage * GetServerInfoMessage(long playerCount = 0L, long maxPlayerCount = 0L, String gameName = "", ResourceName worldName = "");
static CompositeMessage * GetGameInfoMessage(unsigned long flags = 0L, ResourceName world = "");
static CompositeMessage * GetUpdateScoreMessage(long playerScore = 0L);
static CompositeMessage * GetUpdateHealthMessage(long playerHealth = 0L);
static CompositeMessage * GetClientOrientationMessage(float azimuth = 0.0F, float altitude = 0.0F);
};

}

#endif

CompositeMessage.cpp
#include "CompositeMessage.h"

#include "MGMultiplayer.h" // message types

using namespace C4;

Visitor::Visitor()
{
// set all 'current X value' indicator to 0.
currentLongValue = 0L;
currentUnsignedLongValue = 0L;
currentBoolValue = 0L;
currentFloatValue = 0L;
currentStringValue = 0L;
currentCharValue = 0L;
currentUnsignedCharValue = 0L;
currentShortValue = 0L;
currentUnsignedShortValue = 0L;
currentLong64Value = 0L;
currentUnsignedLong64Value = 0L;
}

Visitor::~Visitor()
{
}

// Adds a long value to the array.
void Visitor::AddLongValue(long value)
{
longValues.AddElement(value);
}

// Gets a long value from the array, or 0 if the array will exceed its bounds.
long Visitor::GetLongValue(void)
{
if (currentLongValue >= longValues.GetElementCount())
return 0;
else
return (longValues[currentLongValue++]);
}

// Adds an unsigned long value to the array.
void Visitor::AddUnsignedLongValue(unsigned long value){
unsignedLongValues.AddElement(value);
}

// Gets an unsigned long value from the array, or 0 if the array will exceed its bounds.
unsigned long Visitor::GetUnsignedLongValue(void){
if (currentUnsignedLongValue >= unsignedLongValues.GetElementCount())
return 0;
else
return (unsignedLongValues[currentUnsignedLongValue++]);
}

void Visitor::AddBoolValue(bool value){
boolValues.AddElement(value);
}

bool Visitor::GetBoolValue(void){
if (currentBoolValue >= boolValues.GetElementCount())
return false;
else
return (boolValues[currentBoolValue++]);
}

void Visitor::AddFloatValue(float value){
floatValues.AddElement(value);
}

float Visitor::GetFloatValue(void){
if (currentFloatValue >= floatValues.GetElementCount())
return false;
else
return (floatValues[currentFloatValue++]);
}

void Visitor::AddStringValue(const char * value){
stringValues.AddElement(value);
}

const char * Visitor::GetStringValue(void){
if (currentStringValue >= stringValues.GetElementCount())
return "";
else
return (stringValues[currentStringValue++]);
}

void Visitor::AddCharValue(char value){
charValues.AddElement(value);
}

char Visitor::GetCharValue(void){
if (currentCharValue >= charValues.GetElementCount())
return ' ';
else
return (charValues[currentCharValue++]);
}

void Visitor::AddUnsignedCharValue(unsigned char value){
unsignedCharValues.AddElement(value);
}

unsigned char Visitor::GetUnsignedCharValue(void){
if (currentUnsignedCharValue >= charValues.GetElementCount())
return ' ';
else
return (unsignedCharValues[currentUnsignedCharValue++]);
}

void Visitor::AddShortValue(short value){
shortValues.AddElement(value);
}

short Visitor::GetShortValue(void){
if (currentShortValue >= shortValues.GetElementCount())
return 0;
else
return (shortValues[currentShortValue++]);
}

void Visitor::AddUnsignedShortValue(unsigned short value){
unsignedShortValues.AddElement(value);
}

unsigned short Visitor::GetUnsignedShortValue(void){
if (currentUnsignedShortValue >= unsignedShortValues.GetElementCount())
return 0;
else
return (unsignedShortValues[currentUnsignedShortValue++]);
}

void Visitor::AddLong64Value(long64 value){
long64Values.AddElement(value);
}

long64 Visitor::GetLong64Value(void)
{
if (currentLong64Value >= long64Values.GetElementCount())
return 0;
else
return (long64Values[currentLong64Value++]);
}

void Visitor::AddUnsignedLong64Value(ulong64 value){
unsignedLong64Values.AddElement(value);
}

ulong64 Visitor::GetUnsignedLong64Value(void){
if (currentUnsignedLong64Value >= unsignedLong64Values.GetElementCount())
return 0;
else
return (unsignedLong64Values[currentUnsignedLong64Value++]);
}

// Compsite Message implementation.

// Constructor calls the parent constructor, passing the type parameter.
CompositeMessage::CompositeMessage(MessageType type) : Message(type){}

CompositeMessage::~CompositeMessage(void){}

void CompositeMessage::Compress(Compressor &data) const{}

bool CompositeMessage::Decompress(Decompressor &data) {return true;}

void CompositeMessage::Add(CompositeMessage * subnode) {}

void CompositeMessage::Visit(C4::Visitor * visitor) const {}

void CompositeMessage::FillVisit(C4::Visitor * visitor){}

/*
Creates a Visitor object and passes it to the Visit method, which
is implemented differently depending on what subclass of CompositeMessage
is currently used. When passed, the Visitor is returned.
*/
Visitor * CompositeMessage::GetData(void) const {
Visitor * visitor = new Visitor();
Visit(visitor);
return visitor;
}

// LeafMessage constructor, calls the parent CompositeMessage constructor
// passing the type parameter to it.
LeafMessage::LeafMessage(MessageType type) : CompositeMessage(type){}

LeafMessage::~LeafMessage(void){}

void LeafMessage::Compress(Compressor& data) const {}

bool LeafMessage::Decompress(Decompressor& data) {return true;}

void LeafMessage::Visit(Visitor * visitor) const {}

void LeafMessage::FillVisit(Visitor * visitor) {}

/*
ComposedMessage implementation.
*/
ComposedMessage::ComposedMessage(MessageType type) : CompositeMessage(type) {}

// deletes all the subnodes (if any) recursively.
ComposedMessage::~ComposedMessage(void)
{
long count = subnodes.GetElementCount();
for (natural a = 0; a < count; a++)
{
if (subnodes[a]) delete subnodes[a];
}
}

// Adds the given element to the subnode array.
void ComposedMessage::Add(CompositeMessage * subnode)
{
subnodes.AddElement(subnode);
}

// Passes the Compressor to all subnodes' Compress methods.
void ComposedMessage::Compress(Compressor& data) const
{
long count = subnodes.GetElementCount();
for (natural a = 0; a < count; a++)
{
if (subnodes[a]) subnodes[a]->Compress(data);
}
}

// Passes the Decompressor to all subnodes' Decompress methods.
bool ComposedMessage::Decompress(Decompressor &data)
{
long count = subnodes.GetElementCount();
for (natural a = 0; a < count; a++)
{
if (subnodes[a])
{
if (!(subnodes[a]->Decompress(data)))
return false;
}
}
return true;
}

// Passes the Visitor object to all subnodes' Visit methods.
void ComposedMessage::Visit(Visitor * visitor) const
{
long count = subnodes.GetElementCount();
for (natural a = 0; a < count; a++)
{
if (subnodes[a])
subnodes[a]->Visit(visitor);
}
}

// Passes the Visitor object to all subnodes' FillVisit methods.
void ComposedMessage::FillVisit(Visitor *visitor)
{
long count = subnodes.GetElementCount();
for (natural a = 0; a < count; a++)
{
if (subnodes[a])
subnodes[a]->FillVisit(visitor);
}
}

/*
BoolMessage implementation
*/
BoolMessage::BoolMessage(bool val) : LeafMessage(kMessageBool)
{
value = val;
}

BoolMessage::BoolMessage(MessageType type, bool val) : LeafMessage(type)
{
value = val;
}

BoolMessage::~BoolMessage(void) {}

void BoolMessage::Compress(Compressor& data) const
{
data << value;
}

bool BoolMessage::Decompress(Decompressor& data)
{
data >> value;
return (true);
}

void BoolMessage::Visit(Visitor * visitor) const
{
visitor->AddBoolValue(value);
}

void BoolMessage::FillVisit(Visitor *visitor)
{
value = visitor->GetBoolValue();
}

/*
LongMessage implementation
*/
LongMessage::LongMessage(long val) : LeafMessage(kMessageLong)
{
value = val;
}

LongMessage::LongMessage(MessageType type, long val) : LeafMessage(type)
{
value = val;
}

LongMessage::~LongMessage(void) {}

void LongMessage::Compress(Compressor &data) const
{
data << value;
}

bool LongMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void LongMessage::Visit(Visitor * visitor) const
{
visitor->AddLongValue(value);
}

void LongMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetLongValue();
}

/*
UnsignedLongMessage implementation
*/
UnsignedLongMessage::UnsignedLongMessage(unsigned long val) : LeafMessage(kMessageUnsignedLong)
{
value = val;
}

UnsignedLongMessage::UnsignedLongMessage(unsigned long val, MessageType type) : LeafMessage(type)
{
value = val;
}

UnsignedLongMessage::~UnsignedLongMessage(void) {}

void UnsignedLongMessage::Compress(Compressor &data) const
{
data << value;
}

bool UnsignedLongMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void UnsignedLongMessage::Visit(Visitor * visitor) const
{
visitor->AddUnsignedLongValue(value);
}

void UnsignedLongMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetUnsignedLongValue();
}

/*
Long64Message implementation
*/
Long64Message::Long64Message(long64 val) : LeafMessage(kMessageLong64)
{
value = val;
}

Long64Message::Long64Message(MessageType type, long64 val) : LeafMessage(type)
{
value = val;
}

Long64Message::~Long64Message(void) {}

void Long64Message::Compress(Compressor &data) const
{
data << value;
}

bool Long64Message::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void Long64Message::Visit(Visitor * visitor) const
{
visitor->AddLong64Value(value);
}

void Long64Message::FillVisit(Visitor * visitor)
{
value = visitor->GetLong64Value();
}

/*
UnsignedLong64Message implementation
*/
UnsignedLong64Message::UnsignedLong64Message(ulong64 val) : LeafMessage(kMessageUnsignedLong64)
{
value = val;
}

UnsignedLong64Message::UnsignedLong64Message(MessageType type, ulong64 val) : LeafMessage(type)
{
value = val;
}

UnsignedLong64Message::~UnsignedLong64Message(void) {}

void UnsignedLong64Message::Compress(Compressor &data) const
{
data << value;
}

bool UnsignedLong64Message::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void UnsignedLong64Message::Visit(Visitor * visitor) const
{
visitor->AddUnsignedLong64Value(value);
}

void UnsignedLong64Message::FillVisit(Visitor * visitor)
{
value = visitor->GetUnsignedLong64Value();
}

/*
CharMessage implementation
*/

CharMessage::CharMessage(char val) : LeafMessage(kMessageChar)
{
value = val;
}

CharMessage::CharMessage(MessageType type, char val) : LeafMessage(type)
{
value = val;
}

CharMessage::~CharMessage(void) {}

void CharMessage::Compress(Compressor &data) const
{
data << value;
}

bool CharMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void CharMessage::Visit(Visitor * visitor) const
{
visitor->AddCharValue(value);
}

void CharMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetCharValue();
}

/*
UnsignedCharMessage implementation
*/
UnsignedCharMessage::UnsignedCharMessage(unsigned char val) : LeafMessage(kMessageUnsignedChar)
{
value = val;
}

UnsignedCharMessage::UnsignedCharMessage(MessageType type, unsigned char val) : LeafMessage(type)
{
value = val;
}

UnsignedCharMessage::~UnsignedCharMessage(void) {}

void UnsignedCharMessage::Compress(Compressor &data) const
{
data << value;
}

bool UnsignedCharMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void UnsignedCharMessage::Visit(Visitor * visitor) const
{
visitor->AddUnsignedCharValue(value);
}

void UnsignedCharMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetUnsignedCharValue();
}

/*
StringMessage implementation
*/
StringMessage::StringMessage(const char *val) : LeafMessage(kMessageString)
{
value = val;
}

StringMessage::StringMessage(MessageType type, const char *val) : LeafMessage(type)
{
value = val;
}

StringMessage::~StringMessage(void) {}

void StringMessage::Compress(Compressor &data) const
{
data << value;
}

bool StringMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void StringMessage::Visit(Visitor * visitor) const
{
visitor->AddStringValue(value);
}

void StringMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetStringValue();
}

/*
ShortMessage implementation
*/
ShortMessage::ShortMessage(short val) : LeafMessage(kMessageShort)
{
value = val;
}

ShortMessage::ShortMessage(MessageType type, short val) : LeafMessage(type)
{
value = val;
}

ShortMessage::~ShortMessage(void) {}

void ShortMessage::Compress(Compressor &data) const
{
data << value;
}

bool ShortMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void ShortMessage::Visit(Visitor * visitor) const
{
visitor->AddShortValue(value);
}

void ShortMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetShortValue();
}

/*
UnsignedShortMessage implementation
*/
UnsignedShortMessage::UnsignedShortMessage(unsigned short val) : LeafMessage(kMessageUnsignedShort)
{
value = val;
}

UnsignedShortMessage::UnsignedShortMessage(MessageType type, unsigned short val) : LeafMessage(type)
{
value = val;
}

UnsignedShortMessage::~UnsignedShortMessage(void) {}

void UnsignedShortMessage::Compress(Compressor &data) const
{
data << value;
}

bool UnsignedShortMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void UnsignedShortMessage::Visit(Visitor * visitor) const
{
visitor->AddUnsignedShortValue(value);
}

void UnsignedShortMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetUnsignedShortValue();
}

/*
FloatMessage implementation
*/
FloatMessage::FloatMessage(float val) : LeafMessage(kMessageFloat)
{
value = val;
}

FloatMessage::FloatMessage(MessageType type, float val) : LeafMessage(type)
{
value = val;
}

FloatMessage::~FloatMessage(void) {}

void FloatMessage::Compress(Compressor &data) const
{
data << value;
}

bool FloatMessage::Decompress(C4::Decompressor &data)
{
data >> value;
return (true);
}

void FloatMessage::Visit(Visitor * visitor) const
{
visitor->AddFloatValue(value);
}

void FloatMessage::FillVisit(Visitor * visitor)
{
value = visitor->GetFloatValue();
}

/*
Creates and returns a CompositeMessage containing two long values and two string values.
Usage:

Creating new filled ServerInfoMessage composite message:

// parameters are all the params you want to send.
CompositeMessage * message = MessageTypeManager::GetServerInfoMessage(playerCount, maxPlayerCount, gameName, worldName);
TheMessageMgr->SendMessage(addressee, *message);
// (note that there should be a star in front of the 'message' param in the sendmessage method, since the
// GetServerInfoMessage method returns a pointer to the message, whereas the SendMessage method expects
// a reference instead.

// Also, the message should probably be deleted after this, but I dunno if the message is sent right
// away or stored in a cache for a while, which could lead to unexpected results if the message is deleted.

Receiving incoming ServerInfoMessage composite message type:

CompositeMessage * msg = static_cast(message);
Visitor * v = msg->GetData();

long playerCount = v->GetLongValue();
long maxPlayerCount = v->GetLongValue();
String gameName = v->GetStringValue();
ResourceName worldName = v->GetStringValue();

// note that the order of inserting and extracting data of the same type should be the same on both
// the sending and receiving side.
*/
CompositeMessage * MessageTypeManager::GetServerInfoMessage(long playerCount, long maxPlayerCount, String gameName, ResourceName worldName)
{
CompositeMessage * message = new ComposedMessage(kMessageServerInfo);
message->Add(new LongMessage(playerCount));
message->Add(new LongMessage(maxPlayerCount));
message->Add(new StringMessage(gameName));
message->Add(new StringMessage(worldName));
return message;
}

/*
Creates and returns a GameInfoMessage composite message type, containing an unsigned long and a string value.

Usage:

CompositeMessage * message = MessageTypeManager::GetGameInfoMessage(multiplayerFlags, worldName);
TheMessageMgr->SendMessage(addressee, *message);

// extracting

CompositeMessage * msg = static_cast(message);
Visitor * v = msg->GetData();

unsigned long flags = v->GetUnsignedLongValue();
ResourceName world = v->GetStringValue();
*/
CompositeMessage * MessageTypeManager::GetGameInfoMessage(unsigned long flags, ResourceName world)
{
CompositeMessage * message = new ComposedMessage(kMessageGameInfo);
message->Add(new UnsignedLongMessage(flags));
message->Add(new StringMessage(world));
return message;
}

/*
Creates and returns an UpdateScoreMessage, which contains a single Long value.
Notice that in this case, a LongMessage is returned directly without first having
to put it into a ComposedMessage - the advantage of using a proper abstract class.
Receiving parties will just treat it like any other CompositeMessage, as in,
they won't be able nor will they have to see the difference between the two.

Usage:

// send
TheMessageMgr->SendMessage(addressee, *MessageTypeManager::GetUpdateScoreMessage(playerScore));

// receive

long score = static_cast(message)->GetData()->GetLongValue();
*/
CompositeMessage * MessageTypeManager::GetUpdateScoreMessage(long playerScore)
{
return (new LongMessage(kMessageUpdateScore, playerScore));
}

/*
Creates and returns a new UpdateHealthMessage, which has the same structure of
GetUpdateScoreMessage. The only difference between the two is the message identification
number. We could make the two even simpler, by defining and implementing a
CreateLongMessage method, which takes a message identifier and an initial value,
and returns a LongMessage with that value. GetUpdateHealthMessage and GetUpdateScoreMessage
could then both call and return the return value of that GetLongMessage method, passing
their own message type identifier to the method. We won't do that in here though,
to keep things straightforward for now.

Usage:

// send
TheMessageMgr->SendMessage(addressee, *MessageTypeManager::GetUpdateHealthMessage(playerHealth));

// receive

long playerHealth = static_cast(message)->GetData()->GetLongValue();

*/
CompositeMessage * MessageTypeManager::GetUpdateHealthMessage(long playerHealth)
{
return new LongMessage(kMessageUpdateHealth, playerHealth);
}

/*
Creates and returns a ClientOrientation CompositeMessage, which contains two float values.

Usage:

Send:
TheMessageMgr->SendMessage(addressee, MessageTypeManager::GetClientOrientationMessage(azimuth, altitude));

Receive:

Visitor * v = static_cast(message)->GetData();
float azimuth = v->GetFloatValue();
float altitude = v->GetFloatValue();

*/
CompositeMessage * MessageTypeManager::GetClientOrientationMessage(float azimuth, float altitude)
{
CompositeMessage * message = new ComposedMessage(kMessageClientOrientation);
message->Add(new FloatMessage(azimuth));
message->Add(new FloatMessage(altitude));
return message;
}
Obligatory list of social network links:

  • Digg
  • del.icio.us
  • Google
  • E-mail this story to a friend!
  • Print this article!
  • Reddit
  • Slashdot
  • StumbleUpon

Tags: , , , ,

  1. 1 Trackback(s)

  2. Oct 1, 2008: The Lost Art of Bitmasks | For Great Justice

Post a Comment