add compression to engine messages

This commit is contained in:
Elizabeth Hunt 2023-09-05 22:28:11 -06:00
parent d19da30f6d
commit 6708160cec
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
8 changed files with 88 additions and 14 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "engine/utils/compatto"]
path = engine/utils/compatto
url = https://github.com/macarie/compatto

View File

@ -1,5 +1,5 @@
import type { Message, MessagePublisher } from '@engine/network'; import type { Message, MessagePublisher } from '@engine/network';
import { stringify } from '@engine/utils'; import { serialize } from '@engine/utils';
export class ClientSocketMessagePublisher implements MessagePublisher { export class ClientSocketMessagePublisher implements MessagePublisher {
private socket: WebSocket; private socket: WebSocket;
@ -17,7 +17,7 @@ export class ClientSocketMessagePublisher implements MessagePublisher {
public publish() { public publish() {
if (this.socket.readyState == WebSocket.OPEN) { if (this.socket.readyState == WebSocket.OPEN) {
this.messages.forEach((message: Message) => this.messages.forEach((message: Message) =>
this.socket.send(stringify(message)) this.socket.send(serialize(message))
); );
this.messages = []; this.messages = [];
} }

View File

@ -10,8 +10,10 @@ export class ClientSocketMessageQueueProvider implements MessageQueueProvider {
this.messages = []; this.messages = [];
this.socket.addEventListener('message', (e) => { this.socket.addEventListener('message', (e) => {
const messages = parse<Message[]>(e.data); e.data.arrayBuffer().then((buffer) => {
this.messages = this.messages.concat(messages); const messages = parse<Message[]>(new Uint8Array(buffer));
this.messages = this.messages.concat(messages);
});
}); });
} }

View File

@ -1,3 +1,8 @@
import { compatto } from './compatto';
import dictionary from './dictionary';
const { compress, decompress } = compatto({ dictionary });
const replacer = (_key: any, value: any) => { const replacer = (_key: any, value: any) => {
if (value instanceof Map) { if (value instanceof Map) {
return { return {
@ -31,10 +36,17 @@ const reviver = (_key: any, value: any) => {
}; };
// "deterministic" stringify // "deterministic" stringify
export const stringify = (obj: any) => {
export const stringify = (obj: any): string => {
return JSON.stringify(sortObj(obj), replacer); return JSON.stringify(sortObj(obj), replacer);
}; };
export const parse = <T>(str: string) => { export const serialize = (obj: any): Uint8Array => {
return JSON.parse(str, reviver) as unknown as T; //return new Uint8Array(new TextEncoder().encode(stringify(obj)));
return compress(stringify(obj));
};
export const parse = <T>(serialized: Uint8Array): T => {
//return JSON.parse(new TextDecoder().decode(serialized), reviver) as T;
return JSON.parse(decompress(serialized), reviver) as T;
}; };

1
engine/utils/compatto Submodule

@ -0,0 +1 @@
Subproject commit 68628ac6f173f50b81cc2d83a75de176484b2c7f

View File

@ -0,0 +1,56 @@
// basically a list of common strings between network components to help in compression
// can't be longer than 254 "words"
export default [
'{',
'}',
',',
'"',
"'",
',',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'0',
'.',
'Map',
'[',
']',
'BoundingBox',
'args',
'height',
'width',
'name',
'body',
'Velocity',
'Control',
'Component',
'NEW_ENTITIES',
'REMOVE_ENTITIES',
'UPDATE_ENTITIES',
'NEW_INPUT',
'REMOVE_INPUT',
'boundingBox',
'dTheta',
'id',
'Forces',
':',
'control',
'controllableBy',
'dimension',
'rotation',
'forces',
'velocity',
'center',
'dCartesian',
'x',
'y',
'"body":[{"args":{',
'controlVelocityComponent',
'"boundingBox":{"center":'
];

View File

@ -1,7 +1,7 @@
import { Message, MessagePublisher } from '@engine/network'; import { Message, MessagePublisher } from '@engine/network';
import { Server } from 'bun'; import { Server } from 'bun';
import { Constants } from '../constants'; import { Constants } from '../constants';
import { stringify } from '@engine/utils'; import { serialize } from '@engine/utils';
export class ServerSocketMessagePublisher implements MessagePublisher { export class ServerSocketMessagePublisher implements MessagePublisher {
private server?: Server; private server?: Server;
@ -23,7 +23,7 @@ export class ServerSocketMessagePublisher implements MessagePublisher {
public publish() { public publish() {
if (this.messages.length) { if (this.messages.length) {
this.server?.publish(Constants.GAME_TOPIC, stringify(this.messages)); this.server?.publish(Constants.GAME_TOPIC, serialize(this.messages));
this.messages = []; this.messages = [];
} }

View File

@ -10,11 +10,10 @@ import {
Session, Session,
SessionManager SessionManager
} from './network'; } from './network';
import { parse } from '@engine/utils'; import { parse, serialize } from '@engine/utils';
import { Server, ServerWebSocket } from 'bun'; import { Server, ServerWebSocket } from 'bun';
import { Input } from '@engine/systems'; import { Input } from '@engine/systems';
import { Control, NetworkUpdateable } from '@engine/components'; import { Control, NetworkUpdateable } from '@engine/components';
import { stringify } from '@engine/utils';
export class GameServer { export class GameServer {
private server?: Server; private server?: Server;
@ -44,7 +43,8 @@ export class GameServer {
websocket: { websocket: {
open: (ws) => this.openWebsocket(ws), open: (ws) => this.openWebsocket(ws),
message: (ws, msg) => { message: (ws, msg) => {
if (typeof msg === 'string') this.websocketMessage(ws, msg); if (typeof msg !== 'string')
this.websocketMessage(ws, new Uint8Array(msg));
}, },
close: (ws) => this.closeWebsocket(ws) close: (ws) => this.closeWebsocket(ws)
} }
@ -57,7 +57,7 @@ export class GameServer {
private websocketMessage( private websocketMessage(
websocket: ServerWebSocket<SessionData>, websocket: ServerWebSocket<SessionData>,
message: string message: Uint8Array
) { ) {
const receivedMessage = parse<ServerMessage>(message); const receivedMessage = parse<ServerMessage>(message);
receivedMessage.sessionData = websocket.data; receivedMessage.sessionData = websocket.data;
@ -117,7 +117,7 @@ export class GameServer {
}) })
} }
]; ];
websocket.sendText(stringify(addCurrentEntities)); websocket.sendBinary(serialize(addCurrentEntities));
const addNewPlayer: Message = { const addNewPlayer: Message = {
type: MessageType.NEW_ENTITIES, type: MessageType.NEW_ENTITIES,