From 8e44dd3733d2303a34a0820f1164b97afa420f18 Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Tue, 6 Oct 2020 21:33:04 +0200 Subject: [PATCH] Solve serialization --- backend/package.json | 3 +- backend/src/main.ts | 7 +-- backend/src/objects/character-physical.ts | 9 +-- backend/src/objects/circle-physical.ts | 6 +- backend/src/objects/lamp-physical.ts | 9 +-- backend/src/objects/tunnel-physical.ts | 9 +-- backend/src/players/player.ts | 29 +++++----- backend/src/serialize.ts | 2 - backend/tsconfig.json | 2 +- backend/webpack.config.js | 19 +----- frontend/package.json | 3 +- .../receivers/command-receiver-socket.ts | 4 +- frontend/src/scripts/game.ts | 10 +++- .../src/scripts/objects/character-view.ts | 3 +- .../scripts/objects/game-object-container.ts | 7 +-- frontend/src/scripts/objects/lamp-view.ts | 3 +- frontend/src/scripts/objects/tunnel-view.ts | 3 +- frontend/src/scripts/transport/deserialize.ts | 28 --------- frontend/tsconfig.json | 7 ++- frontend/webpack.config.js | 23 ++------ package.json | 4 +- shared/package.json | 2 +- shared/src/commands/command.ts | 10 +++- shared/src/commands/types/create-objects.ts | 9 ++- shared/src/commands/types/create-player.ts | 9 ++- shared/src/commands/types/delete-objects.ts | 6 +- shared/src/commands/types/move-action.ts | 6 +- shared/src/commands/types/primary-action.ts | 6 +- shared/src/commands/types/secondary-action.ts | 6 +- .../commands/types/set-view-area-action.ts | 6 +- shared/src/commands/types/ternary-action.ts | 6 +- shared/src/commands/types/update-objects.ts | 9 ++- shared/src/helper/circle.ts | 8 ++- shared/src/helper/rectangle.ts | 8 ++- shared/src/main.ts | 4 +- shared/src/objects/deserialize.ts | 26 --------- shared/src/objects/game-object.ts | 8 --- shared/src/objects/types/character-base.ts | 9 ++- shared/src/objects/types/lamp-base.ts | 8 ++- shared/src/objects/types/tunnel-base.ts | 10 +++- .../src/transport/serializable/deserialize.ts | 13 +++++ .../transport/serializable/serializable.ts | 58 +++++++++++++++++++ .../src/transport/serializable/serialize.ts | 10 ++++ shared/src/transport/typed.ts | 9 --- shared/tsconfig.json | 7 ++- 45 files changed, 242 insertions(+), 201 deletions(-) delete mode 100644 backend/src/serialize.ts delete mode 100644 frontend/src/scripts/transport/deserialize.ts delete mode 100644 shared/src/objects/deserialize.ts create mode 100644 shared/src/transport/serializable/deserialize.ts create mode 100644 shared/src/transport/serializable/serializable.ts create mode 100644 shared/src/transport/serializable/serialize.ts delete mode 100644 shared/src/transport/typed.ts diff --git a/backend/package.json b/backend/package.json index 3789a5e..d31329d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "start": "concurrently --kill-others \"webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"", "lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"", "build": "webpack --mode production", + "try-build": "npm run build && node dist/main.js", "initialize": "npm install" }, "keywords": [], @@ -31,4 +32,4 @@ "uws": "^10.148.1", "webpack-node-externals": "^2.5.2" } -} +} \ No newline at end of file diff --git a/backend/src/main.ts b/backend/src/main.ts index 1e90611..72f472f 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -6,7 +6,7 @@ import { applyArrayPlugins, Random, TransportEvents, - deserializeCommand, + deserialize, StepCommand, } from 'shared'; import './index.html'; @@ -23,7 +23,6 @@ applyArrayPlugins(); Random.seed = 42; const objects = new PhysicalContainer(); - createDungeon(objects); createDungeon(objects); createDungeon(objects); @@ -59,8 +58,8 @@ app.get('/', function (req, res) { io.on('connection', (socket: SocketIO.Socket) => { socket.on(TransportEvents.PlayerJoining, () => { const player = new Player(objects, socket); - socket.on(TransportEvents.PlayerToServer, (text: string) => { - const command = deserializeCommand(JSON.parse(text)); + socket.on(TransportEvents.PlayerToServer, (json: string) => { + const command = deserialize(json); player.sendCommand(command); }); diff --git a/backend/src/objects/character-physical.ts b/backend/src/objects/character-physical.ts index 3bc0bd2..5d093e2 100644 --- a/backend/src/objects/character-physical.ts +++ b/backend/src/objects/character-physical.ts @@ -6,7 +6,7 @@ import { settings, CommandExecutors, MoveActionCommand, - typeToBaseType, + serializable, } 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'; -@typeToBaseType +@serializable(CharacterBase) export class CharacterPhysical extends CharacterBase implements Physical { public readonly canCollide = true; public readonly isInverted = false; @@ -166,9 +166,4 @@ export class CharacterPhysical extends CharacterBase implements Physical { this.container.removeObject(this.leftFoot); this.container.removeObject(this.rightFoot); } - - public toJSON(): any { - const { type, id, head, leftFoot, rightFoot } = this; - return [type, id, head, leftFoot, rightFoot]; - } } diff --git a/backend/src/objects/circle-physical.ts b/backend/src/objects/circle-physical.ts index 45df3df..b14ac85 100644 --- a/backend/src/objects/circle-physical.ts +++ b/backend/src/objects/circle-physical.ts @@ -4,6 +4,7 @@ import { clamp, CommandExecutors, GameObject, + serializable, settings, StepCommand, } from 'shared'; @@ -14,6 +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) export class CirclePhysical implements Circle, Physical { readonly isInverted = false; readonly canCollide = true; @@ -154,8 +156,8 @@ export class CirclePhysical implements Circle, Physical { return wasHit; } - public toJSON(): any { + public toArray(): Array { const { center, radius } = this; - return { center, radius }; + return [center, radius]; } } diff --git a/backend/src/objects/lamp-physical.ts b/backend/src/objects/lamp-physical.ts index ebd8924..b397347 100644 --- a/backend/src/objects/lamp-physical.ts +++ b/backend/src/objects/lamp-physical.ts @@ -1,11 +1,11 @@ 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 { Physical } from '../physics/physical'; -@typeToBaseType +@serializable(LampBase) export class LampPhysical extends LampBase implements Physical { public readonly canCollide = false; public readonly isInverted = false; @@ -38,9 +38,4 @@ export class LampPhysical extends LampBase implements Physical { public distance(_: vec2): number { return 0; } - - public toJSON(): any { - const { type, id, center, color, lightness } = this; - return [type, id, center, color, lightness]; - } } diff --git a/backend/src/objects/tunnel-physical.ts b/backend/src/objects/tunnel-physical.ts index a72c49a..eadfc70 100644 --- a/backend/src/objects/tunnel-physical.ts +++ b/backend/src/objects/tunnel-physical.ts @@ -1,10 +1,10 @@ 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 { StaticPhysical } from '../physics/containers/static-physical-object'; -@typeToBaseType +@serializable(TunnelBase) export class TunnelPhysical extends TunnelBase implements StaticPhysical { public readonly canCollide = true; public readonly isInverted = true; @@ -45,9 +45,4 @@ export class TunnelPhysical extends TunnelBase implements StaticPhysical { public get gameObject(): TunnelPhysical { return this; } - - public toJSON(): any { - const { type, id, from, to, fromRadius, toRadius } = this; - return [type, id, from, to, fromRadius, toRadius]; - } } diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index 2df28dc..75452b2 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -7,6 +7,7 @@ import { CreatePlayerCommand, DeleteObjectsCommand, MoveActionCommand, + serialize, SetViewAreaActionCommand, TransportEvents, UpdateObjectsCommand, @@ -16,7 +17,6 @@ import { CharacterPhysical } from '../objects/character-physical'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { PhysicalContainer } from '../physics/containers/physical-container'; import { Physical } from '../physics/physical'; -import { jsonSerialize } from '../serialize'; export class Player extends CommandReceiver { public isActive = true; @@ -50,7 +50,7 @@ export class Player extends CommandReceiver { socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize(new CreatePlayerCommand(jsonSerialize(this.character))) + serialize(new CreatePlayerCommand(this.character)) ); this.sendObjects(); @@ -72,13 +72,12 @@ export class Player extends CommandReceiver { const noLongerIntersecting = this.objectsPreviouslyInViewArea.filter( (o) => !this.objectsInViewArea.includes(o) ); - this.objectsPreviouslyInViewArea = this.objectsInViewArea; if (noLongerIntersecting.length > 0) { this.socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize( + serialize( new DeleteObjectsCommand([ ...new Set(noLongerIntersecting.map((p) => p.gameObject.id)), ]) @@ -89,24 +88,22 @@ export class Player extends CommandReceiver { if (newlyIntersecting.length > 0) { this.socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize( - new CreateObjectsCommand( - jsonSerialize([...new Set(newlyIntersecting.map((p) => p.gameObject))]) - ) + serialize( + new CreateObjectsCommand([ + ...new Set(newlyIntersecting.map((p) => p.gameObject)), + ]) ) ); } this.socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize( - new UpdateObjectsCommand( - jsonSerialize([ - ...new Set( - this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject) - ), - ]) - ) + serialize( + new UpdateObjectsCommand([ + ...new Set( + this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject) + ), + ]) ) ); diff --git a/backend/src/serialize.ts b/backend/src/serialize.ts deleted file mode 100644 index 973cedd..0000000 --- a/backend/src/serialize.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const jsonSerialize = (o: any): string => - JSON.stringify(o, (key, value) => (value?.toFixed ? Number(value.toFixed(3)) : value)); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 5521a05..480d017 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -11,4 +11,4 @@ "module": "commonjs", "esModuleInterop": true } -} +} \ No newline at end of file diff --git a/backend/webpack.config.js b/backend/webpack.config.js index a66ad35..281a1d1 100644 --- a/backend/webpack.config.js +++ b/backend/webpack.config.js @@ -32,29 +32,14 @@ module.exports = { new TerserJSPlugin({ sourceMap: true, cache: true, - test: /\.ts$/i, - 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, - }, + test: /\.ts$/, }), ], }, module: { rules: [ { - test: /\.html$/i, + test: /\.html$/, use: { loader: 'file-loader', query: { diff --git a/frontend/package.json b/frontend/package.json index 2d37f2c..e2696ab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "start": "webpack-dev-server --mode development", "lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.ts\"", "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" }, "keywords": [], @@ -41,4 +42,4 @@ "sideEffects": [ "*.scss" ] -} +} \ No newline at end of file diff --git a/frontend/src/scripts/commands/receivers/command-receiver-socket.ts b/frontend/src/scripts/commands/receivers/command-receiver-socket.ts index cb6fc59..16a2687 100644 --- a/frontend/src/scripts/commands/receivers/command-receiver-socket.ts +++ b/frontend/src/scripts/commands/receivers/command-receiver-socket.ts @@ -1,4 +1,4 @@ -import { Command, CommandReceiver, TransportEvents } from 'shared'; +import { Command, CommandReceiver, serialize, TransportEvents } from 'shared'; export class CommandReceiverSocket extends CommandReceiver { constructor(private readonly socket: SocketIOClient.Socket) { @@ -6,6 +6,6 @@ export class CommandReceiverSocket extends CommandReceiver { } protected defaultCommandExecutor(command: Command) { - this.socket.emit(TransportEvents.PlayerToServer, JSON.stringify(command)); + this.socket.emit(TransportEvents.PlayerToServer, serialize(command)); } } diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 4865042..aa8a5f0 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -12,7 +12,9 @@ import { } from 'sdf-2d'; import { broadcastCommands, + deserialize, prettyPrint, + serialize, settings, SetViewAreaActionCommand, StepCommand, @@ -27,9 +29,13 @@ 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'; -import { deserialize } from './transport/deserialize'; + +const a = [CharacterView, TunnelView, LampView]; export class Game { public readonly gameObjects = new GameObjectContainer(); @@ -149,7 +155,7 @@ export class Game { // todo: Should only send aspect ratio this.socket.emit( TransportEvents.PlayerToServer, - JSON.stringify(new SetViewAreaActionCommand(this.gameObjects.camera.viewArea)) + serialize(new SetViewAreaActionCommand(this.gameObjects.camera.viewArea)) ); } diff --git a/frontend/src/scripts/objects/character-view.ts b/frontend/src/scripts/objects/character-view.ts index 8cb63b6..7de66bc 100644 --- a/frontend/src/scripts/objects/character-view.ts +++ b/frontend/src/scripts/objects/character-view.ts @@ -1,8 +1,9 @@ import { vec2 } from 'gl-matrix'; -import { CharacterBase, CommandExecutors } from 'shared'; +import { CharacterBase, CommandExecutors, deserializable } 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(); diff --git a/frontend/src/scripts/objects/game-object-container.ts b/frontend/src/scripts/objects/game-object-container.ts index 20680b2..3accee6 100644 --- a/frontend/src/scripts/objects/game-object-container.ts +++ b/frontend/src/scripts/objects/game-object-container.ts @@ -10,7 +10,6 @@ import { StepCommand, UpdateObjectsCommand, } from 'shared'; -import { deserialize, deserializeJsonArray } from '../transport/deserialize'; import { Camera } from './camera'; import { CharacterView } from './character-view'; @@ -21,7 +20,7 @@ export class GameObjectContainer extends CommandReceiver { protected commandExecutors: CommandExecutors = { [CreatePlayerCommand.type]: (c: CreatePlayerCommand) => { - this.player = deserialize(c.serializedPlayer) as CharacterView; + this.player = c.character as CharacterView; this.camera = new Camera(); this.addObject(this.player); this.addObject(this.camera); @@ -34,13 +33,13 @@ export class GameObjectContainer extends CommandReceiver { }, [CreateObjectsCommand.type]: (c: CreateObjectsCommand) => - deserializeJsonArray(c.serializedObjects).forEach((o) => this.addObject(o)), + c.objects.forEach((o) => this.addObject(o)), [DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) => c.ids.forEach((id: Id) => this.objects.delete(id)), [UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => - deserializeJsonArray(c.serializedObjects).forEach((o) => { + c.objects.forEach((o) => { this.objects.delete(o.id); this.addObject(o); if (o.id === this.player.id) { diff --git a/frontend/src/scripts/objects/lamp-view.ts b/frontend/src/scripts/objects/lamp-view.ts index a2b1045..1c04fb6 100644 --- a/frontend/src/scripts/objects/lamp-view.ts +++ b/frontend/src/scripts/objects/lamp-view.ts @@ -1,8 +1,9 @@ import { vec2, vec3 } from 'gl-matrix'; import { CircleLight } from 'sdf-2d'; -import { CommandExecutors, Id, LampBase } from 'shared'; +import { CommandExecutors, deserializable, Id, LampBase } from 'shared'; import { RenderCommand } from '../commands/types/render'; +@deserializable(LampBase) export class LampView extends LampBase { private light: CircleLight; diff --git a/frontend/src/scripts/objects/tunnel-view.ts b/frontend/src/scripts/objects/tunnel-view.ts index ecd6638..bd34bf8 100644 --- a/frontend/src/scripts/objects/tunnel-view.ts +++ b/frontend/src/scripts/objects/tunnel-view.ts @@ -1,8 +1,9 @@ import { vec2 } from 'gl-matrix'; import { InvertedTunnel } from 'sdf-2d'; -import { CommandExecutors, Id, TunnelBase } from 'shared'; +import { CommandExecutors, deserializable, Id, TunnelBase } from 'shared'; import { RenderCommand } from '../commands/types/render'; +@deserializable(TunnelBase) export class TunnelView extends TunnelBase { private shape: InvertedTunnel; diff --git a/frontend/src/scripts/transport/deserialize.ts b/frontend/src/scripts/transport/deserialize.ts deleted file mode 100644 index cfe0f56..0000000 --- a/frontend/src/scripts/transport/deserialize.ts +++ /dev/null @@ -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) => 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 => { - return (JSON.parse(json) as Array).map(([type, ...values]) => { - return new constructors[type](...values); - }); -}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3ce307e..c042138 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -10,6 +10,9 @@ "allowSyntheticDefaultImports": true, "moduleResolution": "Node", "module": "es6", - "lib": ["es2015", "dom"] + "lib": [ + "es2015", + "dom" + ] } -} +} \ No newline at end of file diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index ebbeb23..98bad7e 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -31,28 +31,13 @@ module.exports = { }, }, optimization: { - minimize: true, + minimize: false, usedExports: true, minimizer: [ new TerserJSPlugin({ sourceMap: true, cache: true, - test: /\.ts$/i, - 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, - }, + test: /\.ts$/, }), new OptimizeCSSAssetsPlugin({}), ], @@ -81,7 +66,7 @@ module.exports = { module: { rules: [ { - test: /\.ico$/i, + test: /\.ico$/, use: { loader: 'file-loader', query: { @@ -91,7 +76,7 @@ module.exports = { }, }, { - test: /\.scss$/i, + test: /\.scss$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', diff --git a/package.json b/package.json index a5de943..045e143 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "private": true, "scripts": { "start": "lerna run --parallel start", + "try-build": "lerna run --parallel try-build", + "build": "lerna run build", "initialize": "lerna run initialize" }, "devDependencies": { @@ -37,4 +39,4 @@ "decla.red-server": "file:backend", "shared": "file:shared" } -} +} \ No newline at end of file diff --git a/shared/package.json b/shared/package.json index 8e255dc..7c852f6 100644 --- a/shared/package.json +++ b/shared/package.json @@ -7,4 +7,4 @@ "test": "echo \"Error: no test specified\" && exit 1", "initialize": "npm install" } -} +} \ No newline at end of file diff --git a/shared/src/commands/command.ts b/shared/src/commands/command.ts index 9beb114..1142b7a 100644 --- a/shared/src/commands/command.ts +++ b/shared/src/commands/command.ts @@ -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; + } +} diff --git a/shared/src/commands/types/create-objects.ts b/shared/src/commands/types/create-objects.ts index 80f3267..b69cbc2 100644 --- a/shared/src/commands/types/create-objects.ts +++ b/shared/src/commands/types/create-objects.ts @@ -1,11 +1,14 @@ +import { GameObject } from '../../objects/game-object'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class CreateObjectsCommand extends Command { - public constructor(public readonly serializedObjects: string) { + public constructor(public readonly objects: Array) { super(); } - public toJSON(): any { - return [this.type, this.serializedObjects]; + public toArray(): Array { + return [this.objects]; } } diff --git a/shared/src/commands/types/create-player.ts b/shared/src/commands/types/create-player.ts index 7778edd..4944d8b 100644 --- a/shared/src/commands/types/create-player.ts +++ b/shared/src/commands/types/create-player.ts @@ -1,11 +1,14 @@ +import { CharacterBase } from '../../objects/types/character-base'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class CreatePlayerCommand extends Command { - public constructor(public readonly serializedPlayer: string) { + public constructor(public readonly character: CharacterBase) { super(); } - public toJSON(): any { - return [this.type, this.serializedPlayer]; + public toArray(): Array { + return [this.character]; } } diff --git a/shared/src/commands/types/delete-objects.ts b/shared/src/commands/types/delete-objects.ts index 2999916..bec92ac 100644 --- a/shared/src/commands/types/delete-objects.ts +++ b/shared/src/commands/types/delete-objects.ts @@ -1,12 +1,14 @@ import { Id } from '../../transport/identity'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class DeleteObjectsCommand extends Command { public constructor(public readonly ids: Array) { super(); } - public toJSON(): any { - return [this.type, this.ids]; + public toArray(): Array { + return [this.ids]; } } diff --git a/shared/src/commands/types/move-action.ts b/shared/src/commands/types/move-action.ts index 131a6d9..902fc16 100644 --- a/shared/src/commands/types/move-action.ts +++ b/shared/src/commands/types/move-action.ts @@ -1,12 +1,14 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class MoveActionCommand extends Command { public constructor(public readonly delta: vec2) { super(); } - public toJSON(): any { - return [this.type, this.delta]; + public toArray(): Array { + return [this.delta]; } } diff --git a/shared/src/commands/types/primary-action.ts b/shared/src/commands/types/primary-action.ts index 8b322b6..6960bb3 100644 --- a/shared/src/commands/types/primary-action.ts +++ b/shared/src/commands/types/primary-action.ts @@ -1,12 +1,14 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class PrimaryActionCommand extends Command { public constructor(public readonly position: vec2) { super(); } - public toJSON(): any { - return [this.type, this.position]; + public toArray(): Array { + return [this.position]; } } diff --git a/shared/src/commands/types/secondary-action.ts b/shared/src/commands/types/secondary-action.ts index d16c347..1241ad3 100644 --- a/shared/src/commands/types/secondary-action.ts +++ b/shared/src/commands/types/secondary-action.ts @@ -1,12 +1,14 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class SecondaryActionCommand extends Command { public constructor(public readonly position: vec2) { super(); } - public toJSON(): any { - return [this.type, this.position]; + public toArray(): Array { + return [this.position]; } } diff --git a/shared/src/commands/types/set-view-area-action.ts b/shared/src/commands/types/set-view-area-action.ts index 3ab9061..f9f86ef 100644 --- a/shared/src/commands/types/set-view-area-action.ts +++ b/shared/src/commands/types/set-view-area-action.ts @@ -1,12 +1,14 @@ import { Rectangle } from '../../helper/rectangle'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class SetViewAreaActionCommand extends Command { public constructor(public readonly viewArea: Rectangle) { super(); } - public toJSON(): any { - return [this.type, this.viewArea]; + public toArray(): Array { + return [this.viewArea]; } } diff --git a/shared/src/commands/types/ternary-action.ts b/shared/src/commands/types/ternary-action.ts index 041387d..404fc70 100644 --- a/shared/src/commands/types/ternary-action.ts +++ b/shared/src/commands/types/ternary-action.ts @@ -1,12 +1,14 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class TernaryActionCommand extends Command { public constructor(public readonly position: vec2) { super(); } - public toJSON(): any { - return [this.type, this.position]; + public toArray(): Array { + return [this.position]; } } diff --git a/shared/src/commands/types/update-objects.ts b/shared/src/commands/types/update-objects.ts index 32732dc..3741f8c 100644 --- a/shared/src/commands/types/update-objects.ts +++ b/shared/src/commands/types/update-objects.ts @@ -1,11 +1,14 @@ +import { GameObject } from '../../objects/game-object'; +import { serializable } from '../../transport/serializable/serializable'; import { Command } from '../command'; +@serializable() export class UpdateObjectsCommand extends Command { - public constructor(public readonly serializedObjects: string) { + public constructor(public readonly objects: Array) { super(); } - public toJSON(): any { - return [this.type, this.serializedObjects]; + public toArray(): Array { + return [this.objects]; } } diff --git a/shared/src/helper/circle.ts b/shared/src/helper/circle.ts index 39451c5..879221e 100644 --- a/shared/src/helper/circle.ts +++ b/shared/src/helper/circle.ts @@ -1,5 +1,11 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../transport/serializable/serializable'; +@serializable() export class Circle { - constructor(public center: vec2, public radius: number) {} + constructor(public center: vec2, public radius: number) { } + + public toArray(): Array { + return [this.center, this.radius]; + } } diff --git a/shared/src/helper/rectangle.ts b/shared/src/helper/rectangle.ts index df856a6..af1f3cc 100644 --- a/shared/src/helper/rectangle.ts +++ b/shared/src/helper/rectangle.ts @@ -1,5 +1,11 @@ import { vec2 } from 'gl-matrix'; +import { serializable } from '../transport/serializable/serializable'; +@serializable() export class Rectangle { - constructor(public topLeft = vec2.create(), public size = vec2.create()) {} + constructor(public topLeft = vec2.create(), public size = vec2.create()) { } + + public toArray(): Array { + return [this.topLeft, this.size]; + } } diff --git a/shared/src/main.ts b/shared/src/main.ts index b9e9a54..8dbea13 100644 --- a/shared/src/main.ts +++ b/shared/src/main.ts @@ -22,7 +22,9 @@ export * from './helper/random'; export * from './helper/unique'; export * from './helper/rotate-90-deg'; 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/lamp-base'; export * from './objects/types/tunnel-base'; diff --git a/shared/src/objects/deserialize.ts b/shared/src/objects/deserialize.ts deleted file mode 100644 index a6c3861..0000000 --- a/shared/src/objects/deserialize.ts +++ /dev/null @@ -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; -} = { - [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]): Command => { - return new commandConstructors[type](...values); -}; diff --git a/shared/src/objects/game-object.ts b/shared/src/objects/game-object.ts index 347fed2..6192edd 100644 --- a/shared/src/objects/game-object.ts +++ b/shared/src/objects/game-object.ts @@ -2,14 +2,6 @@ import { CommandReceiver } from '../commands/command-receiver'; import { Id } from '../transport/identity'; 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) { super(); } diff --git a/shared/src/objects/types/character-base.ts b/shared/src/objects/types/character-base.ts index 5be52c6..d7310ad 100644 --- a/shared/src/objects/types/character-base.ts +++ b/shared/src/objects/types/character-base.ts @@ -1,7 +1,9 @@ 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'; +@serializable() export abstract class CharacterBase extends GameObject { constructor( id: Id, @@ -11,4 +13,9 @@ export abstract class CharacterBase extends GameObject { ) { super(id); } + + public toArray(): Array { + const { id, head, leftFoot, rightFoot } = this as any; + return [id, head, leftFoot, rightFoot]; + } } diff --git a/shared/src/objects/types/lamp-base.ts b/shared/src/objects/types/lamp-base.ts index f4d3e71..8090ae6 100644 --- a/shared/src/objects/types/lamp-base.ts +++ b/shared/src/objects/types/lamp-base.ts @@ -1,9 +1,15 @@ import { vec2, vec3 } from 'gl-matrix'; -import { Id } from '../../main'; +import { Id, serializable } from '../../main'; import { GameObject } from '../game-object'; +@serializable() export abstract class LampBase extends GameObject { constructor(id: Id, public center: vec2, public color: vec3, public lightness: number) { super(id); } + + public toArray(): Array { + const { id, center, color, lightness } = this as any; + return [id, center, color, lightness]; + } } diff --git a/shared/src/objects/types/tunnel-base.ts b/shared/src/objects/types/tunnel-base.ts index 53d30ca..6818c55 100644 --- a/shared/src/objects/types/tunnel-base.ts +++ b/shared/src/objects/types/tunnel-base.ts @@ -1,6 +1,9 @@ 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 { constructor( id: Id, @@ -11,4 +14,9 @@ export abstract class TunnelBase extends GameObject { ) { super(id); } + + public toArray(): Array { + const { id, from, to, fromRadius, toRadius } = this as any; + return [id, from, to, fromRadius, toRadius]; + } } diff --git a/shared/src/transport/serializable/deserialize.ts b/shared/src/transport/serializable/deserialize.ts new file mode 100644 index 0000000..0d9009a --- /dev/null +++ b/shared/src/transport/serializable/deserialize.ts @@ -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; + }); +}; diff --git a/shared/src/transport/serializable/serializable.ts b/shared/src/transport/serializable/serializable.ts new file mode 100644 index 0000000..85cfc63 --- /dev/null +++ b/shared/src/transport/serializable/serializable.ts @@ -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; + }; +}; diff --git a/shared/src/transport/serializable/serialize.ts b/shared/src/transport/serializable/serialize.ts new file mode 100644 index 0000000..521c0f7 --- /dev/null +++ b/shared/src/transport/serializable/serialize.ts @@ -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; +}; diff --git a/shared/src/transport/typed.ts b/shared/src/transport/typed.ts deleted file mode 100644 index b9663ef..0000000 --- a/shared/src/transport/typed.ts +++ /dev/null @@ -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; - } -} diff --git a/shared/tsconfig.json b/shared/tsconfig.json index 3ce307e..c042138 100644 --- a/shared/tsconfig.json +++ b/shared/tsconfig.json @@ -10,6 +10,9 @@ "allowSyntheticDefaultImports": true, "moduleResolution": "Node", "module": "es6", - "lib": ["es2015", "dom"] + "lib": [ + "es2015", + "dom" + ] } -} +} \ No newline at end of file