Composite Message

From C4 Engine Wiki

Jump to: navigation, search

Intro

Composite Message is basically a replacement / addition to the existing method of creating a Message type in C4, thought up and implemented by C4 forums user Cthulhu. The current / default method is to create a new class that inherits from C4::Message, in which a programmer defines the data the Message can contain and implements the Compress and Decompress methods, which adds the data contained in an instance of the Message object to a Compressor and Decompressor object, respectively.

However, this can become a very tedious task, and for even the most simplest of messages, a programmer has to write at least 30 lines of code or more - even if only a single value has to be sent. There are ways to simplify this, by subclassing an existing Message subclass that contains a single value, but even then the programmer has to define a new class and minimally re-define the constructor of that message.

The Composite Message system is an attempt to avoid that tedious process, by breaking down messages into smaller parts. People that have defined a few Message classes in C4 are probably aware that, in most cases, Message classes all contain the same set of basic datatypes, defined by the datatypes supported by Compressor and Decompressor. Looking at that, one can safely say that every message, no matter how complex or small, is composed of these simple building blocks.

The Composite Message system is based on the Composite design pattern, a pattern that defines a way to assemble complex objects from simpler parts. The Composite Message system does just that, and defines both a way to assemble complex messages from simple parts, and an implementation of those simple parts in the form of simple classes that contain one of the data types supported by Compressor and Decompressor.

Using the Composite Message system, one can define a new message type in just one line. Where before you had to create a new class, its fields (data), its constructor, accessors, compress, decompress and then implement them in your .cpp file, now you can create a new message type at runtime with the following line:

return new LongMessage(kMessageSomeMessageType, someValue);

which can replace the entire class definition of, for example, the UpdateScore and UpdateHealthMessage in C4 - replacing 103 lines in the code with just two. More complex messages that contain two or more fields now take just the amount of fields + 1 lines of code. For example, the ClientOrientationMessage, which sends two float values, can be re-defined as follows using the Complex Message system:

CompositeMessage * message = new ComposedMessage(kMessageClientOrientation);
message->Add(new FloatMessage(azimuth));
message->Add(new FloatMessage(altitude));

A notice before I post this code: This code has NOT been tested properly, and probably shouldn't be used in a production environment as-is. Instead, it should be checked and re-checked, mainly for memory leaks and possible errors.

Another notice is that this method has also not been tested in terms of performance. Logically, this method is at least half as slow as the existing message system - the message has to be re-built each time it has to be sent or received, and the message 'tree' has to be traversed at least twice to get data from it. However, how much penalty this translates to isn't known. Limited testing hasn't revealed any significant decrease in performance.

Thus, this message is most suitable for increasing the development speed of a game, thus reducing the time a programmer has to work on it. Once a game has been completed in terms of functionality, the Composite Messages can be replaced with the 'static' message system that currently exists.

For more information, read the elaborate comments in the code itself. If you have any comments, problems or questions, you can post them in this thread at the C4 forums.

The files:

CompositeMessage.h

#ifndef CompositeMessage_h
#define CompositeMessage_h
 
#include "C4Messages.h"
#include "C4Engine.h"
 
/*
 
	Composite Message system
 
The Composite Message system is an alternative to the existing method of defining message types
and their contents in C4 games.
 
The original system, as seen in the C4 demo game, is based on a 'one-class-per-message-type'~ish system,
where a programmer has to write a new subclass to Message, which defines the data to send, the accesors
to that data, and the Compress and Decompress methods to add and extract the data defined in the message
class to and from a Compressor and a Decompressor object, which is sent to an instance of the Message object
after it's received and its type has been established.
 
The classes defined in this Composite Message system provide an alternative to this system.
 
The existing system does work, and does so pretty fast as well, but from a programmer design's point of view,
it's far from optimal. When a programmer just wants to send a single long value, he's still forced to create
and implement an entire new class, including preprocessor statements, the class definition, the fields, the
constructors, the accessors, and the compress / decompress methods. For something as simple as sending a single
numeric value, that seems like a lot of work - and it is. It distracts the programmer from his task at hand,
since for at least a few minutes, he has to concentrate on creating a new Message class, instead of solving an
actual problem.
 
The Composite Message system is an attempt to solve this program, by providing a flexible, re-usable structure
to the problem of creating message types.
 
It works by basically 'assembling' or 'compositing' a complex (or simple) message type from simple building blocks.
At the lowest level, there is a collection of Message subclasses that store a single piece of data. These
classes correspond to the types supported by the Compressor and Decompressor objects.
 
These 'building blocks' can be used to assemble a complex message type, by using a ComposedMessage class which
contains an X-amount of 'subnodes', each of which containing a low-level building block.
 
But it doesn't stop there. The structure also allows other complex messages to appear in the list of subnodes,
allowing complex messages to be composed of other complex messages.
 
Picture a tree diagram. At the top of the diagram, there's your message type, your complex message type. Below that
are three subnodes, the first containing a LongMessage instance (see the classes in this file), the second
containing a FloatMessage instance, and the last containing a Vector3DMessage instance. The LongMessage and
FloatMessage instances end there, but the Vector3DMessage itself has 3 subnodes of its own: three FloatMessages,
each representing an X, Y or Z coordinate in 3D space.
 
That is, the CompositeMessage is 'composed' of two 'simple' messages (also known as 'leaf' messages, since they
don't have any 'branches' themselves), and one other CompositeMessage. Read the comments at the classes themselves
for additional information.
 
There are three main classes in the CompositeMessage class:
 
* CompositeMessage
* ComposedMessage
* LeafMessage
 
CompositeMessage is the parent of all complex messages, both composed or leaf messages. It defines the behavior
of all underlying classes, and is the one that should be used to access and work with all lower messages.
That is, program to the CompositeMessage interface, not to any of its subclasses. See the comments on the
CompositeMessage class for an overview of the methods it defines for itself and its subclasses to implement.
 
The ComposedMessage is a message type that contains submessages (or subnodes). It adds a field to the CompositeMessage
class which stores the actual subnodes. This list is not defined in CompositeMessage, because LeafMessages have no
subnodes. As such, it would be a waste to still have them create and reserve room for a list of subnodes.
 
Finally, the LeafMessage is a CompositeMessage subclass that does not contain / maintain a list of subnodes, thus
making it smaller in terms of memory usage and complexity.
 
See the comments at these three classes themselves for more information.
 
 
What are the advantages and disadvantages of using this message system over the existing one?
 
* Flexibility
	You are no longer bound to a fixed class, and instead can assemble a complex message type at runtime,
	based on certain criteria. Of course, you'll want to make sure a complex message is of the exact same
	structure (or at least contains the exact same data in the same order) on both the sending and receiving
	side of the program.
 
* Reduced classes
	In the current demo game, there's over two dozen Message classes, a part of them equal in terms of the data
	they contain, and the rest all share roughly the same set of data types to be sent between clients (that is,
	they're all composed of the same building blocks, but then hard-coded). Using this structure, you can do away
	with all those classes, reducing the total amount of code both to be produced and maintained, which makes the
	overall program less complex and easier to maintain.
 
* Concentrate on solving problems, not copy/pasting classes
	To add a Message type to the game is, in the old system, a tedious, boring task, which mainly involves
	copying an existing Message type and changing some of the variable and accessor names. This isn't motivating
	for a programmer, since it's boring and unchallenging, and it wastes time, time an employer (if used in a 
	professional environment) has to pay for. With this solution, a programmer can assemble a message type
	in a much shorter time, and concentrate on solving a problem instead of copying a class.
 
* Fully compatible with existing Message system
	Using this system does not rule out the existing system. You could, if you want, use this system alongside
	the existing one. The only difference between the two systems is how the objects are created and its 
	data is extracted. It's even possible to change the internal structure of an existing Message type to
	use this Message type, so that a programmer doesn't have to change anything in the code that uses the 
	message type.
 
Of course, there are also some disadvantages.
 
* Complexer than the existing system.
	The current system is pretty straightforward, a class has some fields and some methods, simple. This system
	uses a tree structure to store the data structure, a Visitor design pattern to both insert and extract data
	from it, and is a lot more generic than the existing system (that is, you work with the CompositeMessage class
	interface mainly, instead of an XMessage type). However, as any good OO-structure should be, a programmer shouldn't
	actually have to know anything about the internal representation of the data of, in this case, a complex message.
	That is, all a programmer would have to know is how to assemble a complex message and insert / retrieve data from
	it. It's a bonus he knows how it's stored internally, but not an obligation.
 
* More demanding of hardware
	A Complex Message uses, by design, more objects than the current message system. The current only uses one
	per message, whereas the Complex Message system uses at least one, and more depending on the amount of
	data to be sent. That in itself is more demanding, but extracting data (by the Visitor object) adds an extra
	layer of operations to be executed to get the data from the message.
 
	However, this extra overhead is almost negligible, when compared to the amount of operations, method calls
	etc used in other systems like collision detection, rendering etc. This system hasn't been properly 
	performance tested or compared with the existing system yet, but it's safe to assume that any additional
	overhead this may cause will not cause any measureable delays over the existing system. That, and one has to
	keep in mind that a programmer costs more than hardware, i.e. it's cheaper to upgrade your hardware to compensate
	for the decrease in performance than it costs to hire a programmer for a day to write out all your Message
	classes.
 
* Fully compatible with existing system
	Yes, this can also be a disadvantage, especially when this system is used alongside the existing system. 
	Doing that will increase the complexity of the code, and may cause confusion.
 
 
Overall, this system is best used for increasing the production speed of a program written in C4. It's ideal
for writing an initial version of a game, where its functionality has to be tested etc. When it turns out this
message system is causing a lot of overhead compared to the old-style message system, it's pretty easy to refactor
the whole thing to use the old system again. However, this will take a lot of time to do, so it's up to a developer
to determine whether it's worth spending time on. Once again, this system has not been performance-tested, so it
may cause a large amount of overhead or something nearly immesureable.
 
 
*/
 
namespace C4
{
 
	/*
		The Visitor class is a class representing an object that is able to traverse a 
		Complex Message structure to gather data.
 
		It works by implementing a 'Visit' method in each subtype of ComplexMessage. The
		Visit method takes an instance of Visitor as parameter, and should call a method
		in that Visitor object that corresponds to the data it carries, or should pass
		the Visitor on to its subnodes, if any.
 
		The method called in the Visitor method can be either AddXValue or GetXValue, where
		X corresponds to one of the supported data types. The main supported values correspond
		to those supported by C4's Compressor and Decompressor method, such as long, bool, float,
		char, and their unsigned counterparts. Depending on the situation, complex types / values
		can be added as well, such as Vector3D and friends.
 
		The Visitor can be used to both extract and fill a complex message with data. When used to
		extract data, the Visit() method in a Complex Message node will call a 'SetXValue()' method
		in the Visitor object, passing its locally stored data to be added to the visitor. Once
		the Visitor has traversed the entire Complex Message structure, a client can then extract 
		the data using the 'GetXValue()' method(s).
 
		The SetXValue() method will append the given value to an internal array of values of that type.
		The GetXValue() method will return the first value in the array of that type when called once,
		the second when called again, the third when called again, etcetera. When there's more calls
		to GetXValue() than there were to SetXValue, GetXValue will return a 0 value (for numbers) or
		an empty value (for other types).
 
		The Visitor can also be used to fill Complex Message types with data. This is done in the exact 
		opposite way of extracting data from the Complex Message, in that first a Visitor object is 
		created, then the client does several calls to the SetXData methods in the Visitor. Then, the
		client sends the Visitor through the Complex Message through the FillVisit() method, which
		works in the exact same way as the Visit method, but instead calls the GetXValue() method
		in the Visitor and sets its local value to the return value of that method.
 
		Feel free to add other data types to the Visitor class, such as Vector3D or other data types
		that are assembled from 'lower' data types.
	*/
	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<long> longValues;
			long currentLongValue;
 
			Array<unsigned long> unsignedLongValues;
			long currentUnsignedLongValue;
 
			// Boolean values
			Array<bool> boolValues;
			long currentBoolValue;
 
			// Float values
			Array<float> floatValues;
			long currentFloatValue;
 
			// String values
			Array<const char *> stringValues;
			long currentStringValue;
 
			// Char values
			Array<char> charValues;
			long currentCharValue;
 
			Array<unsigned char> unsignedCharValues;
			long currentUnsignedCharValue;
 
			// Short values
			Array<short> shortValues;
			long currentShortValue;
 
			Array<unsigned short> unsignedShortValues;
			long currentUnsignedShortValue;
 
			// long64 values
			Array<long64> long64Values;
			long currentLong64Value;
 
			Array<ulong64> 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.
 
		The CompositeMessage class only has two implemented methods: the Constructor,
		which passes the 'type' parameter to the Message superclass and the GetData() 
		method, which creates a Visitor object (see above) and passes it to the
		object's Visit method. The observant may note that CompositeMessage itself
		does not implement a Visit method - instead, the Visit method of one of its
		subclasses is called, whichever is currently in use.
 
		(That is, when CompositeMessage x is an instance of LeafMessage, calling
		GetData on x will cause LeafMessage's Visit method to be called, etc. Good
		example of polymorphism etc)
 
		A more detailed description of the methods and what they do is at the method definitions.
 
	*/
	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.
			// A Leaf subclass should add its local data to the Compressor.
			// A ComposedMessage should either just pass the Compressor
			// to its subnodes, or disassemble its own data and pass it
			// to the Compressor.
			virtual void Compress(Compressor& data) const;
 
			// The Decompress method is called after the type of a Message has
			// been determined. As above, leaf classes should extract 
			// their data from the Decompressor and assign that data
			// to their local fields, while ComposedMessages should pass
			// the data to their subnodes.
			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<CompositeMessage *> 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.
 
		Note that this one's typed constructor HAS to specify a value for its second (value) 
		argument. MessageType is a typedef of unsigned long, and as such may cause ambiguity
		with the default constructor. Haven't been able to figure out a proper fix, since
		while setting a default MessageType would work, it also causes a circular dependency
		(at the moment) with MGMultiplayer.h.
	*/
	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<kMaxChatMessageLength> 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<kMaxGameNameLength> 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<CompositeMessage *>(message);
	Visitor * v = msg->GetData();
 
	long playerCount = v->GetLongValue();
	long maxPlayerCount = v->GetLongValue();
	String<kMaxGameNameLength> 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<kMaxGameNameLength> 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<CompositeMessage *>(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<CompositeMessage *>(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<CompositeMessage *>(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<CompositeMessage *>(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;
}
Personal tools