/// <summary> /// Determine what type of <see cref="Schema" /> this is /// </summary> /// <param name="bytes">Incoming data</param> /// <param name="it"> /// The <see cref="Iterator" /> used to <see cref="ColyseusDecoder.DecodeNumber" /> the <paramref name="bytes" /> /// </param> /// <param name="defaultType"> /// The default <see cref="Schema" /> type, if one cant be determined from the /// <paramref name="bytes" /> /// </param> /// <returns>The parsed <see cref="System.Type" /> if found, <paramref name="defaultType" /> if not</returns> protected System.Type GetSchemaType(byte[] bytes, Iterator it, System.Type defaultType) { System.Type type = defaultType; if (it.Offset < bytes.Length && bytes[it.Offset] == (byte)SPEC.TYPE_ID) { it.Offset++; int typeId = Convert.ToInt32(ColyseusDecoder.GetInstance().DecodeNumber(bytes, it)); type = ColyseusContext.GetInstance().Get(typeId); } return(type); }
/// <summary> /// Decode incoming data /// </summary> /// <param name="bytes">The incoming data</param> /// <param name="it"><see cref="Iterator" /> used to decode. If null, will create a new one</param> /// <param name="refs"> /// <see cref="ColyseusReferenceTracker" /> for all refs found through the decoding process. If null, will /// create a new one /// </param> /// <exception cref="Exception">If no decoding fails</exception> public void Decode(byte[] bytes, Iterator it = null, ColyseusReferenceTracker refs = null) { ColyseusDecoder decode = ColyseusDecoder.GetInstance(); if (it == null) { it = new Iterator(); } if (refs == null) { refs = new ColyseusReferenceTracker(); } int totalBytes = bytes.Length; int refId = 0; IRef _ref = this; this.refs = refs; refs.Add(refId, _ref); List <DataChange> changes = new List <DataChange>(); OrderedDictionary allChanges = new OrderedDictionary(); // Dictionary<int, List<DataChange>> allChanges.Add(refId, changes); while (it.Offset < totalBytes) { byte _byte = bytes[it.Offset++]; if (_byte == (byte)SPEC.SWITCH_TO_STRUCTURE) { refId = Convert.ToInt32(decode.DecodeNumber(bytes, it)); _ref = refs.Get(refId); // // Trying to access a reference that haven't been decoded yet. // if (_ref == null) { throw new Exception("refId not found: " + refId); } // create empty list of changes for this refId. changes = new List <DataChange>(); allChanges[(object)refId] = changes; continue; } bool isSchema = _ref is Schema; byte operation = (byte)(isSchema ? (_byte >> 6) << 6 // "compressed" index + operation : _byte); // "uncompressed" index + operation (array/map items) if (operation == (byte)OPERATION.CLEAR) { ((ISchemaCollection)_ref).Clear(refs); continue; } int fieldIndex; string fieldName = null; string fieldType = null; System.Type childType = null; if (isSchema) { fieldIndex = _byte % (operation == 0 ? 255 : operation); // FIXME: JS allows (0 || 255) ((Schema)_ref).fieldsByIndex.TryGetValue(fieldIndex, out fieldName); // fieldType = ((Schema)_ref).fieldTypes[fieldName]; ((Schema)_ref).fieldTypes.TryGetValue(fieldName ?? "", out fieldType); ((Schema)_ref).fieldChildTypes.TryGetValue(fieldName ?? "", out childType); } else { fieldName = ""; // FIXME fieldIndex = Convert.ToInt32(decode.DecodeNumber(bytes, it)); if (((ISchemaCollection)_ref).HasSchemaChild) { fieldType = "ref"; childType = ((ISchemaCollection)_ref).GetChildType(); } else { fieldType = ((ISchemaCollection)_ref).ChildPrimitiveType; } } object value = null; object previousValue = null; object dynamicIndex = null; if (!isSchema) { previousValue = _ref.GetByIndex(fieldIndex); if ((operation & (byte)OPERATION.ADD) == (byte)OPERATION.ADD) { // MapSchema dynamic index. dynamicIndex = ((ISchemaCollection)_ref).GetItems() is OrderedDictionary ? (object)decode.DecodeString(bytes, it) : fieldIndex; ((ISchemaCollection)_ref).SetIndex(fieldIndex, dynamicIndex); } else { dynamicIndex = ((ISchemaCollection)_ref).GetIndex(fieldIndex); } } else if (fieldName != null) // FIXME: duplicate check { previousValue = ((Schema)_ref)[fieldName]; } // // Delete operations // if ((operation & (byte)OPERATION.DELETE) == (byte)OPERATION.DELETE) { if (operation != (byte)OPERATION.DELETE_AND_ADD) { _ref.DeleteByIndex(fieldIndex); } // Flag `refId` for garbage collection. if (previousValue != null && previousValue is IRef) { refs.Remove(((IRef)previousValue).__refId); } value = null; } if (fieldName == null) { // // keep skipping next bytes until reaches a known structure // by local decoder. // Iterator nextIterator = new Iterator { Offset = it.Offset }; while (it.Offset < totalBytes) { if (decode.SwitchStructureCheck(bytes, it)) { nextIterator.Offset = it.Offset + 1; if (refs.Has(Convert.ToInt32(decode.DecodeNumber(bytes, nextIterator)))) { break; } } it.Offset++; } continue; } if (operation == (byte)OPERATION.DELETE) { // // FIXME: refactor me. // Don't do anything. // } else if (fieldType == "ref") { refId = Convert.ToInt32(decode.DecodeNumber(bytes, it)); value = refs.Get(refId); if (operation != (byte)OPERATION.REPLACE) { System.Type concreteChildType = GetSchemaType(bytes, it, childType); if (value == null) { value = CreateTypeInstance(concreteChildType); if (previousValue != null) { ((Schema)value).MoveEventHandlers((Schema)previousValue); if ( ((IRef)previousValue).__refId > 0 && refId != ((IRef)previousValue).__refId ) { refs.Remove(((IRef)previousValue).__refId); } } } refs.Add(refId, (IRef)value, value != previousValue); } } else if (childType == null) { // primitive values value = decode.DecodePrimitiveType(fieldType, bytes, it); } else { refId = Convert.ToInt32(decode.DecodeNumber(bytes, it)); value = refs.Get(refId); ISchemaCollection valueRef = refs.Has(refId) ? (ISchemaCollection)previousValue : (ISchemaCollection)Activator.CreateInstance(childType); value = valueRef.Clone(); // keep reference to nested childPrimitiveType. string childPrimitiveType; ((Schema)_ref).fieldChildPrimitiveTypes.TryGetValue(fieldName, out childPrimitiveType); ((ISchemaCollection)value).ChildPrimitiveType = childPrimitiveType; if (previousValue != null) { ((ISchemaCollection)value).MoveEventHandlers( (ISchemaCollection)previousValue); if ( ((IRef)previousValue).__refId > 0 && refId != ((IRef)previousValue).__refId ) { refs.Remove(((IRef)previousValue).__refId); List <DataChange> deletes = new List <DataChange>(); IDictionary items = ((ISchemaCollection)previousValue).GetItems(); foreach (object key in items.Keys) { deletes.Add(new DataChange { DynamicIndex = key, Op = (byte)OPERATION.DELETE, Value = null, PreviousValue = items[key] }); } allChanges[(object)((IRef)previousValue).__refId] = deletes; } } refs.Add(refId, (IRef)value, valueRef != previousValue); } bool hasChange = previousValue != value; if (value != null) { if (value is IRef) { ((IRef)value).__refId = refId; } if (_ref is Schema) { ((Schema)_ref)[fieldName] = value; } else if (_ref is ISchemaCollection) { ((ISchemaCollection)_ref).SetByIndex(fieldIndex, dynamicIndex, value); } } if (hasChange) { changes.Add(new DataChange { Op = operation, Field = fieldName, DynamicIndex = dynamicIndex, Value = value, PreviousValue = previousValue }); } } TriggerChanges(ref allChanges); refs.GarbageCollection(); }