// 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);
        }
    }
    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;
        }
        }
    }