Page 1 of 2

Sending/receiving classes

Posted: 25 Mar 2011, 07:44
by ThomasLund
I just wanted to give a heads up on this little buried gem in the new API.

You can pretty easily send and receive class structures between C# and Java on the backend without having to create intermediate SFSObject structures.

I will soon make a small example project, but here are some of the details on how to get started on this.

First off - read more about this advanced feature here
http://docs2x.smartfoxserver.com/Advanc ... ialization

To get this working in C#, there is one additional nugget of information that you need. In your code you need to add this:

Code: Select all

         DefaultSFSDataSerializer.RunningAssembly = Assembly.GetExecutingAssembly(); 
otherwise the serialization wont work. Also remember (as stated in the linked documentation page) that your class/package names need to match on server and on client side.

Below are some cut'n'pastes from a test case I got. It should serve as example until I get a small project together that you can run.

First up - we make a test class which is what we want to transport

Code: Select all

	public class TestObject : SerializableSFSType {
		
		public int intField;
		public string strField;
		public bool boolField;
		public double doubleField;
		
		public NestedObject nestedField;
		
		public ArrayList arrayField;
		public Hashtable dictField;
				
		public bool IsEqual(TestObject anotherObject) {
			if (this.intField!=anotherObject.intField) return false;
			if (this.strField!=anotherObject.strField) return false;
			if (this.boolField!=anotherObject.boolField) return false;
			if (this.doubleField!=anotherObject.doubleField) return false;
						
			return true;
		}
	
		#region SerializableSFSType implementation
		public string GetJavaPackageName ()
		{
			return "";
		}
		
		#endregion
		
	}
And here is a usage example where my test code serializes and deserializes the object. Note that here it doesnt actually send it to the server. But inserting a send/receive is trivial.

Code: Select all

	[Test]
		public void TestClass() {
			DefaultSFSDataSerializer serializer = DefaultSFSDataSerializer.Instance;
			DefaultSFSDataSerializer.RunningAssembly = Assembly.GetExecutingAssembly();
			
			TestObject testObj = new TestObject();
			testObj.intField = 10;
			testObj.strField = "test";
			testObj.boolField = true;
			testObj.doubleField = 1.421;
			testObj.nestedField = new NestedObject();
			testObj.nestedField.intField = 20;
			
			testObj.arrayField = new ArrayList();
			testObj.arrayField.Add((int)1);
			testObj.arrayField.Add("testArray");
			
			testObj.dictField = new Hashtable();
			testObj.dictField["test1"] = false;
			testObj.dictField["test2"] = "testDict";
			
			ISFSObject obj = serializer.Cs2Sfs(testObj);
			Assert.IsNotNull(obj);
			TestObject receivedTestObj = serializer.Sfs2Cs(obj) as TestObject;
			Assert.IsNotNull(receivedTestObj);
			
			Assert.IsTrue(receivedTestObj.IsEqual(testObj));
			Assert.IsNotNull(receivedTestObj.nestedField);
			Assert.AreEqual(testObj.nestedField.intField, receivedTestObj.nestedField.intField);
			
			Assert.IsNotNull(receivedTestObj.arrayField);
			Assert.AreEqual(2, receivedTestObj.arrayField.Count);
			Assert.AreEqual(1, (int)receivedTestObj.arrayField[0]);
			Assert.AreEqual("testArray", (string)receivedTestObj.arrayField[1]);
			
			Assert.IsNotNull(receivedTestObj.dictField);
			Assert.AreEqual(2, receivedTestObj.dictField.Count);
			Assert.AreEqual(false, (bool)receivedTestObj.dictField["test1"]);
			Assert.AreEqual("testDict", (string)receivedTestObj.dictField["test2"]);
					
		}
Some important notes to get this working:
1. Only non-generic ArrayList and Hashtable work in C#
2. Hashtable can have only use string type as the key
3. The java extension must be in __lib__ folder to allow sending classes from C# (AS3) -> Java
4. C# class needs this: DefaultSFSDataSerializer.RunningAssembly = Assembly.GetExecutingAssembly();
5. Package names need to match in the C# and Java class

Previous requirement of C# class having
   public string GetJavaPackageName ()
     {
        return "";
     }
is not needed anymore

I trid, but failed

Posted: 31 Mar 2011, 03:49
by xms_wufucan@126.com
There is an error in C# file.
Witch namespace is SerializableSFSType in?

Posted: 31 Mar 2011, 08:14
by ThomasLund
namespace Sfs2X.Protocol.Serialization

Posted: 11 May 2011, 09:01
by Whiskey
Hi Thomas,

would you care to also post the server-side java code for the example above and how to pass the object? I'm trying to get this to work, but having some difficulty. I read the documentation you pointed to, but there the getClass and putClass are used, which you don't mention. I did notice that the server tries to deserialize the object even without a requesthandler being defined: I get a warning in the log saying:

Code: Select all

11 May 2011 11:46:24,637 WARN  [SocketReader] v2.protocol.SFSProtocolCodec     - Error deserializing request: com.smartfoxserver.v2.exceptions.SFSRuntimeException: java.lang.ClassNotFoundException: /TestObject

Posted: 24 May 2011, 10:04
by ThomasLund
Client snippet that works with the advanced tutorial will be posted one of these days!

/T

Posted: 24 May 2011, 10:05
by Whiskey
Awesome!

Posted: 25 May 2011, 10:41
by ThomasLund
In relation to the advanced tutorial, here are some snippets that send classes to the server as in the tutorial

Client side - object

Code: Select all

using System;
using Sfs2X.Protocol.Serialization;

namespace sfs2x.extension.test.serialization.model
{
	public class WaterFloodSpell: SerializableSFSType
	{
		string id ="myId";
    	int hitPoints =100;
    	int count = 7;
     
		public WaterFloodSpell ()
		{
		}
	}
}
Full C# console client

Code: Select all

using System;
using System.Collections;

using System.Collections.Generic;

using Sfs2X;
using Sfs2X.Core;
using Sfs2X.Entities;
using Sfs2X.Requests;
using Sfs2X.Logging;
using Sfs2X.Entities.Data;

using sfs2x.extension.test.serialization.model;
using Sfs2X.Protocol.Serialization;

namespace DemoClient
{
	class ClientSideApp
	{
		bool isStoped = false;
		private SmartFox smartFox;
		private string serverIP = "127.0.0.1";
		private string serverPort = "9933";
		public string zoneName = "SimpleChat";
		public string roomName = "The Lobby";
		private string userName = "userName";
			
		public void Start ()
		{
			try 
			{
				bool debug = false;
				smartFox = new SmartFox (debug);
				smartFox.ThreadSafeMode = false;
			} 
			catch (Exception e) 
			{
				Console.WriteLine("loginErrorMessage = " + e.ToString ());
			}
			RegisterSFSSceneCallbacks ();
			smartFox.Connect (serverIP, Convert.ToInt32 (serverPort));
			Console.WriteLine("Init SmartFoxServer OK");
		}

		public bool IsStoped {
			get { return this.isStoped; }
		}

		public void OnExtensionResponse (BaseEvent evt)
		{
			string cmd = (string)evt.Params["cmd"];
			SFSObject dataObject = (SFSObject)evt.Params["params"];
			Console.WriteLine("send to server cmd = " + dataObject.GetDump());
		}
	

		public void OnConnection (BaseEvent evt)
		{
			bool success = (bool)evt.Params["success"];
			string error = (string)evt.Params["error"];
			Console.WriteLine("On Connection callback got: " + success + " (error : <" + error + ">)");
			if (success) 
			{
				SmartFoxConnection.Connection = smartFox;
				if (smartFox.IsConnected) 
					smartFox.Send (new LoginRequest (userName, "", zoneName));
			} 
			else 
			{
				Console.WriteLine("Connection Error");
			}
		}

		public void OnDebugMessage (BaseEvent evt)
		{
			string message = (string)evt.Params["message"];
			Console.WriteLine("[DEBUG:] " + message);
		}

		public void OnLogin (BaseEvent evt)
		{
			if (evt.Params.ContainsKey ("success") && !(bool)evt.Params["success"])
			{
				Console.WriteLine("[ERROR]: login error" );
			} else 
			{
				smartFox.Send (new JoinRoomRequest (roomName));
			}
		}
		void OnRoomList (SFSObject roomList)
		{
			Console.WriteLine("OnRoomList" );
		}

		public void Quit()
		{
			smartFox.Disconnect ();
			UnregisterSFSSceneCallbacks ();
			isStoped = true;
		}
		
		void OnJoinRoom (BaseEvent evt)
		{
			Room room = (Room)evt.Params["room"];
			Console.WriteLine("OnJoinRoom room = "+ room.Name);
			 SendTestMessage ();
		}
	
		void OnConnectionLost (BaseEvent evt)
		{
			Console.WriteLine("OnConnectionLost");
			UnregisterSFSSceneCallbacks ();
		}
		
		public void  SendTestMessage ()
		{		
			try
			{
				SFSObject info = new SFSObject();
				info.PutBool("IsActive", true);
				info.PutInt("TheNumber", 42);
				info.PutClass("spell", new WaterFloodSpell());
				smartFox.Send(new ExtensionRequest("test", info));
				Console.WriteLine("send to server cmd = " + info.GetDump());
			}
			catch(Exception e)
			{
				Console.WriteLine("SEND ERROR e = " + e.Message);
			}
		}
		
		private void RegisterSFSSceneCallbacks ()
		{
			smartFox.AddEventListener (SFSEvent.CONNECTION, OnConnection);
			smartFox.AddEventListener (SFSEvent.CONNECTION_LOST, OnConnectionLost);
			smartFox.AddEventListener (SFSEvent.LOGIN, OnLogin);
			smartFox.AddEventListener (SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
			smartFox.AddEventListener(SFSEvent.DEBUG_MESSAGE, OnDebugMessage);
			smartFox.AddEventListener (SFSEvent.ROOM_JOIN, OnJoinRoom);
	}
		
		private void UnregisterSFSSceneCallbacks ()
		{
			smartFox.RemoveEventListener (SFSEvent.CONNECTION, OnConnection);
			smartFox.RemoveEventListener (SFSEvent.CONNECTION_LOST, OnConnectionLost);
			smartFox.RemoveEventListener (SFSEvent.LOGIN, OnLogin);
			smartFox.RemoveEventListener (SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
			smartFox.RemoveEventListener(SFSEvent.DEBUG_MESSAGE, OnDebugMessage);
			smartFox.RemoveEventListener (SFSEvent.ROOM_JOIN, OnJoinRoom);
		}
	}
	//class
}
//namespace

Main class

Code: Select all

using System;
using System.Timers;

namespace DemoClient
{
	class MainClass
	{
		public static ClientSideApp _clientSideApp;
		public static void Main (string[] args)
		{
			_clientSideApp = new ClientSideApp();
			_clientSideApp.Start();
			while(!_clientSideApp.IsStoped)
			{
			}
			_clientSideApp.Quit();
		}
	}
}

Posted: 25 May 2011, 11:12
by Whiskey
Thanks! One question: what about GetJavaPackageName ?

I got the suggestion that that is needed client side so that serverside the class definition (that needs to be put in __lib__) can be located. I haven't been able to analyze much today because I'm not at the office, but it seems that will solve my earlier problem. However, I don't see it in these snippets?

Posted: 25 May 2011, 18:41
by Lightnet
How to get the data back into class when send to the server and back again.

WaterFloodSpell waterspell = dataObject.GetClass("spell") as WaterFloodSpell;

I get an null from it. Just an example.

Posted: 25 May 2011, 20:11
by ThomasLund
OK - I'll expand it to cover a round trip object sending. Will take some days as I'm in the middle of Java API docs + some contract work

/T

Posted: 26 May 2011, 14:14
by Whiskey
I would love to see that as well. After some testing, I found with GetDump of the object to send that after PutClass both Unity (before sending), and the server (after receiving and before sending back) report the contents of the object as:

Code: Select all

(class) test: nl.inthere.sfs2x.serialization.TestObject
However, when the object is received in Unity (and evt.Params["params" is casted to an SFSObject) the contents are reported as:

Code: Select all

(sfs_object) test: 
	(utf_string) $C: nl.inthere.sfs2x.serialization.TestObject
	(sfs_array) $F: 
		(sfs_object) 
			(utf_string) V: Server generated class
			(utf_string) N: name
		
		(sfs_object) 
			(int) V: 24
			(utf_string) N: id
The values are correct, but it is not classified as a class, so GetClass can't find it.

Posted: 02 Jul 2011, 06:48
by ThomasLund
Updated original post with 5 good to know bullets

/T

Re: Sending/receiving classes

Posted: 06 Sep 2011, 01:42
by Zelek
ThomasLund wrote: Some important notes to get this working:
1. Only non-generic ArrayList and Hashtable work in C#
2. Hashtable can have only use string type as the key
These two requirements are making my code somewhat unpleasant to work with. Do you think this might change in a future release?

Posted: 07 Sep 2011, 09:13
by ThomasLund
No plans at the moment for that.

/Thomas

Serialization Attempt

Posted: 14 Sep 2011, 11:22
by Dilbertian
I understand that the namespaces have to match but am still trying to figure out what the rules are. For example, in my Unity project, I have a folder _MyCode and have created a sub-folder under that called Serialization, where I have placed this C# class which should be namespace "_MyCode.Serialization" as shown below. Does the Java side dictate what the namespaces will be or can I create it as such in C# and then do the same on the Java side?

Code: Select all

using System;
using Sfs2X.Protocol.Serialization;

namespace _MyCode.Serialization
{
	public class myNewClass : SerializableSFSType
	{
		private const int _MAXPROPS1= 6;
		private const int _MAXPROPS2= 6;
		
		private enum _PROPS1ENUM {
			Prop1A,
			Prop1B,
			Prop1C,
			Prop1D,
			Prop1E,
			Prop1F};
		
		private enum _PROPS2ENUM {
			Prop2A,
			Prop2B,
			Prop2C,
			Prop2D,
			Prop2E,
			Prop2F};
			
		private string _name;
		private int[] _props1;
		private float[] _props2;
		
		public myNewClass() 
		{
			int i;
			
			_name="test";
			_props1=new int[_MAXPROPS1];
			_props2=new float[_MAXPROPS2];
			
			for (i=0; i < _MAXPROPS1; i++) {
				_props1[i]=i;
			}
			for (i=0; i < _MAXPROPS2; i++) {
				_props2[i]=(float)i;
			}
		}
		
		public string GetJavaPackageName()
		{
			return("");
		}
	}
}
Does this look like it will work like this, with the array of ints and floats - or do you see any other glaring issues with my attempt?

Are there any C# samples of this implementation (something simple like your example) which demonstrate this functionality? It seems that all of the more complex samples are only for Flash or AS3 while the 3 C# examples are more simplistic.

-Tim