Explain how to use byte code on PhotonPeer.RegisterType()

The whole answer can be found below.

Please note: The Photon forum is closed permanently. After many dedicated years of service we have made the decision to retire our forum and switch to read-only: we've saved the best to last! And we offer you support through these channels:

Try Our
Documentation

Please check if you can find an answer in our extensive documentation on Photon Server.

Join Us
on Discord

Meet and talk to our staff and the entire Photon-Community via Discord.

Read More on
Stack Overflow

Find more information on Stack Overflow (for Circle members only).

Write Us
an E-Mail

Feel free to send your question directly to our developers.

Explain how to use byte code on PhotonPeer.RegisterType()

johnny_tictoc
2017-02-22 21:44:15

This link doesn't explain what byte code that we need to pass has to be. Can I get clarification? Why (byte)'W' in this example? What are the byte codes that are already being used by the framework? Thanks.

Comments

JohnTube
2017-02-22 22:56:39

Hi @johnny_tictoc,

Good question, we should add this information to the documentation.
Your answer can be found in the PUN source code, CustomTypes.cs class file:

internal static void Register()  
    {  
        PhotonPeer.RegisterType(typeof(Vector2), (byte)'W', SerializeVector2, DeserializeVector2);  
        PhotonPeer.RegisterType(typeof(Vector3), (byte)'V', SerializeVector3, DeserializeVector3);  
        PhotonPeer.RegisterType(typeof(Quaternion), (byte)'Q', SerializeQuaternion, DeserializeQuaternion);  
        PhotonPeer.RegisterType(typeof(PhotonPlayer), (byte)'P', SerializePhotonPlayer, DeserializePhotonPlayer);  
    }

So reserved codes for default registered types in PUN are:

  • W (23) for Vector2
  • V (22) for Vector3
  • Q (17) for Quaternion
  • P (16) for PhotonPlayer

johnny_tictoc
2017-02-23 01:50:20

Thanks!

And I assume we can register up to 255 byte code?

JohnTube
2017-02-23 12:41:54

Yes since it's byte you can use values from 0 to 255 except 16, 17, 22 and 23.

johnny_tictoc
2017-02-23 18:04:29

Thanks!

johnny_tictoc
2017-02-27 19:50:47

@JohnTube

The example in this link is very confusing. The method
short SerializeVector2(StreamBuffer outStream, object customobject)
does not conform to the delegate signature
public delegate byte[] SerializeMethod(object customObject);

It looks like PhotonPeer.RegisterType has 2 overloads:
PhotonPeer.RegisterType(Type customType, byte code, SerializeMethod serializeMethod, DeserializeMethod constructor)
and
PhotonPeer.RegisterType(Type customType, byte code, SerializeStreamMethod serializeMethod, DeserializeStreamMethod constructor)

I think that the documentation should be updated, and have examples for both PhotonPeer.RegisterType() overloads.

JohnTube
2017-02-28 09:14:02

Hi @johnny_tictoc,

Thank you for pointing that out.
The RegisterType methods signatures were changed lately but we forgot to update the examples.
We will fix the documentation.

ostryjagoda
2017-06-28 15:48:46

I'm integrating photon with our game.
When I had stuck on custom serialization, I decided to use C# BinarySerializer, because we have much custom types (C# need only serialization attribute in most cases) and it isn't easy to define methods for all those types.
Today we have perform performence tests and we had discovered, that BinarySerializer is sending redundant data.
We decided to use photon custom serialization.

I had make and register methods for GameObject, Component and Vector3.
We have more custom types, but I was curious what will happen (C# BinarySerializer needs only those 3 methods, cause other types had serialization flag).

I have run main client (create room) and join to this room by other client.
Main client perform PhotonNetwork.Instantiate, passed argument was prefab with PhotonView and few components, that implements IPunObservable.
When prefab appears (I gues, that there is chance that OnPhotonSerializeView was performed on that prefab), on the main client side, that error apears (edit - it apears always, this is not some kind of connection problem):

Receiving failed. SocketException: ConnectionReset
UnityEngine.Debug:LogError(Object)
NetworkingPeer:DebugReturn(DebugLevel, String) (at Assets/Modules/Photon/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:1522)
ExitGames.Client.Photon.<>c__DisplayClass145_0:b__0()
ExitGames.Client.Photon.TPeer:DispatchIncomingCommands()
ExitGames.Client.Photon.PhotonPeer:DispatchIncomingCommands()
ExitGames.Client.Photon.PhotonPeer:Service()
//private stack trace - cutted

I gues, that this error apears because I miss some serialization methods, but, normaly there is error (or warning) about serialization and it informs me what have i miss (I have seen it once).
Can someone explain it to me, why there is no error about serialization, and why that error apears?

Serialization problem may occures only here:

public void OnPhotonSerializeView (PhotonStream stream, PhotonMessageInfo info) { if (!isDisposed) { if (stream.isWriting) { stream.SendNext (calls.ToArray()); calls.Clear (); } else { object[][] queue = (object[][])stream.ReceiveNext (); for (int i = 0; i < queue.Length; i++) { if (_dequeuer != null) _dequeuer (ref queue[i]); CallEvent (queue[i]); } } } else Debug.LogWarning ("PhotonEventSynchronizer is disposed!"); }

Objects in queue can be type of everything.

Just after this error, that log appears:

OnStatusChanged: DisconnectByServer current State: Joined
UnityEngine.Debug:Log(Object)
NetworkingPeer:OnStatusChanged(StatusCode) (at Assets/Modules/Photon/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:1981)
ExitGames.Client.Photon.<>c__DisplayClass146_0:b__0()
ExitGames.Client.Photon.TPeer:DispatchIncomingCommands()
ExitGames.Client.Photon.PhotonPeer:DispatchIncomingCommands()
ExitGames.Client.Photon.PhotonPeer:Service()
//private stack trace - cutted

ostryjagoda
2017-06-29 14:38:40

I'm trying to add custom serialization method for class "a".
Class "a" contains Vector3 field, which I need to serialize.
I want use Vector3 serialization method to achieve that (DRY standard), which is defined in CustomTypes, but this method is private.
I don't wan't to modify code provided by Exit Games (CustomTypes.cs and PhotonNetwork.cs - where CustomTypes.Register() is called - are provided by Exit Games).

How can I provide serialization method for class "a", without breaking DRY standard or modify Exit Games code?
Is there any method, that can I call, for already defined custom types? It would be usefull, because calling serialization method directly, also isn't perfect idea... its only acceptable.
Something like "Protocol.Serialize/outStream.Write(Registered type that photon can serialize)"

Edit: I need also to serialize String field in class "a", photon can serialize that type, but I'm forced to conver string to bytes on "my own hand"? Seriously?

I would like to notice, that:

  • serialization methods in "CustomTypes" class are private, but buffors associated with those methods, are public XD - there is no logic in that.
  • CustomTypes.Register() is called from Exit Games code - it's bad. Custom types are tool for developer, so developer should decide about custom types registration.
  • Using char, to register custom types, is rly, rly, rly bad - developer have to remember, which chars was used, it should be done like this (without handling chars):
  • Your code section (forum) is creepy
  • When i use post edit twice, my post vanish (when i click accept edit). It happens 3 times for me, its rly annoying. I'm writing post by 15-30 minutes (with editions) and... booom vanish, destroy, my 30 minutes goes to trash (there is hack for it, you have to press cancel and somehow post is restored from cache, but you have to copy it and post again).
    Bug screen shot:

ostryjagoda
2017-06-29 15:31:51

I will add new post instead of edit (that bug is horror).

I have figur out the solution, but there is huuge lack of documentation.
There is method, that allow me, to serialize object, that photon knows (Protocol.Serialize (Object o)) - no documentation.
There is also no documentation, about custom serialization methods parameters (for example length in deserialization), the same about StreamBuffer.

My today work was guessing, guessing and again guessing - no documentation...

Maybe my boss, will post code here (its is his decision). That code should be provided with photon...

julkopki
2017-06-29 15:41:38

Find the mentioned code here:

https://pastebin.com/5bS3TDsS

JohnTube
2017-06-30 13:35:23

Hi @ostryjagoda @julkopki

I apologize about the issues you encountered in your experience on the forums.
It uncommon but it happens.

Thank you for your reports and sharing the code with us.
It looks useful.
We are busy here after a Unite week.
We will take a look the code and let you know.

jeanfabre
2017-06-30 13:56:00

Hi,

please check this post, it features a fully working example on how to serialize a class:

http://forum.photonengine.com/discussion/comment/35023/#Comment_35023

the class contains a list of vector3 and ints, so in your case it will be simpler, just serialize the vector3.

Let me know how it goes.

Bye,

Jean

ostryjagoda
2017-07-03 07:13:40

Wow. clean this topic - somehow duplicates appears.

@Jeanfabre - I had check that post.
Its something new for me, I was thinking that there is only "stream way", to serialize custom objects.
It still isn't clear for me, how it works - I can define serialization method for Vector3 using "stream way", and next try, to use Protocol.Serialize(Object o), which isn't "stream way".
I guess, that "stream way" and "byte way" are not compatibile and there will be redundant gc (Photon need to create stream only for single method use)?

Edit:
Two things:

  • There is no documentation, about that Photon is using Equals method, on custom type, to decide if it should be serialized or not (if it wasn't change). I have stuck on it, because I was thinking that this mechanism is working after serialization (based on byte array for e.g) and default equals method was not suitable for my case...
  • There is no documentation, about that Photon isn't perform serialization in the same line, when you call PhotonStream.SendNext. It causes races condition in my case (I was clearing queue right after that call).
  • You should copy object, if you wan't to compare it by equals. Curently, I'm reciving "this" reference in my Equals method - creepy solution. I will have to use custom serialization methods, before I will pass my object to photon. Consider using hashes...

jeanfabre
2017-07-03 11:44:46

Hi,

yes, the problem is complex because there is yet another factor coming into play with streaming data where the stream is highly efficient at not sending a value that hasn't changed, so it is justified imo that you have two ways of serializing, one where you get the whole object within RPC data where you have no history of it and so you send everything, and one with streaming where it internally efficiently only send what has changed.

as for GC, I am not sure what you mean here because only one serialization technic is used at a time, for a given context, so there are no redundant GC. Photon Creates a stream for sending data to the server yes, but it doesn't create an extra stream because you want to serialize, it works the opposite way, photon detects you have registered a class.

to be clear: the registration and serialization will work as well for streaming data, simply use Stream.SendNext and Stream.ReceiveNext passing your custom class and all will be well.

full component below ( don't forget to observe that component for data to be streamed)

  
  
using ExitGames.Client.DemoParticle;  
using ExitGames.Client.Photon;  
using UnityEngine;  
using System.Linq;

[RequireComponent(typeof(PhotonView))]  
public class CustomClassRpcs : Photon.MonoBehaviour , IPunObservable  
{

    public int RpcIntervalSeconds = 2;

    private TimeKeeper InstantiateTimer;

    void Start()  
    {

		PhotonPeer.RegisterType(typeof (CustomClassTest), (byte) 'A', SerializeCustomClassTest, DeserializeCustomClassTest);

        InstantiateTimer = new TimeKeeper(RpcIntervalSeconds*1000);

    }


    private int rpcCallCounter = 0;  
    private int lastRpcCallId = 0;  
    private int rpcCalledCounter = 0;

    public void Update()  
    {  
        if (OnIntervalInstantiate.pause)  
        {  
            return;  
        }

        if (this.photonView.isMine)  
        {  
            if (InstantiateTimer.ShouldExecute)  
            {  
                InstantiateTimer.Reset();

				CustomClassTest _t = new CustomClassTest();  
				_t.m_indices = new int[]{1,2,3};  
				_t.m_vertices = new Vector3[]{Vector3.zero,Vector3.one};  
				this.photonView.RPC("SomeRpc", PhotonTargets.All,_t);  
            }  
        }  
    }


    [PunRPC]  
	public void SomeRpc(CustomClassTest t)  
    {  
		string result = "Result: ";  
		if (t.m_indices!=null)  
		{  
			result += "Indices : ";  
			foreach(int _i in t.m_indices)  
			{  
				result += " "+_i;  
			}  
		}  
		if (t.m_vertices!=null)  
		{  
			result += " Vertices : ";  
			foreach(Vector3 _v in t.m_vertices)  
			{  
				result += " "+_v;  
			}  
		}

		Debug.Log(result);  
    }

	 static byte[] SerializeCustomClassTest(object customObject)  
	{  
		CustomClassTest so = (CustomClassTest) customObject;

		//count of indices, following by indices, count of vertices followed by vertices  
		object[] temp = new object[so.m_indices.Length+so.m_vertices.Length+2];  
		int i = 0;  
		temp[i++] = so.m_indices.Length;

		foreach(int _i in so.m_indices)  
		{  
			temp[i++] = _i;  
		}

		temp[i++] = so.m_vertices.Length;  
		  
		foreach(Vector3 _v in so.m_vertices)  
		{  
			temp[i++] = _v;  
		}

		return Protocol.Serialize(temp);  
	}  
	  
	  
	static object DeserializeCustomClassTest(byte[] bytes)  
	{  
		CustomClassTest so = new CustomClassTest();

		object[] tmp = (object[]) Protocol.Deserialize(bytes);  
		int o_i = 0;

		int _indicesCount = (int)tmp[o_i++];  
		if (tmp.Length>o_i+_indicesCount-1)  
		{  
			so.m_indices = new int[_indicesCount];  
			for(int i=0;i<_indicesCount;i++)  
			{  
				so.m_indices[i] = (int)tmp[o_i++];  
			}  
		}

		int _verticesCount = (int)tmp[o_i++];  
		if (tmp.Length>o_i+_verticesCount-1)  
		{  
			so.m_vertices = new Vector3[_verticesCount];  
			for(int i=0;i<_verticesCount;i++)  
			{  
				so.m_vertices[i] = (Vector3)tmp[o_i++];  
			}  
		}

		return (object) so;  
	}

	#region IPunObservable implementation

	void IPunObservable.OnPhotonSerializeView (PhotonStream stream, PhotonMessageInfo info)  
	{  
		if (stream.isWriting)  
		{  
			CustomClassTest _t = new CustomClassTest();  
			_t.m_indices = new int[]{1,2,Random.Range(0,100)};  
			_t.m_vertices = new Vector3[]{Vector3.zero,Vector3.one};  
			stream.SendNext(_t);

		}else{  
			CustomClassTest t = stream.ReceiveNext() as CustomClassTest;  
			string result = "stream: ";  
			if (t.m_indices!=null)  
			{  
				result += "Indices : ";  
				foreach(int _i in t.m_indices)  
				{  
					result += " "+_i;  
				}  
			}  
			if (t.m_vertices!=null)  
			{  
				result += " Vertices : ";  
				foreach(Vector3 _v in t.m_vertices)  
				{  
					result += " "+_v;  
				}  
			}  
			Debug.Log(result);  
		}

	}

	#endregion  
}

public class CustomClassTest  
{  
	public int[] m_indices;  
	public Vector3[] m_vertices;

}

  

Bye,

Jean

ostryjagoda
2017-07-03 12:17:23

@jeanfabre its clear for me, that best way to sync is send only changes - its ok.
I'm just telling you, that using equeals method, on objects without copying it, meakes high risk of this.equals(this) case, which will always return true (even if someone will override this method properly), even if content of some object will change.
You should inform developer, that he shouldn't use serialization methods with not copied reference types or use hashes insted of equals.
To by clear, consider this example:

public class MyDataAgregationClass{  
   public int someData = 1;  
}

MyDataAgregationClass mdac = new ....

public void OnPhotonSerializeView (.....){  
    mdac.someData++;  
    stream.SendNext (mdac);  
}

-Serialization methods for MyDataAgregationClass and it registration here- (implementation is not important here)

For this case, data will be send only once, even if mdac.someData will change.

"Photon Creates a stream for sending data to the server yes, but it doesn't create an extra stream because you want to serialize"
I'm only guessing. Its clear, that stream is created only once (in typical case), but what if I will use "Protocol.Serialize (-Type x object-);" huge ammount of times in other custom serialization methods? (I have defined serialization methods for type x, by the "stream way", so they need stream passed as a parameter).
For each call of "Protocol.Serialize (-Type x object-);", photon needs to pass some stream, to my custom serialization methods - I'm guessing, that photon needs to create that stream, if there is no context of serialization (if you use "Protocol.Serialize (-Type x object-);", no context is passed).

jeanfabre
2017-07-03 13:38:51

Hi,

Sorry, I am lost at what you trying to explain. there is no equal statement in the component I sent you, can you be more precise, maybe with pointing at a code line and giving a counter example? We always strive to improve this framework, so if we can change something to improve, we'll definitly consider it.

the serialization protocole is meant to be used by Photon, if you plan on using this serialization system outside Photon usage, then maybe you should look into your own serialization system. If you make custom usage of this protocole, Photon will nto be the one initiating the serialization process. so Photon will not create a new sream, Photon Only create a new stream when it needs to send/receive data.

does the stream way works for RPC calls? have you checked?

Bye,

Jean

ostryjagoda
2017-07-03 14:58:43

4360 line in NetworkingPeer.cs - there is equal statement.
In your case, everything is fine, because you are creating new CustomClassTest, in each OnPhotonSerializeView.

I'm using Protocol.Serialize in custom serialization methods (for Photon), is that "outside Photon usage"?
If i have class, which contains Vector3 field, I'm not implementing whole Vector3 serialization again, but just using Protocol.Serialize(vector3FieldFromMentionedClass).
As you can see, I'm not passing to that method any serialization context (stream in this case), but Vector3 serialization methods must get it in parameter - how it works? Where that stream comes from? Will it be the same stream instance, if I will call Protocol.Serialize(Vector3) three times?

jeanfabre
2017-07-25 12:09:16

Hi,

Sorry for the delay, had to be away from work.

Outside usage was meant to say if you are manually calling serialization, but if you have registered your types for serialization, then it falls into photon usage yes, as it will be Photon internals that will call for serializations on your custom class when required.

The Stream comes from Photon Internal handling of data coming in and out of photonViews.

Maybe you could provide a sample showing how you use your class and how you feed it to the photonView stream? then I'll look at it and be able to help some more.

Typically, we do need an equal statement because when we find that the content is the same we skip it and thus saves a lot of bandwidth, which cut down costs of hosting as well as improve networking and allow you to do more, if you create a custom class, you are also responsible for creating the right set of methods to take equal statements in consideration, you are doing this already right?

Bye,

Jean

Chernikov
2019-03-28 00:20:55

You still not updated that in documentation.

Neither

@JohnTube wrote:

Hi @johnny_tictoc,

Good question, we should add this information to the documentation.
Your answer can be found in the PUN source code, CustomTypes.cs class file:

internal static void Register()  
   {  
       PhotonPeer.RegisterType(typeof(Vector2), (byte)'W', SerializeVector2, DeserializeVector2);  
       PhotonPeer.RegisterType(typeof(Vector3), (byte)'V', SerializeVector3, DeserializeVector3);  
       PhotonPeer.RegisterType(typeof(Quaternion), (byte)'Q', SerializeQuaternion, DeserializeQuaternion);  
       PhotonPeer.RegisterType(typeof(PhotonPlayer), (byte)'P', SerializePhotonPlayer, DeserializePhotonPlayer);  
   }

So reserved codes for default registered types in PUN are:

  • W (23) for Vector2
  • V (22) for Vector3
  • Q (17) for Quaternion
  • P (16) for PhotonPlayer

nor

@JohnTube wrote:

Hi @johnny_tictoc,

Thank you for pointing that out.
The RegisterType methods signatures were changed lately but we forgot to update the examples.
We will fix the documentation.

JohnTube
2019-03-28 10:10:56

Hi @Chernikov,

Thank you for choosing Photon!

Are you sure you are browsing the correct link for PUN product?

Link to PUN2 docs page: "Serilization in Photon"

Note: This page may be still showing content for PUN1 while it's meant for PUN2, hence the disclaimer if it's still there when you are reading this.

We have added the list of registered types as well as the new methods to register types to that page a while ago.

The link in the original post is for Photon Realtime product:

Link to Photon Realtime docs page: "Serialization in Photon"

Each product has its own documentation page view.

Back to top