0

I'm working on a multiplayer game and need to serialize and deserialize entities while sending client <-> server. To do it, I'm just sending the object as JSON, then parsing it and assigning to an object's instance got by registry name. In the registry I put to array string with callback to create object:

static create(registryName: string, args: {}) {
   const object = Object.assign(this.prototype,args);
   object.registryName = registryName;
   return object;
}

while args is my deserialized object, and the whole method is in my abstract entity class that all entities extend.
So what I want to get here is certain (defined in the registry) entity instance. However, when running the code, it throws me an exception, that this.prototype is undefined:

const object = Object.assign(this.prototype,args);
                      ^
TypeError: Cannot convert undefined or null to object
    at Function.assign (<anonymous>)
    at Array.IGameEntity.create [as x_wing] 

And now, is there any other method that would allow me to do that? I guess current prototype does not exists because it's static method (however PhpStorm says it is valid). Of course I could just put that method in every entity class and put the class name instead of this, but I'm curious if the current way could work somehow.

Code to reproduce:

export namespace Test{
    export abstract class IGameEntity{
        registryName: string;
        readonly id: number;
        vec: {
            x: number;
            y: number;
        };
        rotation: number = 0;
        scale: number = 1;
        
        static ids = 0;

        protected constructor(vec: {x: number, y: number}, rotation: number = 0, scale: number = 1) {
            this.id = IGameEntity.ids++;
            this.vec = vec;
            this.rotation = rotation;
            this.scale = scale;
        }
        
        abstract tick(time: number);
        
        static create(registryName: string, args: {}) {
            const object = Object.assign(this.prototype,args);
            object.registryName = registryName;
            return object;
        }
    }
    
    export class XWingEntity extends IGameEntity{
        tick(time: number) {
            //something here...
        }
    }
    
    export class EntityRegistry {
        static instance: EntityRegistry;
        entities: Array<(registryName: string, data: {}) => IGameEntity> = [];

        static readonly ENTITY_X_WING: string = "x_wing";

        constructor() {
            EntityRegistry.instance = this;

            this.register();
        }

        register(){
            this.reg(EntityRegistry.ENTITY_X_WING,XWingEntity.create);
        }

        private reg(registryName: string, caller: (registryName: string, args: {}) => Object) {
            this.entities[registryName] = caller;
        }

        create(registryName: string, args: {}): IGameEntity | null {
            if(this.entities[registryName]){
                return this.entities[registryName](registryName, args);
            }
            return null;
        }
    }
}

And now sending:

    //server code
    const registry = new EntityRegistry();
    let entityServer = registry.create(EntityRegistry.ENTITY_X_WING, {x: 5, y: 3})
    //add to world, etc

    //then send
    let json = JSON.stringify(entityServer);
    //send json
    
    //client code
    //get json
    let object = JSON.parse(json);
    let entityClient = registry.create(object['registryName'],object);

Thanks for any help.

8
  • I'm not sure I see the point of assigning to the prototype to begin with. If you're it makes a lot more sense to just send simple DTO payloads, as they are a lot easier to serialise/deserialise. And if you happen to already be serialising/deserialising into JSON, then you cannot have anything worth assigning to a prototype - it would all be plain values or arrays/objects. Even then, assigning to the prototype means you are trying to affect all instances. Whereas your static method seems to be a simple factory, which should just be creating instances from data. Commented Nov 19, 2021 at 21:48
  • Please show us how you are calling create(). If called as a method, this should exist, and should indeed point to the class and have a .prototype. Can you edit your question to include a minimal reproducible example please? Commented Nov 19, 2021 at 21:58
  • 1
    Object.assign(this.prototype, args), even if the this is working, is still wrong. That puts the JSON data on the prototype of all your instances! And object === this.prototype. What you actually want is Object.assign(Object.create(this.prototype), args) or (if all your entity constructors support this signature) Object.assign(new this(), args). Commented Nov 19, 2021 at 22:00
  • 1
    What do you mean by "object's instance got by registry name"? I can't see how the create method of an abstract Entity class could need a registryName argument. Can you show a complete code example of an entity roundtrip (instance -> json -> instance)? Commented Nov 19, 2021 at 22:02
  • 1
    Ah, there's your problem, thanks! this.entities[registryName](registryName, args); does call the create function with the this keyword bound to this.entities. You'll want to do this.reg(EntityRegistry.ENTITY_X_WING,XWingEntity); and then this.entities[registryName].create(registryName, args);. Commented Nov 20, 2021 at 12:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.