Example #1
0
        public void Decode(byte[] bytes, Iterator it = null)
        {
            var decode = Decoder.GetInstance();

            if (it == null)
            {
                it = new Iterator();
            }

            var changes    = new List <DataChange>();
            var totalBytes = bytes.Length;

            // skip TYPE_ID of existing instances
            if (bytes[it.Offset] == (byte)SPEC.TYPE_ID)
            {
                it.Offset += 2;
            }

            while (it.Offset < totalBytes)
            {
                var index = bytes[it.Offset++];

                if (index == (byte)SPEC.END_OF_STRUCTURE)
                {
                    break;
                }

                // Schema version mismatch (backwards compatibility)
                if (!fieldsByIndex.ContainsKey(index))
                {
                    continue;
                }

                var field     = fieldsByIndex[index];
                var fieldType = fieldTypes[field];

                System.Type childType;
                fieldChildTypes.TryGetValue(field, out childType);

                string childPrimitiveType;
                fieldChildPrimitiveTypes.TryGetValue(field, out childPrimitiveType);

                object value = null;

                object change    = null;
                bool   hasChange = false;

                if (fieldType == "ref")
                {
                    // child schema type
                    if (decode.NilCheck(bytes, it))
                    {
                        it.Offset++;
                        value = null;
                    }
                    else
                    {
                        value = this[field] ?? CreateTypeInstance(bytes, it, childType);
                        (value as Schema).Decode(bytes, it);
                    }

                    hasChange = true;
                }

                // Array type
                else if (fieldType == "array")
                {
                    change = new List <object>();

                    ISchemaCollection valueRef     = (ISchemaCollection)(this[field] ?? Activator.CreateInstance(childType));
                    ISchemaCollection currentValue = valueRef.Clone();

                    int newLength  = Convert.ToInt32(decode.DecodeNumber(bytes, it));
                    int numChanges = Math.Min(Convert.ToInt32(decode.DecodeNumber(bytes, it)), newLength);

                    hasChange = (numChanges > 0);

                    bool hasIndexChange = false;

                    // ensure current array has the same length as encoded one
                    if (currentValue.Count > newLength)
                    {
                        IDictionary items = currentValue.GetItems();

                        for (int i = newLength, l = currentValue.Count; i < l; i++)
                        {
                            var item = currentValue[i];
                            if (item is Schema)
                            {
                                (item as Schema).OnRemove?.Invoke();
                            }

                            items.Remove(i);
                            currentValue.InvokeOnRemove(item, i);
                        }
                    }

                    for (var i = 0; i < numChanges; i++)
                    {
                        var newIndex = Convert.ToInt32(decode.DecodeNumber(bytes, it));

                        int indexChangedFrom = -1;
                        if (decode.IndexChangeCheck(bytes, it))
                        {
                            decode.DecodeUint8(bytes, it);
                            indexChangedFrom = Convert.ToInt32(decode.DecodeNumber(bytes, it));
                            hasIndexChange   = true;
                        }

                        var isNew = (!hasIndexChange && !currentValue.ContainsKey(newIndex)) || (hasIndexChange && indexChangedFrom != -1);

                        if (currentValue.HasSchemaChild)
                        {
                            Schema item = null;

                            if (isNew)
                            {
                                item = (Schema)CreateTypeInstance(bytes, it, currentValue.GetChildType());
                            }
                            else if (indexChangedFrom != -1)
                            {
                                item = (Schema)valueRef[indexChangedFrom];
                            }
                            else
                            {
                                item = (Schema)valueRef[newIndex];
                            }

                            if (item == null)
                            {
                                item  = (Schema)CreateTypeInstance(bytes, it, currentValue.GetChildType());
                                isNew = true;
                            }

                            if (decode.NilCheck(bytes, it))
                            {
                                it.Offset++;
                                valueRef.InvokeOnRemove(item, newIndex);
                                continue;
                            }

                            item.Decode(bytes, it);
                            currentValue[newIndex] = item;
                        }
                        else
                        {
                            currentValue[newIndex] = decode.DecodePrimitiveType(childPrimitiveType, bytes, it);
                        }

                        if (isNew)
                        {
                            currentValue.InvokeOnAdd(currentValue[newIndex], newIndex);
                        }
                        else
                        {
                            currentValue.InvokeOnChange(currentValue[newIndex], newIndex);
                        }

                        (change as List <object>).Add(currentValue[newIndex]);
                    }

                    value = currentValue;
                }

                // Map type
                else if (fieldType == "map")
                {
                    ISchemaCollection valueRef     = (ISchemaCollection)(this[field] ?? Activator.CreateInstance(childType));
                    ISchemaCollection currentValue = valueRef.Clone();

                    int length = Convert.ToInt32(decode.DecodeNumber(bytes, it));
                    hasChange = (length > 0);

                    bool hasIndexChange = false;

                    OrderedDictionary items   = currentValue.GetItems() as OrderedDictionary;
                    string[]          mapKeys = new string[items.Keys.Count];
                    items.Keys.CopyTo(mapKeys, 0);

                    for (var i = 0; i < length; i++)
                    {
                        // `encodeAll` may indicate a higher number of indexes it actually encodes
                        // TODO: do not encode a higher number than actual encoded entries
                        if (it.Offset > bytes.Length || bytes[it.Offset] == (byte)SPEC.END_OF_STRUCTURE)
                        {
                            break;
                        }

                        string previousKey = null;
                        if (decode.IndexChangeCheck(bytes, it))
                        {
                            it.Offset++;
                            previousKey    = mapKeys[Convert.ToInt32(decode.DecodeNumber(bytes, it))];
                            hasIndexChange = true;
                        }

                        bool hasMapIndex  = decode.NumberCheck(bytes, it);
                        bool isSchemaType = childType != null;

                        string newKey = (hasMapIndex)
                ? mapKeys[Convert.ToInt32(decode.DecodeNumber(bytes, it))]
                : decode.DecodeString(bytes, it);

                        object item;
                        bool   isNew = (!hasIndexChange && !valueRef.ContainsKey(newKey)) || (hasIndexChange && previousKey == null && hasMapIndex);

                        if (isNew && isSchemaType)
                        {
                            item = (Schema)CreateTypeInstance(bytes, it, currentValue.GetChildType());
                        }
                        else if (previousKey != null)
                        {
                            item = valueRef[previousKey];
                        }
                        else
                        {
                            item = valueRef[newKey];
                        }

                        if (decode.NilCheck(bytes, it))
                        {
                            it.Offset++;

                            if (item != null && isSchemaType)
                            {
                                (item as Schema).OnRemove?.Invoke();
                            }

                            valueRef.InvokeOnRemove(item, newKey);
                            items.Remove(newKey);
                            continue;
                        }
                        else if (!isSchemaType)
                        {
                            currentValue[newKey] = decode.DecodePrimitiveType(childPrimitiveType, bytes, it);
                        }
                        else
                        {
                            (item as Schema).Decode(bytes, it);
                            currentValue[newKey] = item;
                        }

                        if (isNew)
                        {
                            currentValue.InvokeOnAdd(currentValue[newKey], newKey);
                        }
                        else
                        {
                            currentValue.InvokeOnChange(currentValue[newKey], newKey);
                        }
                    }

                    value = currentValue;
                }

                // Primitive type
                else
                {
                    value     = decode.DecodePrimitiveType(fieldType, bytes, it);
                    hasChange = true;
                }

                if (hasChange)
                {
                    changes.Add(new DataChange
                    {
                        Field         = field,
                        Value         = (change != null) ? change : value,
                        PreviousValue = this[field]
                    });
                }

                this[field] = value;
            }

            if (changes.Count > 0)
            {
                OnChange?.Invoke(changes);
            }
        }
        /// <summary>
        ///     The function that will be called when the <see cref="colyseusConnection" /> receives a message
        /// </summary>
        /// <param name="bytes">The message as provided from the <see cref="colyseusConnection" /></param>
        protected async void ParseMessage(byte[] bytes)
        {
            byte code = bytes[0];

            if (code == ColyseusProtocol.JOIN_ROOM)
            {
                int offset = 1;

                SerializerId = Encoding.UTF8.GetString(bytes, offset + 1, bytes[offset]);
                offset      += SerializerId.Length + 1;

                if (SerializerId == "schema")
                {
                    try
                    {
                        serializer = new ColyseusSchemaSerializer <T>();
                    }
                    catch (Exception e)
                    {
                        DisplaySerializerErrorHelp(e,
                                                   "Consider using the \"schema-codegen\" and providing the same room state for matchmaking instead of \"" +
                                                   typeof(T).Name + "\"");
                    }
                }
                else if (SerializerId == "fossil-delta")
                {
                    Debug.LogError(
                        "FossilDelta Serialization has been deprecated. It is highly recommended that you update your code to use the Schema Serializer. Otherwise, you must use an earlier version of the Colyseus plugin");
                }
                else
                {
                    try
                    {
                        serializer = (IColyseusSerializer <T>) new ColyseusNoneSerializer();
                    }
                    catch (Exception e)
                    {
                        DisplaySerializerErrorHelp(e,
                                                   "Consider setting state in the server-side using \"this.setState(new " + typeof(T).Name +
                                                   "())\"");
                    }
                }

                if (bytes.Length > offset)
                {
                    serializer.Handshake(bytes, offset);
                }

                OnJoin?.Invoke();

                // Acknowledge JOIN_ROOM
                await colyseusConnection.Send(new[] { ColyseusProtocol.JOIN_ROOM });
            }
            else if (code == ColyseusProtocol.ERROR)
            {
                Iterator it = new Iterator {
                    Offset = 1
                };
                float  errorCode    = Decode.DecodeNumber(bytes, it);
                string errorMessage = Decode.DecodeString(bytes, it);
                OnError?.Invoke((int)errorCode, errorMessage);
            }
            else if (code == ColyseusProtocol.ROOM_DATA_SCHEMA)
            {
                Iterator it = new Iterator {
                    Offset = 1
                };
                float typeId = Decode.DecodeNumber(bytes, it);

                Type          messageType = ColyseusContext.GetInstance().Get(typeId);
                Schema.Schema message     = (Schema.Schema)Activator.CreateInstance(messageType);

                message.Decode(bytes, it);

                IColyseusMessageHandler handler = null;
                OnMessageHandlers.TryGetValue("s" + message.GetType(), out handler);

                if (handler != null)
                {
                    handler.Invoke(message);
                }
                else
                {
                    Debug.LogWarning("room.OnMessage not registered for Schema of type: '" + message.GetType() + "'");
                }
            }
            else if (code == ColyseusProtocol.LEAVE_ROOM)
            {
                await Leave();
            }
            else if (code == ColyseusProtocol.ROOM_STATE)
            {
                Debug.Log("ROOM_STATE");
                SetState(bytes, 1);
            }
            else if (code == ColyseusProtocol.ROOM_STATE_PATCH)
            {
                Patch(bytes, 1);
            }
            else if (code == ColyseusProtocol.ROOM_DATA)
            {
                IColyseusMessageHandler handler = null;
                object type;

                Iterator it = new Iterator {
                    Offset = 1
                };

                if (Decode.NumberCheck(bytes, it))
                {
                    type = Decode.DecodeNumber(bytes, it);
                    OnMessageHandlers.TryGetValue("i" + type, out handler);
                }
                else
                {
                    type = Decode.DecodeString(bytes, it);
                    OnMessageHandlers.TryGetValue(type.ToString(), out handler);
                }

                if (handler != null)
                {
                    //
                    // MsgPack deserialization can be optimized:
                    // https://github.com/deniszykov/msgpack-unity3d/issues/23
                    //
                    object message = bytes.Length > it.Offset
                        ? MsgPack.Deserialize(handler.Type,
                                              new MemoryStream(bytes, it.Offset, bytes.Length - it.Offset, false))
                        : null;

                    handler.Invoke(message);
                }
                else
                {
                    Debug.LogWarning("room.OnMessage not registered for: '" + type + "'");
                }
            }
        }