Refactor serialization
This commit is contained in:
parent
8e44dd3733
commit
b3da27e73b
35 changed files with 142 additions and 133 deletions
|
|
@ -6,7 +6,7 @@ import {
|
|||
settings,
|
||||
CommandExecutors,
|
||||
MoveActionCommand,
|
||||
serializable,
|
||||
serializesTo,
|
||||
} from 'shared';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
|
|
@ -15,7 +15,7 @@ import { CirclePhysical } from './circle-physical';
|
|||
import { Physical } from '../physics/physical';
|
||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||
|
||||
@serializable(CharacterBase)
|
||||
@serializesTo(CharacterBase)
|
||||
export class CharacterPhysical extends CharacterBase implements Physical {
|
||||
public readonly canCollide = true;
|
||||
public readonly isInverted = false;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
clamp,
|
||||
CommandExecutors,
|
||||
GameObject,
|
||||
serializable,
|
||||
serializesTo,
|
||||
settings,
|
||||
StepCommand,
|
||||
} from 'shared';
|
||||
|
|
@ -15,7 +15,7 @@ import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
|
|||
import { moveCircle } from '../physics/move-circle';
|
||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||
|
||||
@serializable(Circle)
|
||||
@serializesTo(Circle)
|
||||
export class CirclePhysical implements Circle, Physical {
|
||||
readonly isInverted = false;
|
||||
readonly canCollide = true;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
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 { Physical } from '../physics/physical';
|
||||
|
||||
@serializable(LampBase)
|
||||
@serializesTo(LampBase)
|
||||
export class LampPhysical extends LampBase implements Physical {
|
||||
public readonly canCollide = false;
|
||||
public readonly isInverted = false;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
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 { StaticPhysical } from '../physics/containers/static-physical-object';
|
||||
|
||||
@serializable(TunnelBase)
|
||||
@serializesTo(TunnelBase)
|
||||
export class TunnelPhysical extends TunnelBase implements StaticPhysical {
|
||||
public readonly canCollide = true;
|
||||
public readonly isInverted = true;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import { glMatrix } from 'gl-matrix';
|
||||
import { CharacterBase, LampBase, overrideDeserialization, TunnelBase } from 'shared';
|
||||
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';
|
||||
|
||||
glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
overrideDeserialization(CharacterBase, CharacterView);
|
||||
overrideDeserialization(TunnelBase, TunnelView);
|
||||
overrideDeserialization(LampBase, LampView);
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
await new Game().start();
|
||||
|
|
|
|||
|
|
@ -29,14 +29,10 @@ import { RenderCommand } from './commands/types/render';
|
|||
import { Configuration } from './config/configuration';
|
||||
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||
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 { BlobShape } from './shapes/blob-shape';
|
||||
|
||||
const a = [CharacterView, TunnelView, LampView];
|
||||
|
||||
export class Game {
|
||||
public readonly gameObjects = new GameObjectContainer();
|
||||
private readonly canvas: HTMLCanvasElement = document.querySelector('canvas#main');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { CharacterBase, CommandExecutors, deserializable } from 'shared';
|
||||
import { CharacterBase, CommandExecutors } from 'shared';
|
||||
import { RenderCommand } from '../commands/types/render';
|
||||
import { BlobShape } from '../shapes/blob-shape';
|
||||
|
||||
@deserializable(CharacterBase)
|
||||
export class CharacterView extends CharacterBase {
|
||||
private shape = new BlobShape();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { CircleLight } from 'sdf-2d';
|
||||
import { CommandExecutors, deserializable, Id, LampBase } from 'shared';
|
||||
import { CommandExecutors, Id, LampBase } from 'shared';
|
||||
import { RenderCommand } from '../commands/types/render';
|
||||
|
||||
@deserializable(LampBase)
|
||||
export class LampView extends LampBase {
|
||||
private light: CircleLight;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { InvertedTunnel } from 'sdf-2d';
|
||||
import { CommandExecutors, deserializable, Id, TunnelBase } from 'shared';
|
||||
import { CommandExecutors, Id, TunnelBase } from 'shared';
|
||||
import { RenderCommand } from '../commands/types/render';
|
||||
|
||||
@deserializable(TunnelBase)
|
||||
export class TunnelView extends TunnelBase {
|
||||
private shape: InvertedTunnel;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { GameObject } from '../../objects/game-object';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class CreateObjectsCommand extends Command {
|
||||
public constructor(public readonly objects: Array<GameObject>) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { CharacterBase } from '../../objects/types/character-base';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class CreatePlayerCommand extends Command {
|
||||
public constructor(public readonly character: CharacterBase) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Id } from '../../transport/identity';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class DeleteObjectsCommand extends Command {
|
||||
public constructor(public readonly ids: Array<Id>) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class MoveActionCommand extends Command {
|
||||
public constructor(public readonly delta: vec2) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class PrimaryActionCommand extends Command {
|
||||
public constructor(public readonly position: vec2) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class SecondaryActionCommand extends Command {
|
||||
public constructor(public readonly position: vec2) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Rectangle } from '../../helper/rectangle';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class SetViewAreaActionCommand extends Command {
|
||||
public constructor(public readonly viewArea: Rectangle) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class TernaryActionCommand extends Command {
|
||||
public constructor(public readonly position: vec2) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { GameObject } from '../../objects/game-object';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { Command } from '../command';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class UpdateObjectsCommand extends Command {
|
||||
public constructor(public readonly objects: Array<GameObject>) {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../transport/serializable/serializable';
|
||||
import { serializable } from '../transport/serialization/serializable';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class Circle {
|
||||
constructor(public center: vec2, public radius: number) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { serializable } from '../transport/serializable/serializable';
|
||||
import { serializable } from '../transport/serialization/serializable';
|
||||
|
||||
@serializable()
|
||||
@serializable
|
||||
export class Rectangle {
|
||||
constructor(public topLeft = vec2.create(), public size = vec2.create()) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ export * from './helper/random';
|
|||
export * from './helper/unique';
|
||||
export * from './helper/rotate-90-deg';
|
||||
export * from './objects/game-object';
|
||||
export * from './transport/serializable/deserialize';
|
||||
export * from './transport/serializable/serialize';
|
||||
export * from './transport/serializable/serializable';
|
||||
export * from './transport/serialization/deserialize';
|
||||
export * from './transport/serialization/serialize';
|
||||
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/lamp-base';
|
||||
export * from './objects/types/tunnel-base';
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Circle } from '../../helper/circle';
|
||||
import { Id } from '../../transport/identity';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { GameObject } from '../game-object';
|
||||
|
||||
@serializable()
|
||||
export abstract class CharacterBase extends GameObject {
|
||||
@serializable
|
||||
export class CharacterBase extends GameObject {
|
||||
constructor(
|
||||
id: Id,
|
||||
public head: Circle,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { vec2, vec3 } from 'gl-matrix';
|
|||
import { Id, serializable } from '../../main';
|
||||
import { GameObject } from '../game-object';
|
||||
|
||||
@serializable()
|
||||
export abstract class LampBase extends GameObject {
|
||||
@serializable
|
||||
export class LampBase extends GameObject {
|
||||
constructor(id: Id, public center: vec2, public color: vec3, public lightness: number) {
|
||||
super(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Id } from '../../transport/identity';
|
||||
import { serializable } from '../../transport/serializable/serializable';
|
||||
import { serializable } from '../../transport/serialization/serializable';
|
||||
import { GameObject } from '../game-object';
|
||||
|
||||
@serializable()
|
||||
export abstract class TunnelBase extends GameObject {
|
||||
@serializable
|
||||
export class TunnelBase extends GameObject {
|
||||
constructor(
|
||||
id: Id,
|
||||
public readonly from: vec2,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export type DeserializableClass = { new(...args: Array<any>): {}; name: string };
|
||||
13
shared/src/transport/serialization/deserialize.ts
Normal file
13
shared/src/transport/serialization/deserialize.ts
Normal 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;
|
||||
});
|
||||
};
|
||||
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
4
shared/src/transport/serialization/serializable-class.ts
Normal file
4
shared/src/transport/serialization/serializable-class.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type SerializableClass = {
|
||||
new(...args: Array<any>): { toArray(): Array<any> };
|
||||
name: string;
|
||||
};
|
||||
12
shared/src/transport/serialization/serializable-mapping.ts
Normal file
12
shared/src/transport/serialization/serializable-mapping.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { DeserializableClass } from './deserializable-class';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const serializableMapping = new Map<
|
||||
string,
|
||||
{
|
||||
constructor: DeserializableClass;
|
||||
overriden: boolean;
|
||||
}
|
||||
>();
|
||||
16
shared/src/transport/serialization/serializable.ts
Normal file
16
shared/src/transport/serialization/serializable.ts
Normal 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;
|
||||
};
|
||||
8
shared/src/transport/serialization/serialize.ts
Normal file
8
shared/src/transport/serialization/serialize.ts
Normal 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;
|
||||
});
|
||||
};
|
||||
20
shared/src/transport/serialization/serializes-to.ts
Normal file
20
shared/src/transport/serialization/serializes-to.ts
Normal 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;
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue