Refactor serialization

This commit is contained in:
schmelczerandras 2020-10-07 10:04:05 +02:00
parent 8e44dd3733
commit b3da27e73b
35 changed files with 142 additions and 133 deletions

View file

@ -6,7 +6,7 @@ import {
settings, settings,
CommandExecutors, CommandExecutors,
MoveActionCommand, MoveActionCommand,
serializable, serializesTo,
} from 'shared'; } from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
@ -15,7 +15,7 @@ import { CirclePhysical } from './circle-physical';
import { Physical } from '../physics/physical'; import { Physical } from '../physics/physical';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
@serializable(CharacterBase) @serializesTo(CharacterBase)
export class CharacterPhysical extends CharacterBase implements Physical { export class CharacterPhysical extends CharacterBase implements Physical {
public readonly canCollide = true; public readonly canCollide = true;
public readonly isInverted = false; public readonly isInverted = false;

View file

@ -4,7 +4,7 @@ import {
clamp, clamp,
CommandExecutors, CommandExecutors,
GameObject, GameObject,
serializable, serializesTo,
settings, settings,
StepCommand, StepCommand,
} from 'shared'; } from 'shared';
@ -15,7 +15,7 @@ import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
import { moveCircle } from '../physics/move-circle'; import { moveCircle } from '../physics/move-circle';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
@serializable(Circle) @serializesTo(Circle)
export class CirclePhysical implements Circle, Physical { export class CirclePhysical implements Circle, Physical {
readonly isInverted = false; readonly isInverted = false;
readonly canCollide = true; readonly canCollide = true;

View file

@ -1,11 +1,11 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { LampBase, settings, id, serializable } from 'shared'; import { LampBase, settings, id, serializesTo } from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { Physical } from '../physics/physical'; import { Physical } from '../physics/physical';
@serializable(LampBase) @serializesTo(LampBase)
export class LampPhysical extends LampBase implements Physical { export class LampPhysical extends LampBase implements Physical {
public readonly canCollide = false; public readonly canCollide = false;
public readonly isInverted = false; public readonly isInverted = false;

View file

@ -1,10 +1,10 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { clamp01, mix, TunnelBase, id, serializable } from 'shared'; import { clamp01, mix, TunnelBase, id, serializesTo } from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { StaticPhysical } from '../physics/containers/static-physical-object'; import { StaticPhysical } from '../physics/containers/static-physical-object';
@serializable(TunnelBase) @serializesTo(TunnelBase)
export class TunnelPhysical extends TunnelBase implements StaticPhysical { export class TunnelPhysical extends TunnelBase implements StaticPhysical {
public readonly canCollide = true; public readonly canCollide = true;
public readonly isInverted = true; public readonly isInverted = true;

View file

@ -1,9 +1,17 @@
import { glMatrix } from 'gl-matrix'; import { glMatrix } from 'gl-matrix';
import { CharacterBase, LampBase, overrideDeserialization, TunnelBase } from 'shared';
import { Game } from './scripts/game'; import { Game } from './scripts/game';
import { CharacterView } from './scripts/objects/character-view';
import { LampView } from './scripts/objects/lamp-view';
import { TunnelView } from './scripts/objects/tunnel-view';
import './styles/main.scss'; import './styles/main.scss';
glMatrix.setMatrixArrayType(Array); glMatrix.setMatrixArrayType(Array);
overrideDeserialization(CharacterBase, CharacterView);
overrideDeserialization(TunnelBase, TunnelView);
overrideDeserialization(LampBase, LampView);
const main = async () => { const main = async () => {
try { try {
await new Game().start(); await new Game().start();

View file

@ -29,14 +29,10 @@ import { RenderCommand } from './commands/types/render';
import { Configuration } from './config/configuration'; import { Configuration } from './config/configuration';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { rgb } from './helper/rgb'; import { rgb } from './helper/rgb';
import { CharacterView } from './objects/character-view';
import { TunnelView } from './objects/tunnel-view';
import { LampView } from './objects/lamp-view';
import { GameObjectContainer } from './objects/game-object-container'; import { GameObjectContainer } from './objects/game-object-container';
import { BlobShape } from './shapes/blob-shape'; import { BlobShape } from './shapes/blob-shape';
const a = [CharacterView, TunnelView, LampView];
export class Game { export class Game {
public readonly gameObjects = new GameObjectContainer(); public readonly gameObjects = new GameObjectContainer();
private readonly canvas: HTMLCanvasElement = document.querySelector('canvas#main'); private readonly canvas: HTMLCanvasElement = document.querySelector('canvas#main');

View file

@ -1,9 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { CharacterBase, CommandExecutors, deserializable } from 'shared'; import { CharacterBase, CommandExecutors } from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
import { BlobShape } from '../shapes/blob-shape'; import { BlobShape } from '../shapes/blob-shape';
@deserializable(CharacterBase)
export class CharacterView extends CharacterBase { export class CharacterView extends CharacterBase {
private shape = new BlobShape(); private shape = new BlobShape();

View file

@ -1,9 +1,8 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { CircleLight } from 'sdf-2d'; import { CircleLight } from 'sdf-2d';
import { CommandExecutors, deserializable, Id, LampBase } from 'shared'; import { CommandExecutors, Id, LampBase } from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
@deserializable(LampBase)
export class LampView extends LampBase { export class LampView extends LampBase {
private light: CircleLight; private light: CircleLight;

View file

@ -1,9 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { InvertedTunnel } from 'sdf-2d'; import { InvertedTunnel } from 'sdf-2d';
import { CommandExecutors, deserializable, Id, TunnelBase } from 'shared'; import { CommandExecutors, Id, TunnelBase } from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
@deserializable(TunnelBase)
export class TunnelView extends TunnelBase { export class TunnelView extends TunnelBase {
private shape: InvertedTunnel; private shape: InvertedTunnel;

View file

@ -1,8 +1,8 @@
import { GameObject } from '../../objects/game-object'; import { GameObject } from '../../objects/game-object';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class CreateObjectsCommand extends Command { export class CreateObjectsCommand extends Command {
public constructor(public readonly objects: Array<GameObject>) { public constructor(public readonly objects: Array<GameObject>) {
super(); super();

View file

@ -1,8 +1,8 @@
import { CharacterBase } from '../../objects/types/character-base'; import { CharacterBase } from '../../objects/types/character-base';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class CreatePlayerCommand extends Command { export class CreatePlayerCommand extends Command {
public constructor(public readonly character: CharacterBase) { public constructor(public readonly character: CharacterBase) {
super(); super();

View file

@ -1,8 +1,8 @@
import { Id } from '../../transport/identity'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class DeleteObjectsCommand extends Command { export class DeleteObjectsCommand extends Command {
public constructor(public readonly ids: Array<Id>) { public constructor(public readonly ids: Array<Id>) {
super(); super();

View file

@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class MoveActionCommand extends Command { export class MoveActionCommand extends Command {
public constructor(public readonly delta: vec2) { public constructor(public readonly delta: vec2) {
super(); super();

View file

@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class PrimaryActionCommand extends Command { export class PrimaryActionCommand extends Command {
public constructor(public readonly position: vec2) { public constructor(public readonly position: vec2) {
super(); super();

View file

@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class SecondaryActionCommand extends Command { export class SecondaryActionCommand extends Command {
public constructor(public readonly position: vec2) { public constructor(public readonly position: vec2) {
super(); super();

View file

@ -1,8 +1,8 @@
import { Rectangle } from '../../helper/rectangle'; import { Rectangle } from '../../helper/rectangle';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class SetViewAreaActionCommand extends Command { export class SetViewAreaActionCommand extends Command {
public constructor(public readonly viewArea: Rectangle) { public constructor(public readonly viewArea: Rectangle) {
super(); super();

View file

@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class TernaryActionCommand extends Command { export class TernaryActionCommand extends Command {
public constructor(public readonly position: vec2) { public constructor(public readonly position: vec2) {
super(); super();

View file

@ -1,8 +1,8 @@
import { GameObject } from '../../objects/game-object'; import { GameObject } from '../../objects/game-object';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable() @serializable
export class UpdateObjectsCommand extends Command { export class UpdateObjectsCommand extends Command {
public constructor(public readonly objects: Array<GameObject>) { public constructor(public readonly objects: Array<GameObject>) {
super(); super();

View file

@ -1,7 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/serializable/serializable'; import { serializable } from '../transport/serialization/serializable';
@serializable() @serializable
export class Circle { export class Circle {
constructor(public center: vec2, public radius: number) { } constructor(public center: vec2, public radius: number) { }

View file

@ -1,7 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/serializable/serializable'; import { serializable } from '../transport/serialization/serializable';
@serializable() @serializable
export class Rectangle { export class Rectangle {
constructor(public topLeft = vec2.create(), public size = vec2.create()) { } constructor(public topLeft = vec2.create(), public size = vec2.create()) { }

View file

@ -22,9 +22,11 @@ export * from './helper/random';
export * from './helper/unique'; export * from './helper/unique';
export * from './helper/rotate-90-deg'; export * from './helper/rotate-90-deg';
export * from './objects/game-object'; export * from './objects/game-object';
export * from './transport/serializable/deserialize'; export * from './transport/serialization/deserialize';
export * from './transport/serializable/serialize'; export * from './transport/serialization/serialize';
export * from './transport/serializable/serializable'; export * from './transport/serialization/serializes-to';
export * from './transport/serialization/serializable';
export * from './transport/serialization/override-deserialization';
export * from './objects/types/character-base'; export * from './objects/types/character-base';
export * from './objects/types/lamp-base'; export * from './objects/types/lamp-base';
export * from './objects/types/tunnel-base'; export * from './objects/types/tunnel-base';

View file

@ -1,10 +1,10 @@
import { Circle } from '../../helper/circle'; import { Circle } from '../../helper/circle';
import { Id } from '../../transport/identity'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@serializable() @serializable
export abstract class CharacterBase extends GameObject { export class CharacterBase extends GameObject {
constructor( constructor(
id: Id, id: Id,
public head: Circle, public head: Circle,

View file

@ -2,8 +2,8 @@ import { vec2, vec3 } from 'gl-matrix';
import { Id, serializable } from '../../main'; import { Id, serializable } from '../../main';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@serializable() @serializable
export abstract class LampBase extends GameObject { export class LampBase extends GameObject {
constructor(id: Id, public center: vec2, public color: vec3, public lightness: number) { constructor(id: Id, public center: vec2, public color: vec3, public lightness: number) {
super(id); super(id);
} }

View file

@ -1,10 +1,10 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Id } from '../../transport/identity'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@serializable() @serializable
export abstract class TunnelBase extends GameObject { export class TunnelBase extends GameObject {
constructor( constructor(
id: Id, id: Id,
public readonly from: vec2, public readonly from: vec2,

View file

@ -1,13 +0,0 @@
import { serializableClasses } from './serializable';
export const deserialize = (json: string): any => {
return JSON.parse(json, (k, v) => {
const possibleType = v[0];
const overridableConstructor = serializableClasses.get(possibleType);
if (overridableConstructor) {
const [_, ...values] = v;
return new overridableConstructor.constructor(...values);
}
return v;
});
};

View file

@ -1,58 +0,0 @@
export const serializableClasses = new Map<
string,
{
constructor: any;
overriden: boolean;
}
>();
export const serializable = (serializableType?) => {
return (type): any => {
const actualType = serializableType
? Object.getPrototypeOf((serializableType as any).prototype).constructor
: type;
const typeName = actualType.name;
const overridableConstructor = serializableClasses.get(typeName);
if (!overridableConstructor) {
serializableClasses.set(typeName, {
constructor: actualType,
overriden: false,
});
}
if (!Object.prototype.hasOwnProperty.call(actualType.prototype, 'toArray')) {
throw new Error(
`Class ${typeName} must define a toArray returning an array containing the arguments for its constructor.`
);
}
return class extends type {
public get type(): string {
return typeName;
}
public static get type(): string {
return typeName;
}
};
};
};
export const deserializable = (fromType) => {
return (type): any => {
const overridableConstructor = serializableClasses.get(fromType.name);
if (!overridableConstructor || !overridableConstructor.overriden) {
serializableClasses.set(fromType.name, {
constructor: type,
overriden: true,
});
} else {
throw new Error(
`Constructor ${fromType} already overriden, cannot override again with ${type}`
);
}
return type;
};
};

View file

@ -1,10 +0,0 @@
export const serialize = (object): string => {
const result = JSON.stringify(object, (key, value) => {
if (value?.type) {
return [value.type, ...value.toArray()];
}
return value?.toFixed ? Number(value.toFixed(3)) : value;
});
return result;
};

View file

@ -0,0 +1 @@
export type DeserializableClass = { new(...args: Array<any>): {}; name: string };

View file

@ -0,0 +1,13 @@
import { serializableMapping } from './serializable-mapping';
export const deserialize = (json: string): any => {
return JSON.parse(json, (k, v) => {
const possibleType = v[0];
const overridableConstructor = serializableMapping.get(possibleType);
if (overridableConstructor) {
v.shift();
return new overridableConstructor.constructor(...v);
}
return v;
});
};

View file

@ -0,0 +1,13 @@
import { DeserializableClass } from './deserializable-class';
import { SerializableClass } from './serializable-class';
import { serializableMapping } from './serializable-mapping';
export const overrideDeserialization = (
source: SerializableClass,
target: DeserializableClass
) => {
serializableMapping.set(source.name, {
constructor: target,
overriden: true,
});
};

View file

@ -0,0 +1,4 @@
export type SerializableClass = {
new(...args: Array<any>): { toArray(): Array<any> };
name: string;
};

View file

@ -0,0 +1,12 @@
import { DeserializableClass } from './deserializable-class';
/**
* @internal
*/
export const serializableMapping = new Map<
string,
{
constructor: DeserializableClass;
overriden: boolean;
}
>();

View file

@ -0,0 +1,16 @@
import { SerializableClass } from './serializable-class';
import { serializableMapping } from './serializable-mapping';
export const serializable = (type: SerializableClass): any => {
if (!serializableMapping.get(type.name)) {
serializableMapping.set(type.name, {
constructor: type,
overriden: false,
});
}
Object.defineProperty(type, '__serializable_type', { value: type.name });
Object.defineProperty(type.prototype, '__serializable_type', { value: type.name });
return type;
};

View file

@ -0,0 +1,8 @@
export const serialize = (object): string => {
return JSON.stringify(object, (_, value) => {
if (value?.__serializable_type) {
return [value.__serializable_type, ...value.toArray()];
}
return value?.toFixed ? Number(value.toFixed(3)) : value;
});
};

View file

@ -0,0 +1,20 @@
import { SerializableClass } from './serializable-class';
import { serializableMapping } from './serializable-mapping';
export const serializesTo = (target: SerializableClass) => {
return (actual: SerializableClass): any => {
if (!serializableMapping.get(target.name)) {
serializableMapping.set(target.name, {
constructor: target,
overriden: false,
});
}
Object.defineProperty(actual, '__serializable_type', { value: target.name });
Object.defineProperty(actual.prototype, '__serializable_type', {
value: target.name,
});
return actual;
};
};