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 + "'"); } } }