• Examples (iOS)
• Examples (Java/Android)
• Examples (C++)
Server API Documentation

 

» SFSObject/SFSArray: Class serialization

In this article we are going to explore an advanced feature provided by the SFSObject and SFSArray classes. The feature allows to exchange custom classes (POJOs) between client and server in a completely transparent fashion. Please note that this feature is not supported by our JavaScript, Objective-C and C++ APIs.

As we have seen in the introduction to SFSObject/SFSArray, you might find yourself copying data to and from your model classes into SFSObject(s) quite often in your Extension code. With Class serialization you will be able to directly send and receive your model classes without any manual conversion.

NOTE
This article touches advanced topics and requires a solid knowledge of OOP (in both Java and the client side language of choice) and familiarity with the concepts of Type Reflection and Class loading mechanism in the JVM.

» The RPG game example

In order to demonstrate the features of Class serialization we will use a real-life use case and provide the source files so you can review the code and experiment with it. The client side code for this example is provided both in C# and ActionScript 3.

For this study we are working on an RPG game. We will create several classes for the game model and we will start off with the characters, using the following properties:

In order to model our RpgCharacter class both for server (Java) and client (C#, ActionScript 3) side, we will have to comply to a series of conventions for the serialization:

Also we must be aware that not all classes can be transported. For example anything referencing a local resource (file, socket, database connection) is not serializable. This is a list of the types that can be used with the Class serialization mechanism and how they are translated from one language to the other:

Java C# ActionScript 3
null null null
boolean bool Boolean
byte byte int
short short int
int int int
long long Number
float float Number
double double Number
String String string
Collection<Object>
(List, Set, Queue...)
ArrayList Array
Map<String, Object> HashTable Object

» The example classes

The following diagram better illustrates our main RpgCharacter class:

RpgCharacter

There are three different interfaces for each of the above fields with the exclusion of CharacterProperty which is a class itself. Each interface has a number of implementations:

Let's see how these interfaces, and the relative implementations, are modeled.
This is the Spell.java interface:

	package sfs2x.extension.test.serialization.model;
	
	public interface Spell
	{
		String getId();
		void setId(String id);
		
		int getHitPoints();
		void setHitPoints(int hit);
		
		int getCount();
		void cast();
	}

And this is one of the implementations, WaterFloodSpell.java:

	package sfs2x.extension.test.serialization.model;
	
	import com.smartfoxserver.v2.protocol.serialization.SerializableSFSType;
	
	public class WaterFloodSpell implements Spell, SerializableSFSType
	{
		String id;
		int hitPoints;
		int count = 7;
		
		public WaterFloodSpell()
	    {
		    // Empty constructor
	    }
		
		public WaterFloodSpell(String id, int hitPoints)
	    {
		    this.id = id;
		    this.hitPoints = hitPoints;
	    }
		
		public String getId()
	    {
	    	return id;
	    }
		public void setId(String id)
	    {
	    	this.id = id;
	    }
		public int getHitPoints()
	    {
	    	return hitPoints;
	    }
		public void setHitPoints(int hitPoints)
	    {
	    	this.hitPoints = hitPoints;
	    }
		public int getCount()
	    {
	    	return count;
	    }
		
		@Override
		public void cast()
		{
			if (count > 0)
			{
				System.out.println("CASTING SPELL: " + id);
				count--;
			}
			else
			{
				System.out.println("CAN'T CAST SPELL. BUY MORE!");
			}
		}
	}

As you can notice the class implements the SerializableSFSType interface and provides an empty constructor as required by the conventions previously discussed. Also all class fields that need to be exposed publicly provide access via getters and setters.

Let's now take a look at the same interface and implementation in client side language:

	interface Spell
    {
        string GetId();
        void SetId(string id);

        int GetHitPoints();
        void SetHitPoints(int hitPoints);

        int Count();
        void Cast();
    }

	class WaterFloodSpell : Spell, SerializableSFSType
    {
        public string id;
        public int hitPoints;
        public int count;

        public string GetId() { return id; }
        public void SetId(string id) { this.id = id; }

        public int GetHitPoints() { return hitPoints; }
        public void SetHitPoints(int hitPoints) { this.hitPoints = hitPoints; }

        public int Count() { return count; }

        public WaterFloodSpell() { }
        public WaterFloodSpell(string id = null, int hitPoints = 70)
        {
            this.id = id;
            this.hitPoints = hitPoints;
        }

        public void Cast()
        {
            Console.WriteLine("Casting " + id);
        }
    }
	package sfs2x.extension.test.serialization.model
	{
		public interface Spell
		{
			function get id():String 
			function set id(id:String):void 
		                 
			function get hitPoints():int 
			function set hitPoints(hit:int):void 
		                 
			function get count():int 
			function cast():void 
		}
	}

	package sfs2x.extension.test.serialization.model
	{
		import com.smartfoxserver.v2.protocol.serialization.SerializableSFSType
	
		public class WaterFloodSpell implements Spell, SerializableSFSType
		{
			private var _id:String
			private var _hitPoints:int
			private var _count:int
		
			public function WaterFloodSpell(id:String=null, hitPoints:int=70)
		    {
			    this._id = id
			    this._hitPoints = hitPoints
		    }
		
			public function get id():String 
			{
		    	return _id
		    }
	        
			public function set id(id:String):void 
			{
		    	this._id = id
		    }
	        
			public function get hitPoints():int 
			{
		    	return _hitPoints
		    }
	        
			public function set hitPoints(hitPoints:int):void 
			{
		    	this._hitPoints = hitPoints
		    }
	        
			public function get count():int 
			{
		    	return _count
		    }
	        
			public function set count(value:int):void 
			{
				_count = value
			}
	
			public function cast():void
			{
				trace("Casting " + _id)
			}
		}
	}

You can notice that both versions use the exact same package as requested by the Class serialization conventions.

Now that we have both classes on the client and server side we are ready to send them over the network in a very convenient way. This is a short example snippet, just to give you an idea.

Client side:

	ISFSObject params = SFSObject.NewInstance();
	params.PutBool("IsActive", true);
	params.PutInteger("TheNumber", 42);
	params.PutClass("spell", new WaterFloodSpell()) // This is our custom class instance!
	
	sfs.Send(new ExtensionRequest("test", params));
	var params:ISFSObject = new SFSObject();
	params.putBool("IsActive", true);
	params.putInteger("TheNumber", 42);
	params.putClass("spell", new WaterFloodSpell()) // This is our custom class instance!
	
	sfs.send(new ExtensionRequest("test", params));

Server side:

	public class TestRequestHandler extends BaseClientRequestHandler
	{
		@Override
		public void handleClientRequest(User sender, ISFSObject params)
		{
	    	boolean isActive = params.getBool("IsActive");
	        int theNumber = params.getInt("TheNumber");
	        WaterFloodSpell spell = (WaterFloodSpell) params.getClass("spell");
	    }
	}

In the source code provided with this article we will create three different instances of the RpgCharacter and populate them with actual game data, which is then exchanged between client and server in both directions to demonstrate the serialization process.

The output of the data structure obtained by the Extension is the following:

+------------------------------------+
 RPG Character: Sigfried
+------------------------------------+
	Type: knight
	Inventory: 
		longSword -- Price: 500, Active: true
		shortSword -- Price: 200, Active: true
	Spells: 
		swarm -- HitPoints: 3, Qty: 10
	Properties: 
		endurance -- value: 70/100
		combatSkill -- value: 55/100
		strength -- value: 60/100
+------------------------------------+
 RPG Character: Tristan
+------------------------------------+
	Type: knight
	Inventory: 
		DragonSword -- Price: 1500, Active: true
		IceKnife -- Price: 300, Active: true
		IronShield -- Price: 150, Active: true
	Spells: 
	Properties: 
		endurance -- value: 50/100
		combatSkill -- value: 75/100
		strength -- value: 80/100
+------------------------------------+
 RPG Character: Hayden
+------------------------------------+
	Type: sorcerer
	Inventory: 
		DiamondKnife -- Price: 600, Active: true
	Spells: 
		vortex -- HitPoints: 30, Qty: 10
		flames -- HitPoints: 20, Qty: 5
		flood -- HitPoints: 3, Qty: 7
		swarm -- HitPoints: 15, Qty: 10
	Properties: 
		endurance -- value: 50/100
		combatSkill -- value: 55/100
		strength -- value: 50/100  

There is also a particular server warning that is logged on the server side when the data structure is sent back from the client to the server:

No public setter. Serializer skipping private field: count, from class: XYZ...

This is not really a problem as it was done on purpose: we decided to make the count property not writable in the server side version of the classes in order to avoid client values overwriting the default values.

» Deploying the Extension

Class serialization requires special attention during the deployment phase because the model classes need to be "seen" by SFS2X at the level of the topmost Class Loader.

As you may recall each Extension runs in a separate Class Loader which implies that if we deploy the model classes inside the Extension .jar file we will isolate those classes in the specific Extension's Class Loader. From the main Class Loader the server won't be able to know about these classes therefore generating an error when attempting to deserialize the data (if you any doubts about how this works in SFS2X we recommend to consult the Extension overview).

In order to avoid this problem we need to make sure that the model classes are deployed in the extensions/__lib__/ folder. So for this example we have prepared two files, RpgModel.jar which goes in extensions/__lib__/ and RpgExtension.jar which goes in extensions/rpg/.

» Conclusions

One last word goes to the performance of Class serialization. In general the process is fast and efficient thanks to the SFS2X protocol compression. In our Rpg game the resulting packet size is only 860 bytes (vs. 4323 bytes uncompressed).

Some of the downsides in using Class serialization is that you don't have direct control in the optimization of numeric types and there is additional overhead due to the runtime type reflection. We think that developers can take the best of both worlds by optimizing via SFSObject the critical messages (those the need maximum efficiency), and use Class serialization where appropriate.

In order to complete the tutorial we highly recommend to study the provided source files and see the Class serialization in action.

>> DOWNLOAD the source files related to this tutorial <<