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.
create(). If called as a method,thisshould exist, and should indeed point to the class and have a.prototype. Can you edit your question to include a minimal reproducible example please?Object.assign(this.prototype, args), even if thethisis working, is still wrong. That puts the JSON data on the prototype of all your instances! Andobject === this.prototype. What you actually want isObject.assign(Object.create(this.prototype), args)or (if all your entity constructors support this signature)Object.assign(new this(), args).createmethod of an abstractEntityclass could need aregistryNameargument. Can you show a complete code example of an entity roundtrip (instance -> json -> instance)?this.entities[registryName](registryName, args);does call thecreatefunction with thethiskeyword bound tothis.entities. You'll want to dothis.reg(EntityRegistry.ENTITY_X_WING,XWingEntity);and thenthis.entities[registryName].create(registryName, args);.