Solve serialization

This commit is contained in:
schmelczerandras 2020-10-06 21:33:04 +02:00
parent ba8b1a29fd
commit 8e44dd3733
45 changed files with 242 additions and 201 deletions

View file

@ -7,6 +7,7 @@
"start": "concurrently --kill-others \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"", "start": "concurrently --kill-others \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"",
"lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"", "lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"",
"build": "webpack --mode production", "build": "webpack --mode production",
"try-build": "npm run build && node dist/main.js",
"initialize": "npm install" "initialize": "npm install"
}, },
"keywords": [], "keywords": [],

View file

@ -6,7 +6,7 @@ import {
applyArrayPlugins, applyArrayPlugins,
Random, Random,
TransportEvents, TransportEvents,
deserializeCommand, deserialize,
StepCommand, StepCommand,
} from 'shared'; } from 'shared';
import './index.html'; import './index.html';
@ -23,7 +23,6 @@ applyArrayPlugins();
Random.seed = 42; Random.seed = 42;
const objects = new PhysicalContainer(); const objects = new PhysicalContainer();
createDungeon(objects); createDungeon(objects);
createDungeon(objects); createDungeon(objects);
createDungeon(objects); createDungeon(objects);
@ -59,8 +58,8 @@ app.get('/', function (req, res) {
io.on('connection', (socket: SocketIO.Socket) => { io.on('connection', (socket: SocketIO.Socket) => {
socket.on(TransportEvents.PlayerJoining, () => { socket.on(TransportEvents.PlayerJoining, () => {
const player = new Player(objects, socket); const player = new Player(objects, socket);
socket.on(TransportEvents.PlayerToServer, (text: string) => { socket.on(TransportEvents.PlayerToServer, (json: string) => {
const command = deserializeCommand(JSON.parse(text)); const command = deserialize(json);
player.sendCommand(command); player.sendCommand(command);
}); });

View file

@ -6,7 +6,7 @@ import {
settings, settings,
CommandExecutors, CommandExecutors,
MoveActionCommand, MoveActionCommand,
typeToBaseType, serializable,
} 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';
@typeToBaseType @serializable(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;
@ -166,9 +166,4 @@ export class CharacterPhysical extends CharacterBase implements Physical {
this.container.removeObject(this.leftFoot); this.container.removeObject(this.leftFoot);
this.container.removeObject(this.rightFoot); this.container.removeObject(this.rightFoot);
} }
public toJSON(): any {
const { type, id, head, leftFoot, rightFoot } = this;
return [type, id, head, leftFoot, rightFoot];
}
} }

View file

@ -4,6 +4,7 @@ import {
clamp, clamp,
CommandExecutors, CommandExecutors,
GameObject, GameObject,
serializable,
settings, settings,
StepCommand, StepCommand,
} from 'shared'; } from 'shared';
@ -14,6 +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)
export class CirclePhysical implements Circle, Physical { export class CirclePhysical implements Circle, Physical {
readonly isInverted = false; readonly isInverted = false;
readonly canCollide = true; readonly canCollide = true;
@ -154,8 +156,8 @@ export class CirclePhysical implements Circle, Physical {
return wasHit; return wasHit;
} }
public toJSON(): any { public toArray(): Array<any> {
const { center, radius } = this; const { center, radius } = this;
return { center, radius }; return [center, radius];
} }
} }

View file

@ -1,11 +1,11 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { LampBase, settings, id, typeToBaseType } from 'shared'; import { LampBase, settings, id, serializable } 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';
@typeToBaseType @serializable(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;
@ -38,9 +38,4 @@ export class LampPhysical extends LampBase implements Physical {
public distance(_: vec2): number { public distance(_: vec2): number {
return 0; return 0;
} }
public toJSON(): any {
const { type, id, center, color, lightness } = this;
return [type, id, center, color, lightness];
}
} }

View file

@ -1,10 +1,10 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { clamp01, mix, TunnelBase, id, typeToBaseType } from 'shared'; import { clamp01, mix, TunnelBase, id, serializable } 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';
@typeToBaseType @serializable(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;
@ -45,9 +45,4 @@ export class TunnelPhysical extends TunnelBase implements StaticPhysical {
public get gameObject(): TunnelPhysical { public get gameObject(): TunnelPhysical {
return this; return this;
} }
public toJSON(): any {
const { type, id, from, to, fromRadius, toRadius } = this;
return [type, id, from, to, fromRadius, toRadius];
}
} }

View file

@ -7,6 +7,7 @@ import {
CreatePlayerCommand, CreatePlayerCommand,
DeleteObjectsCommand, DeleteObjectsCommand,
MoveActionCommand, MoveActionCommand,
serialize,
SetViewAreaActionCommand, SetViewAreaActionCommand,
TransportEvents, TransportEvents,
UpdateObjectsCommand, UpdateObjectsCommand,
@ -16,7 +17,6 @@ import { CharacterPhysical } from '../objects/character-physical';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { Physical } from '../physics/physical'; import { Physical } from '../physics/physical';
import { jsonSerialize } from '../serialize';
export class Player extends CommandReceiver { export class Player extends CommandReceiver {
public isActive = true; public isActive = true;
@ -50,7 +50,7 @@ export class Player extends CommandReceiver {
socket.emit( socket.emit(
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
jsonSerialize(new CreatePlayerCommand(jsonSerialize(this.character))) serialize(new CreatePlayerCommand(this.character))
); );
this.sendObjects(); this.sendObjects();
@ -72,13 +72,12 @@ export class Player extends CommandReceiver {
const noLongerIntersecting = this.objectsPreviouslyInViewArea.filter( const noLongerIntersecting = this.objectsPreviouslyInViewArea.filter(
(o) => !this.objectsInViewArea.includes(o) (o) => !this.objectsInViewArea.includes(o)
); );
this.objectsPreviouslyInViewArea = this.objectsInViewArea; this.objectsPreviouslyInViewArea = this.objectsInViewArea;
if (noLongerIntersecting.length > 0) { if (noLongerIntersecting.length > 0) {
this.socket.emit( this.socket.emit(
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
jsonSerialize( serialize(
new DeleteObjectsCommand([ new DeleteObjectsCommand([
...new Set(noLongerIntersecting.map((p) => p.gameObject.id)), ...new Set(noLongerIntersecting.map((p) => p.gameObject.id)),
]) ])
@ -89,25 +88,23 @@ export class Player extends CommandReceiver {
if (newlyIntersecting.length > 0) { if (newlyIntersecting.length > 0) {
this.socket.emit( this.socket.emit(
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
jsonSerialize( serialize(
new CreateObjectsCommand( new CreateObjectsCommand([
jsonSerialize([...new Set(newlyIntersecting.map((p) => p.gameObject))]) ...new Set(newlyIntersecting.map((p) => p.gameObject)),
) ])
) )
); );
} }
this.socket.emit( this.socket.emit(
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
jsonSerialize( serialize(
new UpdateObjectsCommand( new UpdateObjectsCommand([
jsonSerialize([
...new Set( ...new Set(
this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject) this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject)
), ),
]) ])
) )
)
); );
if (this.isActive) { if (this.isActive) {

View file

@ -1,2 +0,0 @@
export const jsonSerialize = (o: any): string =>
JSON.stringify(o, (key, value) => (value?.toFixed ? Number(value.toFixed(3)) : value));

View file

@ -32,29 +32,14 @@ module.exports = {
new TerserJSPlugin({ new TerserJSPlugin({
sourceMap: true, sourceMap: true,
cache: true, cache: true,
test: /\.ts$/i, test: /\.ts$/,
terserOptions: {
ecma: 5,
warnings: true,
parse: {},
compress: { defaults: true },
mangle: true,
module: false,
output: null,
toplevel: true,
nameCache: null,
ie8: false,
keep_classnames: false,
keep_fnames: false,
safari10: false,
},
}), }),
], ],
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.html$/i, test: /\.html$/,
use: { use: {
loader: 'file-loader', loader: 'file-loader',
query: { query: {

View file

@ -7,6 +7,7 @@
"start": "webpack-dev-server --mode development", "start": "webpack-dev-server --mode development",
"lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"", "lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"",
"build": "webpack --mode production && find dist -type f -not -name '*.html' | xargs rm", "build": "webpack --mode production && find dist -type f -not -name '*.html' | xargs rm",
"try-build": "npm run build && cd dist && python3 -m http.server 8080",
"initialize": "npm install" "initialize": "npm install"
}, },
"keywords": [], "keywords": [],

View file

@ -1,4 +1,4 @@
import { Command, CommandReceiver, TransportEvents } from 'shared'; import { Command, CommandReceiver, serialize, TransportEvents } from 'shared';
export class CommandReceiverSocket extends CommandReceiver { export class CommandReceiverSocket extends CommandReceiver {
constructor(private readonly socket: SocketIOClient.Socket) { constructor(private readonly socket: SocketIOClient.Socket) {
@ -6,6 +6,6 @@ export class CommandReceiverSocket extends CommandReceiver {
} }
protected defaultCommandExecutor(command: Command) { protected defaultCommandExecutor(command: Command) {
this.socket.emit(TransportEvents.PlayerToServer, JSON.stringify(command)); this.socket.emit(TransportEvents.PlayerToServer, serialize(command));
} }
} }

View file

@ -12,7 +12,9 @@ import {
} from 'sdf-2d'; } from 'sdf-2d';
import { import {
broadcastCommands, broadcastCommands,
deserialize,
prettyPrint, prettyPrint,
serialize,
settings, settings,
SetViewAreaActionCommand, SetViewAreaActionCommand,
StepCommand, StepCommand,
@ -27,9 +29,13 @@ 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';
import { deserialize } from './transport/deserialize';
const a = [CharacterView, TunnelView, LampView];
export class Game { export class Game {
public readonly gameObjects = new GameObjectContainer(); public readonly gameObjects = new GameObjectContainer();
@ -149,7 +155,7 @@ export class Game {
// todo: Should only send aspect ratio // todo: Should only send aspect ratio
this.socket.emit( this.socket.emit(
TransportEvents.PlayerToServer, TransportEvents.PlayerToServer,
JSON.stringify(new SetViewAreaActionCommand(this.gameObjects.camera.viewArea)) serialize(new SetViewAreaActionCommand(this.gameObjects.camera.viewArea))
); );
} }

View file

@ -1,8 +1,9 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { CharacterBase, CommandExecutors } from 'shared'; import { CharacterBase, CommandExecutors, deserializable } 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

@ -10,7 +10,6 @@ import {
StepCommand, StepCommand,
UpdateObjectsCommand, UpdateObjectsCommand,
} from 'shared'; } from 'shared';
import { deserialize, deserializeJsonArray } from '../transport/deserialize';
import { Camera } from './camera'; import { Camera } from './camera';
import { CharacterView } from './character-view'; import { CharacterView } from './character-view';
@ -21,7 +20,7 @@ export class GameObjectContainer extends CommandReceiver {
protected commandExecutors: CommandExecutors = { protected commandExecutors: CommandExecutors = {
[CreatePlayerCommand.type]: (c: CreatePlayerCommand) => { [CreatePlayerCommand.type]: (c: CreatePlayerCommand) => {
this.player = deserialize(c.serializedPlayer) as CharacterView; this.player = c.character as CharacterView;
this.camera = new Camera(); this.camera = new Camera();
this.addObject(this.player); this.addObject(this.player);
this.addObject(this.camera); this.addObject(this.camera);
@ -34,13 +33,13 @@ export class GameObjectContainer extends CommandReceiver {
}, },
[CreateObjectsCommand.type]: (c: CreateObjectsCommand) => [CreateObjectsCommand.type]: (c: CreateObjectsCommand) =>
deserializeJsonArray(c.serializedObjects).forEach((o) => this.addObject(o)), c.objects.forEach((o) => this.addObject(o)),
[DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) => [DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) =>
c.ids.forEach((id: Id) => this.objects.delete(id)), c.ids.forEach((id: Id) => this.objects.delete(id)),
[UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => [UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) =>
deserializeJsonArray(c.serializedObjects).forEach((o) => { c.objects.forEach((o) => {
this.objects.delete(o.id); this.objects.delete(o.id);
this.addObject(o); this.addObject(o);
if (o.id === this.player.id) { if (o.id === this.player.id) {

View file

@ -1,8 +1,9 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { CircleLight } from 'sdf-2d'; import { CircleLight } from 'sdf-2d';
import { CommandExecutors, Id, LampBase } from 'shared'; import { CommandExecutors, deserializable, 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,8 +1,9 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { InvertedTunnel } from 'sdf-2d'; import { InvertedTunnel } from 'sdf-2d';
import { CommandExecutors, Id, TunnelBase } from 'shared'; import { CommandExecutors, deserializable, 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,28 +0,0 @@
import {
CharacterBase,
commandConstructors,
GameObject,
LampBase,
TunnelBase,
} from 'shared';
import { CharacterView } from '../objects/character-view';
import { LampView } from '../objects/lamp-view';
import { TunnelView } from '../objects/tunnel-view';
const constructors: { [type: string]: new (...values: Array<any>) => GameObject } = {
[TunnelBase.type]: TunnelView,
[LampBase.type]: LampView,
[CharacterBase.type]: CharacterView,
...commandConstructors,
};
export const deserialize = (json: string): GameObject => {
const [type, ...values] = JSON.parse(json);
return new constructors[type](...values);
};
export const deserializeJsonArray = (json: string): Array<GameObject> => {
return (JSON.parse(json) as Array<any>).map(([type, ...values]) => {
return new constructors[type](...values);
});
};

View file

@ -10,6 +10,9 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"moduleResolution": "Node", "moduleResolution": "Node",
"module": "es6", "module": "es6",
"lib": ["es2015", "dom"] "lib": [
"es2015",
"dom"
]
} }
} }

View file

@ -31,28 +31,13 @@ module.exports = {
}, },
}, },
optimization: { optimization: {
minimize: true, minimize: false,
usedExports: true, usedExports: true,
minimizer: [ minimizer: [
new TerserJSPlugin({ new TerserJSPlugin({
sourceMap: true, sourceMap: true,
cache: true, cache: true,
test: /\.ts$/i, test: /\.ts$/,
terserOptions: {
ecma: 5,
warnings: true,
parse: {},
compress: { defaults: true },
mangle: true,
module: false,
output: null,
toplevel: true,
nameCache: null,
ie8: false,
keep_classnames: false,
keep_fnames: false,
safari10: false,
},
}), }),
new OptimizeCSSAssetsPlugin({}), new OptimizeCSSAssetsPlugin({}),
], ],
@ -81,7 +66,7 @@ module.exports = {
module: { module: {
rules: [ rules: [
{ {
test: /\.ico$/i, test: /\.ico$/,
use: { use: {
loader: 'file-loader', loader: 'file-loader',
query: { query: {
@ -91,7 +76,7 @@ module.exports = {
}, },
}, },
{ {
test: /\.scss$/i, test: /\.scss$/,
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
'css-loader', 'css-loader',

View file

@ -3,6 +3,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "lerna run --parallel start", "start": "lerna run --parallel start",
"try-build": "lerna run --parallel try-build",
"build": "lerna run build",
"initialize": "lerna run initialize" "initialize": "lerna run initialize"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,3 +1,9 @@
import { Typed } from '../transport/typed'; export abstract class Command {
public static get type(): string {
return (this as any).name;
}
export abstract class Command extends Typed { } public get type(): string {
return (this as any).constructor.name;
}
}

View file

@ -1,11 +1,14 @@
import { GameObject } from '../../objects/game-object';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable()
export class CreateObjectsCommand extends Command { export class CreateObjectsCommand extends Command {
public constructor(public readonly serializedObjects: string) { public constructor(public readonly objects: Array<GameObject>) {
super(); super();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.serializedObjects]; return [this.objects];
} }
} }

View file

@ -1,11 +1,14 @@
import { CharacterBase } from '../../objects/types/character-base';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable()
export class CreatePlayerCommand extends Command { export class CreatePlayerCommand extends Command {
public constructor(public readonly serializedPlayer: string) { public constructor(public readonly character: CharacterBase) {
super(); super();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.serializedPlayer]; return [this.character];
} }
} }

View file

@ -1,12 +1,14 @@
import { Id } from '../../transport/identity'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.ids]; return [this.ids];
} }
} }

View file

@ -1,12 +1,14 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.delta]; return [this.delta];
} }
} }

View file

@ -1,12 +1,14 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.position]; return [this.position];
} }
} }

View file

@ -1,12 +1,14 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.position]; return [this.position];
} }
} }

View file

@ -1,12 +1,14 @@
import { Rectangle } from '../../helper/rectangle'; import { Rectangle } from '../../helper/rectangle';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.viewArea]; return [this.viewArea];
} }
} }

View file

@ -1,12 +1,14 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@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();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.position]; return [this.position];
} }
} }

View file

@ -1,11 +1,14 @@
import { GameObject } from '../../objects/game-object';
import { serializable } from '../../transport/serializable/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable()
export class UpdateObjectsCommand extends Command { export class UpdateObjectsCommand extends Command {
public constructor(public readonly serializedObjects: string) { public constructor(public readonly objects: Array<GameObject>) {
super(); super();
} }
public toJSON(): any { public toArray(): Array<any> {
return [this.type, this.serializedObjects]; return [this.objects];
} }
} }

View file

@ -1,5 +1,11 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/serializable/serializable';
@serializable()
export class Circle { export class Circle {
constructor(public center: vec2, public radius: number) { } constructor(public center: vec2, public radius: number) { }
public toArray(): Array<any> {
return [this.center, this.radius];
}
} }

View file

@ -1,5 +1,11 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { serializable } from '../transport/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()) { }
public toArray(): Array<any> {
return [this.topLeft, this.size];
}
} }

View file

@ -22,7 +22,9 @@ 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 './objects/deserialize'; export * from './transport/serializable/deserialize';
export * from './transport/serializable/serialize';
export * from './transport/serializable/serializable';
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,26 +0,0 @@
import { CreatePlayerCommand } from '../commands/types/create-player';
import { MoveActionCommand } from '../commands/types/move-action';
import { PrimaryActionCommand } from '../commands/types/primary-action';
import { SecondaryActionCommand } from '../commands/types/secondary-action';
import { SetViewAreaActionCommand } from '../commands/types/set-view-area-action';
import { TernaryActionCommand } from '../commands/types/ternary-action';
import { UpdateObjectsCommand } from '../commands/types/update-objects';
import { Command, CreateObjectsCommand, DeleteObjectsCommand } from '../main';
export const commandConstructors: {
[type: string]: new (...values: Array<any>) => any;
} = {
[CreateObjectsCommand.type]: CreateObjectsCommand,
[DeleteObjectsCommand.type]: DeleteObjectsCommand,
[CreatePlayerCommand.type]: CreatePlayerCommand,
[MoveActionCommand.type]: MoveActionCommand,
[PrimaryActionCommand.type]: PrimaryActionCommand,
[SecondaryActionCommand.type]: SecondaryActionCommand,
[TernaryActionCommand.type]: TernaryActionCommand,
[SetViewAreaActionCommand.type]: SetViewAreaActionCommand,
[UpdateObjectsCommand.type]: UpdateObjectsCommand,
};
export const deserializeCommand = ([type, ...values]: [string, Array<any>]): Command => {
return new commandConstructors[type](...values);
};

View file

@ -2,14 +2,6 @@ import { CommandReceiver } from '../commands/command-receiver';
import { Id } from '../transport/identity'; import { Id } from '../transport/identity';
export abstract class GameObject extends CommandReceiver { export abstract class GameObject extends CommandReceiver {
public static get type(): string {
return (this as any).name;
}
public get type(): string {
return (this as any).constructor.name;
}
constructor(public readonly id: Id) { constructor(public readonly id: Id) {
super(); super();
} }

View file

@ -1,7 +1,9 @@
import { Circle } from '../../helper/circle'; import { Circle } from '../../helper/circle';
import { Id } from '../../main'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@serializable()
export abstract class CharacterBase extends GameObject { export abstract class CharacterBase extends GameObject {
constructor( constructor(
id: Id, id: Id,
@ -11,4 +13,9 @@ export abstract class CharacterBase extends GameObject {
) { ) {
super(id); super(id);
} }
public toArray(): Array<any> {
const { id, head, leftFoot, rightFoot } = this as any;
return [id, head, leftFoot, rightFoot];
}
} }

View file

@ -1,9 +1,15 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { Id } from '../../main'; import { Id, serializable } from '../../main';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
@serializable()
export abstract class LampBase extends GameObject { export abstract 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);
} }
public toArray(): Array<any> {
const { id, center, color, lightness } = this as any;
return [id, center, color, lightness];
}
} }

View file

@ -1,6 +1,9 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { GameObject, Id } from '../../main'; import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serializable/serializable';
import { GameObject } from '../game-object';
@serializable()
export abstract class TunnelBase extends GameObject { export abstract class TunnelBase extends GameObject {
constructor( constructor(
id: Id, id: Id,
@ -11,4 +14,9 @@ export abstract class TunnelBase extends GameObject {
) { ) {
super(id); super(id);
} }
public toArray(): Array<any> {
const { id, from, to, fromRadius, toRadius } = this as any;
return [id, from, to, fromRadius, toRadius];
}
} }

View file

@ -0,0 +1,13 @@
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

@ -0,0 +1,58 @@
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

@ -0,0 +1,10 @@
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

@ -1,9 +0,0 @@
export abstract class Typed {
public static get type(): string {
return (this as any).name;
}
public get type(): string {
return (this as any).constructor.name;
}
}

View file

@ -10,6 +10,9 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"moduleResolution": "Node", "moduleResolution": "Node",
"module": "es6", "module": "es6",
"lib": ["es2015", "dom"] "lib": [
"es2015",
"dom"
]
} }
} }