// read/write *primitive* types from an Astron stream (e.g.: uint16)
    // distinguished from a higher level type, such as structs, dclasses,
    // or uint16%360/100, all of which are made up of primitive types

    public object readPrimitive(DatagramIn dg, string type_n)
    {
        switch (type_n)
        {
        case "uint8":
            return(dg.ReadByte());

        case "uint16":
            return(dg.ReadUInt16());

        case "uint32":
            return(dg.ReadUInt32());

        case "uint64":
            return(dg.ReadUInt64());

        case "int8":
            return(dg.ReadSByte());

        case "int16":
            return(dg.ReadInt16());

        case "int32":
            return(dg.ReadInt32());

        case "int64":
            return(dg.ReadInt64());

        case "string":
            return(dg.ReadString());

        case "blob":
            return(dg.ReadBlob());

        case "float64":
            return(dg.ReadDouble());

        default:
            Debug.Log("Reading Error: Type '" + type_n + "' is not a primitive");
            return(null);
        }
    }
    public void unserializeClass(DatagramIn dg, SerializationLevel level, bool owner, bool optionals)
    {
        // since it's the same for all the messages, quickly unpack the header

        UInt32 do_id     = dg.ReadUInt32();
        UInt32 parent_id = dg.ReadUInt32();
        UInt32 zone_id   = dg.ReadUInt32();
        UInt16 dclass_id = dg.ReadUInt16();

        string class_n = DCFile.DCRoot[dclass_id];

        // when unpacking a class, there are two phases:
        // 1) unpacking the required fields (required modifiers are defined by level)
        // and 2) unpacking optional fields

        string r_class_n = owner ? class_n + "OV" : class_n;

        Type t = Type.GetType(r_class_n);

        IDistributedObject distObj;

        if (t.IsSubclassOf(typeof(DistributedUnityObject)))
        {
            Debug.Log("DistributedUnityObject instantiated of type " + t);

            GameObject prefab;

            try {
                prefab = prefabs[r_class_n];
            } catch (Exception e) {
                Debug.LogException(e);
                return;
            }

            GameObject gameObject = UnityEngine.Object.Instantiate(prefab) as GameObject;

            distObj = gameObject.GetComponent <MonoBehaviour>() as IDistributedObject;
        }
        else
        {
            Debug.Log("DistributedObject instantiated of type " + t);

            distObj = Activator.CreateInstance(t) as IDistributedObject;
        }

        distObj.setCR(this);

        // give it some context

        distObj.setDoID(do_id);
        distObj.setLocation(new Location(zone_id, parent_id));

        // to unpack required fields, first get a list of all fields

        UInt16[] field_list = DCFile.classLookup[class_n];


        // next, iterate through the fields to find required fields

        for (int i = 0; i < field_list.Length; ++i)
        {
            string[] modifiers = DCFile.fieldModifierLookup[field_list[i]];

            if (Array.IndexOf(modifiers, "required") > -1)
            {
                if (level == SerializationLevel.REQUIRED)
                {
                    // go ahead, receive the update
                    receiveUpdate(dg, distObj, field_list[i]);
                }
                else if (level == SerializationLevel.REQUIRED_BCAST)
                {
                    // only if it contains broadcast
                    if (Array.IndexOf(modifiers, "broadcast") > -1)
                    {
                        receiveUpdate(dg, distObj, field_list[i]);
                    }
                }
                else if (level == SerializationLevel.REQUIRED_BCAST_OR_OWNRECV)
                {
                    // it either needs to contain broadcast OR ownrecv
                    if ((Array.IndexOf(modifiers, "broadcast") > -1) || (Array.IndexOf(modifiers, "ownrecv") > -1))
                    {
                        receiveUpdate(dg, distObj, field_list[i]);
                    }
                }
            }
        }

        // without optionals, we'd be done
        // however, unpacking optionals is significantly easier
        // because we don't care about modifiers.
        // assume the server is sending fields we understand

        if (optionals)
        {
            UInt16 numOptionals = dg.ReadUInt16();

            for (int o = 0; o < numOptionals; ++o)
            {
                receiveUpdate(dg, distObj, dg.ReadUInt16());
            }
        }

        if (owner)
        {
            ovId2ov.Add(do_id, distObj);
        }
        else
        {
            doId2do.Add(do_id, distObj);
        }
    }
    private void onData(MemoryStream data)
    {
        DatagramIn reader = new DatagramIn(data);

        UInt16 type = reader.ReadUInt16();

        switch ((MessageTypes)type)
        {
        case MessageTypes.CLIENT_HELLO_RESP:
        {
            Debug.Log("Response to client_hello");

            if (onHello != null)
            {
                onHello();
            }
            break;
        }

        case MessageTypes.CLIENT_EJECT:
        {
            UInt16 error_code = reader.ReadUInt16();
            string reason     = reader.ReadString();

            Debug.Log("Ejected Code " + error_code + ": " + reason);

            if (onEject != null)
            {
                onEject(error_code, reason);
            }
            break;
        }

        case MessageTypes.CLIENT_ADD_INTEREST:
        {
            UInt32 context     = reader.ReadUInt32();
            UInt16 interest_id = reader.ReadUInt16();
            UInt32 parent_id   = reader.ReadUInt32();
            UInt32 zone_id     = reader.ReadUInt32();

            Interest newInterest = new Interest(context, interest_id, zone_id, parent_id);
            context2interest.Add(context, newInterest);

            if (onAddInterest != null)
            {
                onAddInterest(newInterest);
            }

            break;
        }

        case MessageTypes.CLIENT_DONE_INTEREST_RESP:
        {
            UInt32 context     = reader.ReadUInt32();
            UInt16 interest_id = reader.ReadUInt16();

            if (onDoneInterest != null)
            {
                onDoneInterest(context2interest[context]);
            }

            break;
        }

        case MessageTypes.CLIENT_ENTER_OBJECT_REQUIRED:
        {
            unserializeClass(reader, SerializationLevel.REQUIRED_BCAST, false, false);
            break;
        }

        case MessageTypes.CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
        {
            unserializeClass(reader, SerializationLevel.REQUIRED_BCAST, false, true);
            break;
        }

        case MessageTypes.CLIENT_ENTER_OBJECT_REQUIRED_OWNER:
        {
            unserializeClass(reader, SerializationLevel.REQUIRED_BCAST_OR_OWNRECV, true, false);
            break;
        }

        case MessageTypes.CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER:
        {
            unserializeClass(reader, SerializationLevel.REQUIRED_BCAST_OR_OWNRECV, true, true);
            break;
        }

        case MessageTypes.CLIENT_OBJECT_LOCATION:
        {
            UInt32 do_id     = reader.ReadUInt32();
            UInt32 parent_id = reader.ReadUInt32();
            UInt32 zone_id   = reader.ReadUInt32();

            doId2do[do_id].getLocation().changeLocation(zone_id, parent_id);
            doId2do[do_id].locationChanged();
            break;
        }

        case MessageTypes.CLIENT_OBJECT_LEAVING:
        {
            UInt32 doId = reader.ReadUInt32();
            doId2do[doId].leaving();

            // freeing the DO from the doId2do map is done by the leaving method
            // via the removeDOfromMap function

            // if the leaving method is overriden,
            // removeDOfromMap should still be called to prevent memory leaks
            break;
        }

        case MessageTypes.CLIENT_OBJECT_SET_FIELD:
        {
            UInt32 doId     = reader.ReadUInt32();
            UInt16 field_id = reader.ReadUInt16();

            receiveUpdate(reader, doId2do[doId], field_id);
            break;
        }

        case MessageTypes.CLIENT_OBJECT_SET_FIELDS:
        {
            UInt32 doId = reader.ReadUInt32();

            IDistributedObject distObj = doId2do[doId];

            UInt16 num_fields = reader.ReadUInt16();
            for (int i = 0; i < num_fields; ++i)
            {
                UInt16 field_id = reader.ReadUInt16();
                receiveUpdate(reader, distObj, field_id);
            }
            break;
        }

        default:
        {
            Debug.Log("Unknown message type: " + type);
            break;
        }
        }
    }