PUN Equivalent for UNet SyncList

I've written my own List container backed by a resizing array (just like C#'s List implementation) and added to it UNet's SendMsg scheme. I've written this to send the custom type via
if (Player.RoomReference != null)
{
    Hashtable properties = new Hashtable();
    properties[_propertyName] = this;
    Player.RoomReference.LoadBalancingClient.LoadBalancingPeer.OpSetPropertiesOfActor(Player.ActorNumber, properties);
}
The one change I made to Photon.Realtime.Player was to make the definition within Player.cs to be a partial class. Then I could implement my SyncList as an inner class definition within another part of the Player class implementation.
namespace Photon.Realtime
{
    public partial class Player
    {
        // some implementation details removed

        public sealed class SyncListInt : SyncList<int>
        {
            public SyncListInt(Player outer, string propertyName) : base(outer, propertyName)
            {
                RegisterType();
            }

            public SyncListInt(Player outer, int capacity, string propertyName) : base(outer, capacity, propertyName)
            {
                RegisterType();
            }

            public SyncListInt(Player outer, string propertyName, byte[] data) : base(outer, propertyName, data)
            {
                RegisterType();
            }

            protected override void RegisterType()
            {
                PhotonPeer.RegisterType(typeof(SyncListInt), (byte)'S', SerializeContents, DeserializeContents);
            }

            // byte structure:
            // [0 to 4]    : Player ID
            // [5]         : (X) size in bytes of the Property Name string
            // [6 to +X]   : Property Name string
            // [X+1 to +Y] : list contents, where Y = SyncListInt._itemSizeInBytes * list.Count = list.ContentByteSize
            public static byte[] SerializeContents(object customObject)
            {
                SyncListInt list = customObject as SyncListInt;
                if (list == null) throw new ArgumentException("Argument type is not compatible with this class type");

                int ID = list._outerPlayer.ActorNumber;
                byte[] bytes = new byte[list.ByteSize];
                int destOffset = 0;
                Protocol.Serialize(ID, bytes, ref destOffset);
                bytes[destOffset++] = list.PropertyNameByteSize;
                Buffer.BlockCopy(list.PropertyNameBytes, 0, bytes, destOffset, list.PropertyNameByteSize);
                Buffer.BlockCopy(list._items, 0, bytes, destOffset, list.ContentByteSize);

                return bytes;
            }

            public static object DeserializeContents(byte[] data)
            {
                return new SyncListInt(data);
            }
        }

        public abstract class SyncList<T> : IList<T>, IEquatable<SyncList<T>> where T : struct
        {
            // implementation details removed

            protected abstract void RegisterType();
            protected T[] _items;
            protected Player _outerPlayer;
            protected string _propertyName;

            public SyncList(Player outer, string propertyName) : this(outer, _defaultCapacity, propertyName)
            {
            }

            public SyncList(Player outer, int capacity, string propertyName)
            {
                if (outer == null) throw new ArgumentNullException();
                if (capacity < 0) throw new ArgumentOutOfRangeException("capacity", "can not be negative");

                _propertyName = propertyName;
                _propertyNameSizeInBytes = (byte)PropertyNameBytes.Length;
                _outerPlayer = outer;

                if (capacity == 0)
                    _items = _emptyArray;
                else
                    _items = new T[capacity];
            }

            // byte structure:
            // [0 to 4]     : Player ID
            // [5]          : (X) size in bytes of the Property Name string
            // [6 to +X]    : Property Name string
            // [X+1 to end] : list contents, remaining bytes comprise the items
            public SyncList(byte[] data)
            {
                if (data == null) throw new ArgumentNullException();

                int ID;
                int sourceOffset = 0;
                Protocol.Deserialize(out ID, data, ref sourceOffset);
                _outerPlayer = PhotonNetwork.CurrentRoom.GetPlayer(ID);

                _propertyNameSizeInBytes = data[sourceOffset++];
                _propertyName = Encoding.ASCII.GetString(data, sourceOffset, _propertyNameSizeInBytes);
                sourceOffset += (int)_propertyNameSizeInBytes;

                _size = data.Length - sourceOffset;
                _items = new T[_size];
                Buffer.BlockCopy(data, sourceOffset, _items, 0, _size);
            }
        }
    }
}
My question is is the mechanism I used for sending the data (ie the OpSetPropertiesOfActor call) the best option for me? I considered using a RPC but ran into a problem with the RPC's requiring a PhotonView. I also researched possibly using PhotonNetwork.RaiseEvent and implementing IOnEventCallback for event receipt, but I ran into a similar problem of not having a Component instance (since my implementation above is part of Player.cs, which is just a data class; not a Monobehaviour).
My objective is to have a solution that can be used somewhat similarly to how UNet's SyncList's work and is able to be used for Player data.
Any comments or guidance would be greatly appreciated.
Thanks.
-Kevin

Comments

  • In testing this, I'm getting an exception when it's about to Deserialize the data of remote events (it's not actually getting into my DeserializeContent method).
    Exception: Read failed. Custom type not found: 83
    ExitGames.Client.Photon.Protocol18.ReadCustomType (ExitGames.Client.Photon.StreamBuffer stream, Byte gpType) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:247)
    ExitGames.Client.Photon.Protocol18.Read (ExitGames.Client.Photon.StreamBuffer stream, Byte gpType) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:20)
    ExitGames.Client.Photon.Protocol18.Read (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:13)
    ExitGames.Client.Photon.Protocol18.ReadHashtable (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:307)
    ExitGames.Client.Photon.Protocol18.Read (ExitGames.Client.Photon.StreamBuffer stream, Byte gpType) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:82)
    ExitGames.Client.Photon.Protocol18.Read (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:13)
    ExitGames.Client.Photon.Protocol18.ReadHashtable (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:307)
    ExitGames.Client.Photon.Protocol18.Read (ExitGames.Client.Photon.StreamBuffer stream, Byte gpType) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:82)
    ExitGames.Client.Photon.Protocol18.ReadParameterTable (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:289)
    ExitGames.Client.Photon.Protocol18.DeserializeOperationResponse (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/Protocol18Read.cs:339)
    ExitGames.Client.Photon.PeerBase.DeserializeMessageAndCallback (ExitGames.Client.Photon.StreamBuffer stream) (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/PeerBase.cs:609)
    ExitGames.Client.Photon.EnetPeer.DispatchIncomingCommands () (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/EnetPeer.cs:550)
    ExitGames.Client.Photon.PhotonPeer.DispatchIncomingCommands () (at C:/Dev/photon-sdk-dotnet/PhotonDotnet/PhotonPeer.cs:1422)
    Photon.Pun.PhotonHandler.FixedUpdate () (at Assets/Photon/PhotonUnityNetworking/Code/PhotonHandler.cs:130)
    Furthermore, since we see it's complaining about Custom type 83 not found, it certainly has processed my PhotonPeer.RegisterType call. Is anyone able to give me a clue what's going wrong? I've also tried several different byte code values for my SyncListInt type, but exception is the same with just a new type value.

    Thanks.

    -Kevin
  • Hi,

    This is likely some over engineering here.

    Could you not just use the custom player properties, seems perfect for storing player data. have you tried this? you do get notified of changed player properties keys, so you can use player properties for synchronizing data indeed. Player custom properties are regular objects, so you can put anything in there.

    Let me know if that's not suitable

    Bye,

    Jean

  • I originally had this as just another custom property. However, I wanted to implement something that could be used just like UNet's SyncLists. It was not difficult to put together the details of C#'s List container and Unity's SyncList messaging. Furthermore, at the end of the day, these are being placed into the custom properties Hashtable.

    My original question, prior to getting the exception, I was inquiring about whether or not using Player.RoomReference.LoadBalancingClient.LoadBalancingPeer.OpSetPropertiesOfActor(this.ActorNumber, properties); to sync the data was ideal.
  • Hi,

    The ideal way to sync data at high rate with Photon is to use a PhotonView that observes a component which writes and read to the photonView Stream of data. That's how it was design with Photon.

    so, no, you should not use OpSetPropertiesOfActor.

    But, can you confirm you are using PUN and not the realtime SDK?

    Bye,

    Jean
  • All we have integrated is the PUN2 Plus unitypackage from the asset store.
  • ok.

    so, yeas, move to using photonView as your mean to sync data. Do you experience issues with your data in this setup too?

    Bye,

    Jean