From c31af6b02ab469082721e5310599ddf498f0e079 Mon Sep 17 00:00:00 2001 From: sli97 <775303361@qq.com> Date: Sun, 4 Dec 2022 22:10:30 +0800 Subject: [PATCH] binary encode --- .../Scripts/Entity/Actor/ActorManager.ts | 15 +- .../Scripts/Entity/Weapon/WeaponManager.ts | 25 +- .../assets/Scripts/Global/DataManager.ts | 14 +- .../assets/Scripts/Global/NetworkManager.ts | 37 +- .../assets/Scripts/Scene/BattleManager.ts | 21 +- apps/client/assets/Scripts/Utils/index.ts | 77 +- apps/server/src/Common/Binary.ts | 67 + apps/server/src/Common/Binary.ts.meta | 9 + apps/server/src/Common/Enum.ts | 55 +- apps/server/src/Common/Msg.ts | 8 +- apps/server/src/Common/Utils.ts | 42 + apps/server/src/Common/Utils.ts.meta | 9 + apps/server/src/Core/MyServer.ts | 64 + apps/server/src/biz/Player.ts | 2 +- apps/server/src/biz/PlayerManager.ts | 3 +- apps/server/src/biz/Room.ts | 30 +- apps/server/src/common/index.ts | 2 +- apps/server/src/core/Connection.ts | 28 +- apps/server/src/core/index.ts | 73 +- apps/server/src/index.ts | 3 +- apps/server/src/utils/binary.ts | 1142 ++++++++--------- apps/server/src/utils/index.ts | 76 ++ test.js | 32 +- 23 files changed, 1089 insertions(+), 745 deletions(-) create mode 100644 apps/server/src/Common/Binary.ts create mode 100644 apps/server/src/Common/Binary.ts.meta create mode 100644 apps/server/src/Common/Utils.ts create mode 100644 apps/server/src/Common/Utils.ts.meta create mode 100644 apps/server/src/Core/MyServer.ts diff --git a/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts b/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts index e783505..eaab0ef 100644 --- a/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts +++ b/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts @@ -1,10 +1,10 @@ import { _decorator, instantiate, ProgressBar, Label } from 'cc'; import { EntityManager } from '../../Base/EntityManager'; -import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2 } from '../../Common'; +import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from '../../Common'; import { EntityStateEnum } from '../../Enum'; import DataManager from '../../Global/DataManager'; import NetworkManager from '../../Global/NetworkManager'; -import { rad2Angle, toFixed } from '../../Utils'; +import { rad2Angle } from '../../Utils'; import { WeaponManager } from '../Weapon/WeaponManager'; import { PlayerStateMachine } from './ActorStateMachine'; const { ccclass } = _decorator; @@ -59,9 +59,9 @@ export class ActorManager extends EntityManager implements IActor { return } - const { x, y } = DataManager.Instance.jm.input - NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { - input: { + if (DataManager.Instance.jm.input.length()) { + const { x, y } = DataManager.Instance.jm.input + NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { type: InputTypeEnum.ActorMove, id: this.id, direction: { @@ -69,8 +69,9 @@ export class ActorManager extends EntityManager implements IActor { y: toFixed(y), }, dt: toFixed(dt) - } - }) + }) + } + } render(data: IActor) { diff --git a/apps/client/assets/Scripts/Entity/Weapon/WeaponManager.ts b/apps/client/assets/Scripts/Entity/Weapon/WeaponManager.ts index 2d7a27d..7d7fb12 100644 --- a/apps/client/assets/Scripts/Entity/Weapon/WeaponManager.ts +++ b/apps/client/assets/Scripts/Entity/Weapon/WeaponManager.ts @@ -1,11 +1,10 @@ import { _decorator, Node, Vec2, UITransform } from 'cc' import { EntityManager } from '../../Base/EntityManager' -import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum } from '../../Common' +import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum, toFixed } from '../../Common' import { EntityStateEnum, EventEnum } from '../../Enum' import DataManager from '../../Global/DataManager' import EventManager from '../../Global/EventManager' import NetworkManager from '../../Global/NetworkManager' -import { toFixed } from '../../Utils' import { WeaponStateMachine } from './WeaponStateMachine' const { ccclass } = _decorator @@ -62,18 +61,16 @@ export class WeaponManager extends EntityManager { const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize() NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { - input: { - type: InputTypeEnum.WeaponShoot, - owner: this.owner, - position: { - x: toFixed(pointStagePos.x), - y: toFixed(pointStagePos.y), - }, - direction: { - x: toFixed(directionVec2.x), - y: toFixed(directionVec2.y), - }, - } + type: InputTypeEnum.WeaponShoot, + owner: this.owner, + position: { + x: toFixed(pointStagePos.x), + y: toFixed(pointStagePos.y), + }, + direction: { + x: toFixed(directionVec2.x), + y: toFixed(directionVec2.y), + }, }) } } diff --git a/apps/client/assets/Scripts/Global/DataManager.ts b/apps/client/assets/Scripts/Global/DataManager.ts index 512196c..786a541 100644 --- a/apps/client/assets/Scripts/Global/DataManager.ts +++ b/apps/client/assets/Scripts/Global/DataManager.ts @@ -1,6 +1,6 @@ import { Node, Prefab, SpriteFrame } from 'cc' import Singleton from '../Base/Singleton' -import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState } from '../Common' +import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from '../Common' import { ActorManager } from '../Entity/Actor/ActorManager' import { BulletManager } from '../Entity/Bullet/BulletManager' import { EventEnum } from '../Enum' @@ -82,8 +82,8 @@ export default class DataManager extends Singleton { return } - player.position.x += x * PLAYER_SPEED * dt - player.position.y += y * PLAYER_SPEED * dt + player.position.x += toFixed(x * PLAYER_SPEED * dt) + player.position.y += toFixed(y * PLAYER_SPEED * dt) player.direction = { x, y } break } @@ -112,8 +112,8 @@ export default class DataManager extends Singleton { const player = players[j]; if (((player.position.x - bullet.position.x) ** 2 + (player.position.y - bullet.position.y) ** 2) < (PLAYER_RADIUS + BULLET_RADIUS) ** 2) { EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, { - x: (player.position.x + bullet.position.x) / 2, - y: (player.position.y + bullet.position.y) / 2, + x: toFixed((player.position.x + bullet.position.x) / 2), + y: toFixed((player.position.y + bullet.position.y) / 2), }) player.hp -= WEAPON_DAMAGE @@ -132,8 +132,8 @@ export default class DataManager extends Singleton { } for (const bullet of this.state.bullets) { - bullet.position.x += bullet.direction.x * BULLET_SPEED * dt - bullet.position.y += bullet.direction.y * BULLET_SPEED * dt + bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt) + bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt) } } } diff --git a/apps/client/assets/Scripts/Global/NetworkManager.ts b/apps/client/assets/Scripts/Global/NetworkManager.ts index 29eead7..6749b48 100644 --- a/apps/client/assets/Scripts/Global/NetworkManager.ts +++ b/apps/client/assets/Scripts/Global/NetworkManager.ts @@ -1,5 +1,7 @@ import Singleton from '../Base/Singleton' -import { IModel } from '../Common'; +import { ApiMsgEnum, IModel, strdecode, strencode } from '../Common'; +import { binaryEncode } from '../Common/Binary'; +import { binaryDecode } from '../Utils'; const TIMEOUT = 5000 @@ -17,7 +19,7 @@ export default class NetworkManager extends Singleton { ws: WebSocket port = 8888 - cbs: Map = new Map() + maps: Map = new Map() isConnected = false connect() { @@ -27,6 +29,8 @@ export default class NetworkManager extends Singleton { return } this.ws = new WebSocket(`ws://localhost:${this.port}`) + + this.ws.binaryType = 'arraybuffer'; this.ws.onopen = () => { console.log("ws onopen") this.isConnected = true @@ -45,15 +49,15 @@ export default class NetworkManager extends Singleton { this.ws.onmessage = (e) => { try { - const json = JSON.parse(e.data) + const json = binaryDecode(e.data) const { name, data } = json try { - if (this.cbs.has(name) && this.cbs.get(name).length) { + if (this.maps.has(name) && this.maps.get(name).length) { console.log(json); - this.cbs.get(name).forEach(cb => cb(data)) + this.maps.get(name).forEach(cb => cb(data)) } } catch (error) { - console.log("this.cbs.get(name).forEach(cb => cb(restData))", error) + console.log("this.maps.get(name).forEach(cb => cb(restData))", error) } } catch (error) { @@ -80,7 +84,7 @@ export default class NetworkManager extends Singleton { } this.listenMsg(name as any, cb) - this.ws.send(JSON.stringify({ name, data })) + this.sendMsg(name as any, data) } catch (error) { console.log(error) resolve({ success: false, error: error as Error }) @@ -89,21 +93,24 @@ export default class NetworkManager extends Singleton { } sendMsg(name: T, data: IModel['msg'][T]) { - this.ws.send(JSON.stringify({ name, data })) + const view = binaryEncode(name, data) + console.log("view", view.buffer); + + this.ws.send(view.buffer) } listenMsg(name: T, cb: (args: IModel['msg'][T]) => void) { - if (this.cbs.has(name)) { - this.cbs.get(name).push(cb) + if (this.maps.has(name)) { + this.maps.get(name).push(cb) } else { - this.cbs.set(name, [cb]) + this.maps.set(name, [cb]) } } - unlistenMsg(name: string, cb: Function) { - if (this.cbs.has(name)) { - const index = this.cbs.get(name).indexOf(cb) - index > -1 && this.cbs.get(name).splice(index, 1) + unlistenMsg(name: ApiMsgEnum, cb: Function) { + if (this.maps.has(name)) { + const index = this.maps.get(name).indexOf(cb) + index > -1 && this.maps.get(name).splice(index, 1) } } } diff --git a/apps/client/assets/Scripts/Scene/BattleManager.ts b/apps/client/assets/Scripts/Scene/BattleManager.ts index 0d5310c..eaeb4ed 100644 --- a/apps/client/assets/Scripts/Scene/BattleManager.ts +++ b/apps/client/assets/Scripts/Scene/BattleManager.ts @@ -8,7 +8,6 @@ import NetworkManager from '../Global/NetworkManager'; import ObjectPoolManager from '../Global/ObjectPoolManager'; import { BulletManager } from '../Entity/Bullet/BulletManager'; import { ApiMsgEnum, EntityTypeEnum, IMsgServerSync, InputTypeEnum } from '../Common'; -import { toFixed } from '../Utils'; const { ccclass } = _decorator; @@ -86,7 +85,7 @@ export class BattleManager extends Component { map.setParent(this.stage) } - handleSync({ inputs }: IMsgServerSync) { + handleSync(inputs: IMsgServerSync) { for (const input of inputs) { DataManager.Instance.applyInput(input) } @@ -102,7 +101,7 @@ export class BattleManager extends Component { tick(dt: number) { this.tickPlayer(dt) - this.tickGlobal(dt) + // this.tickGlobal(dt) } tickPlayer(dt: number) { @@ -115,14 +114,14 @@ export class BattleManager extends Component { } } - tickGlobal(dt: number) { - NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { - input: { - type: InputTypeEnum.TimePast, - dt: toFixed(dt), - } - }) - } + // tickGlobal(dt: number) { + // NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { + // input: { + // type: InputTypeEnum.TimePast, + // dt: toFixed(dt), + // } + // }) + // } render() { this.renderPlayer() diff --git a/apps/client/assets/Scripts/Utils/index.ts b/apps/client/assets/Scripts/Utils/index.ts index a13e573..405d8e6 100644 --- a/apps/client/assets/Scripts/Utils/index.ts +++ b/apps/client/assets/Scripts/Utils/index.ts @@ -1,4 +1,5 @@ import { SpriteFrame } from "cc" +import { ApiMsgEnum, InputTypeEnum, strdecode } from "../Common" const INDEX_REG = /\((\d+)\)/ @@ -9,4 +10,78 @@ export const sortSpriteFrame = (spriteFrame: Array) => export const rad2Angle = (rad: number) => rad / Math.PI * 180 -export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit +export const binaryDecode = (buffer: ArrayBuffer) => { + let index = 0 + const view = new DataView(buffer) + const type = view.getUint8(index++) + + if (type === ApiMsgEnum.MsgClientSync) { + const inputType = view.getUint8(index++) + if (inputType === InputTypeEnum.ActorMove) { + const id = view.getUint8(index++) + const directionX = view.getFloat32(index) + index += 4 + const directionY = view.getFloat32(index) + index += 4 + const dt = view.getFloat32(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.ActorMove, + id, + direction: { + x: directionX, + y: directionY, + }, + dt + } + } + + return msg + } else if (inputType === InputTypeEnum.WeaponShoot) { + const id = view.getUint8(index++) + const positionX = view.getFloat32(index) + index += 4 + const positionY = view.getFloat32(index) + index += 4 + const directionX = view.getFloat32(index) + index += 4 + const directionY = view.getFloat32(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.WeaponShoot, + id, + position: { + x: positionX, + y: positionY, + }, + direction: { + x: directionX, + y: directionY, + }, + } + } + + return msg + } else { + const dt = view.getFloat32(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.TimePast, + dt, + } + } + return msg + } + } else { + return { + name: type, + data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1)))) + } + } +} \ No newline at end of file diff --git a/apps/server/src/Common/Binary.ts b/apps/server/src/Common/Binary.ts new file mode 100644 index 0000000..f6d2634 --- /dev/null +++ b/apps/server/src/Common/Binary.ts @@ -0,0 +1,67 @@ +import { ApiMsgEnum, InputTypeEnum } from "./Enum"; +import { strencode } from "./Utils"; + +export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => { + if (proto === ApiMsgEnum.MsgClientSync) { + switch (data.type) { + case InputTypeEnum.ActorMove: { + let index = 0 + const ab = new ArrayBuffer(3 + 12) + const view = new DataView(ab) + view.setUint8(index++, proto) + view.setUint8(index++, data.type) + view.setUint8(index++, data.id) + view.setFloat32(index, data.direction.x) + index += 4 + view.setFloat32(index, data.direction.y) + index += 4 + view.setFloat32(index, data.dt) + index += 4 + return view + } + case InputTypeEnum.WeaponShoot: { + let index = 0 + const ab = new ArrayBuffer(3 + 16) + const view = new DataView(ab) + view.setUint8(index++, proto) + view.setUint8(index++, data.type) + view.setUint8(index++, data.id) + view.setFloat32(index, data.position.x) + index += 4 + view.setFloat32(index, data.position.y) + index += 4 + view.setFloat32(index, data.direction.x) + index += 4 + view.setFloat32(index, data.direction.y) + index += 4 + return view + } + case InputTypeEnum.TimePast: { + let index = 0 + const ab = new ArrayBuffer(1 + 1 + 4) + const view = new DataView(ab) + view.setUint8(index++, proto) + view.setUint8(index++, data.type) + view.setFloat32(index, data.dt) + index += 4 + return view + } + default: { + const ab = new ArrayBuffer(0) + const view = new DataView(ab) + return view + } + } + } else { + let index = 0 + const str = JSON.stringify(data) + const ta = strencode(str) + const ab = new ArrayBuffer(ta.length + 1) + const view = new DataView(ab) + view.setUint8(index++, proto) + for (let i = 0; i < ta.length; i++) { + view.setUint8(index++, ta[i]) + } + return view + } +} \ No newline at end of file diff --git a/apps/server/src/Common/Binary.ts.meta b/apps/server/src/Common/Binary.ts.meta new file mode 100644 index 0000000..1945fcf --- /dev/null +++ b/apps/server/src/Common/Binary.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "9888254e-f4d1-4b2a-a814-75fb288e474f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/apps/server/src/Common/Enum.ts b/apps/server/src/Common/Enum.ts index 1f41c0c..0ec8245 100644 --- a/apps/server/src/Common/Enum.ts +++ b/apps/server/src/Common/Enum.ts @@ -1,23 +1,46 @@ +// export enum ApiMsgEnum { +// ApiPlayerList = 'ApiPlayerList', +// ApiPlayerJoin = 'ApiPlayerJoin', +// ApiRoomList = 'ApiRoomList', +// ApiRoomCreate = 'ApiRoomCreate', +// ApiRoomJoin = 'ApiRoomJoin', +// ApiRoomLeave = 'ApiRoomLeave', +// ApiGameStart = 'ApiGameStart', +// MsgPlayerList = 'MsgPlayerList', +// MsgRoomList = 'MsgRoomList', +// MsgRoom = 'MsgRoom', +// MsgGameStart = 'MsgGameStart', +// MsgClientSync = 'MsgClientSync', +// MsgServerSync = 'MsgServerSync', +// } + export enum ApiMsgEnum { - ApiPlayerList = 'ApiPlayerList', - ApiPlayerJoin = 'ApiPlayerJoin', - ApiRoomList = 'ApiRoomList', - ApiRoomCreate = 'ApiRoomCreate', - ApiRoomJoin = 'ApiRoomJoin', - ApiRoomLeave = 'ApiRoomLeave', - ApiGameStart = 'ApiGameStart', - MsgPlayerList = 'MsgPlayerList', - MsgRoomList = 'MsgRoomList', - MsgRoom = 'MsgRoom', - MsgGameStart = 'MsgGameStart', - MsgClientSync = 'MsgClientSync', - MsgServerSync = 'MsgServerSync', + ApiPlayerList, + ApiPlayerJoin, + ApiRoomList, + ApiRoomCreate, + ApiRoomJoin, + ApiRoomLeave, + ApiGameStart, + MsgPlayerList, + MsgRoomList, + MsgRoom, + MsgGameStart, + MsgClientSync, + MsgServerSync, } +// export enum InputTypeEnum { +// ActorMove = 'ActorMove', +// WeaponShoot = 'WeaponShoot', +// TimePast = 'TimePast', +// } + + export enum InputTypeEnum { - ActorMove = 'ActorMove', - WeaponShoot = 'WeaponShoot', - TimePast = 'TimePast', + ActorMove, + WeaponShoot, + TimePast, } export enum EntityTypeEnum { diff --git a/apps/server/src/Common/Msg.ts b/apps/server/src/Common/Msg.ts index 15a9466..c22c949 100644 --- a/apps/server/src/Common/Msg.ts +++ b/apps/server/src/Common/Msg.ts @@ -22,10 +22,6 @@ export interface IMsgGameStart { state: IState } -export interface IMsgClientSync { - input: IClientInput -} +export type IMsgClientSync = IClientInput -export interface IMsgServerSync { - inputs: Array -} \ No newline at end of file +export type IMsgServerSync = Array \ No newline at end of file diff --git a/apps/server/src/Common/Utils.ts b/apps/server/src/Common/Utils.ts new file mode 100644 index 0000000..9629064 --- /dev/null +++ b/apps/server/src/Common/Utils.ts @@ -0,0 +1,42 @@ +export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit + +export const strencode = (str: string) => { + let byteArray: number[] = []; + for (let i = 0; i < str.length; i++) { + let charCode = str.charCodeAt(i); + if (charCode <= 0x7f) { + byteArray.push(charCode); + } else if (charCode <= 0x7ff) { + byteArray.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f)); + } else if (charCode <= 0xffff) { + byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)); + } else { + byteArray.push(0xf0 | (charCode >> 18), 0x80 | ((charCode & 0x3f000) >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)); + } + } + return new Uint8Array(byteArray); +} + +export const strdecode = (bytes: Uint8Array) => { + let array: number[] = []; + let offset = 0; + let charCode = 0; + let end = bytes.length; + while (offset < end) { + if (bytes[offset] < 128) { + charCode = bytes[offset]; + offset += 1; + } else if (bytes[offset] < 224) { + charCode = ((bytes[offset] & 0x3f) << 6) + (bytes[offset + 1] & 0x3f); + offset += 2; + } else if (bytes[offset] < 240) { + charCode = ((bytes[offset] & 0x0f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f); + offset += 3; + } else { + charCode = ((bytes[offset] & 0x07) << 18) + ((bytes[offset + 1] & 0x3f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f); + offset += 4; + } + array.push(charCode); + } + return String.fromCharCode.apply(null, array); +} \ No newline at end of file diff --git a/apps/server/src/Common/Utils.ts.meta b/apps/server/src/Common/Utils.ts.meta new file mode 100644 index 0000000..df2e80b --- /dev/null +++ b/apps/server/src/Common/Utils.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "b44d77f2-f4af-4b06-bc29-986d191fe180", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/apps/server/src/Core/MyServer.ts b/apps/server/src/Core/MyServer.ts new file mode 100644 index 0000000..02a5e73 --- /dev/null +++ b/apps/server/src/Core/MyServer.ts @@ -0,0 +1,64 @@ +import { EventEmitter } from 'stream'; +import WebSocket, { WebSocketServer } from 'ws'; +import { ApiMsgEnum } from '../Common'; +import { Connection, ConnectionEventEnum } from './Connection'; + +export interface IMyServerOptions { + port: number +} + +export enum MyServerEventEnum { + Connect = 'Connect', + DisConnect = 'DisConnect', +} + +export class MyServer extends EventEmitter { + wss?: WebSocketServer + port: number + connections: Set = new Set() + apiMap: Map = new Map() + + constructor({ port = 8080 }: Partial) { + super() + this.port = port + } + + start() { + return new Promise((resolve, reject) => { + this.wss = new WebSocketServer({ port: this.port }); + this.wss.on('connection', this.handleConnect.bind(this)); + + this.wss.on("error", (e) => { + reject(e) + }) + + this.wss.on("close", () => { + console.log("MyServer 服务关闭"); + }) + + this.wss.on("listening", () => { + resolve(true) + }) + }) + + } + + handleConnect(ws: WebSocket) { + //初始化 + const connection = new Connection(this, ws) + + //向外告知有人来了 + this.connections.add(connection) + this.emit(MyServerEventEnum.Connect, connection) + + //向外告知有人走了 + connection.on(ConnectionEventEnum.Close, (code: number, reason: string) => { + this.connections.delete(connection) + this.emit(MyServerEventEnum.DisConnect, connection, code, reason) + }) + } + + setApi(apiName: ApiMsgEnum, cb: Function) { + this.apiMap.set(apiName, cb) + } +} \ No newline at end of file diff --git a/apps/server/src/biz/Player.ts b/apps/server/src/biz/Player.ts index b6ad1ea..3be2644 100644 --- a/apps/server/src/biz/Player.ts +++ b/apps/server/src/biz/Player.ts @@ -1,4 +1,4 @@ -import Connection from '../Core/Connection' +import { Connection } from "../Core" export default class Player { id: number diff --git a/apps/server/src/biz/PlayerManager.ts b/apps/server/src/biz/PlayerManager.ts index 9c9e09a..ff041a8 100644 --- a/apps/server/src/biz/PlayerManager.ts +++ b/apps/server/src/biz/PlayerManager.ts @@ -1,9 +1,8 @@ import Singleton from '../Base/Singleton' import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common' +import { Connection } from '../Core' import Player from './Player' import RoomManager from './RoomManager' -import Connection from '../Core/Connection' - export default class PlayerManager extends Singleton { static get Instance() { return super.GetInstance() diff --git a/apps/server/src/biz/Room.ts b/apps/server/src/biz/Room.ts index 32a9cfe..3e60309 100644 --- a/apps/server/src/biz/Room.ts +++ b/apps/server/src/biz/Room.ts @@ -1,4 +1,4 @@ -import { ApiMsgEnum, EntityTypeEnum, IClientInput, IState } from '../Common' +import { ApiMsgEnum, EntityTypeEnum, IClientInput, InputTypeEnum, IState, toFixed } from '../Common' import type Player from './Player' import PlayerManager from './PlayerManager' import RoomManager from './RoomManager' @@ -6,12 +6,14 @@ import RoomManager from './RoomManager' export default class Room { id: number players: Set = new Set() + lastSyncTime?: number + + private inputs: Array = [] constructor(rid: number) { this.id = rid } - private inputs: Array = [] join(uid: number) { const player = PlayerManager.Instance.getPlayerById(uid) @@ -71,12 +73,15 @@ export default class Room { this.listenPlayer() setInterval(() => { this.syncInput() - }, 300) + }, 100) + setInterval(() => { + this.timePast() + }, 16) } listenPlayer() { for (const player of this.players) { - player.connection.listenMsg(ApiMsgEnum.MsgClientSync, ({ input }) => { + player.connection.listenMsg(ApiMsgEnum.MsgClientSync, (input) => { this.inputs.push(input) }) } @@ -85,10 +90,21 @@ export default class Room { syncInput() { const inputs = this.inputs this.inputs = [] + + for (const player of this.players) { - player.connection.sendMsg(ApiMsgEnum.MsgServerSync, { - inputs - }) + player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs) } } + + timePast() { + let now = process.uptime(); + const dt = now - (this.lastSyncTime ?? now) + this.inputs.push({ + type: InputTypeEnum.TimePast, + dt: toFixed(dt) + }) + this.lastSyncTime = now; + + } } diff --git a/apps/server/src/common/index.ts b/apps/server/src/common/index.ts index dae85d0..9ace9d1 100644 --- a/apps/server/src/common/index.ts +++ b/apps/server/src/common/index.ts @@ -1,12 +1,12 @@ import { IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes } from './Api' import { ApiMsgEnum } from './Enum' import { IMsgClientSync, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg' -import { IClientInput } from './State' export * from './Api' export * from './Msg' export * from './Enum' export * from './Model' export * from './State' +export * from './Utils' export interface IModel { api: { diff --git a/apps/server/src/core/Connection.ts b/apps/server/src/core/Connection.ts index 9aa3b1a..9046feb 100644 --- a/apps/server/src/core/Connection.ts +++ b/apps/server/src/core/Connection.ts @@ -1,17 +1,18 @@ import WebSocket from 'ws'; import { EventEmitter } from 'stream'; -import MyServer, { IData } from '.'; -import { getTime } from '../Utils'; -import { IModel } from '../Common'; +import { MyServer } from './MyServer'; +import { binaryDecode, getTime } from '../Utils'; +import { ApiMsgEnum, IModel } from '../Common'; +import { binaryEncode } from '../Common/Binary'; export enum ConnectionEventEnum { Close = 'Close', } -export default class Connection extends EventEmitter { +export class Connection extends EventEmitter { server: MyServer ws: WebSocket - msgMap: Map = new Map() + msgMap: Map = new Map() playerId?: number; constructor(server: MyServer, ws: WebSocket) { @@ -24,10 +25,12 @@ export default class Connection extends EventEmitter { }) this.ws.on('message', (buffer: Buffer) => { - const str = buffer.toString() + // const str = buffer.toString() try { - const { name, data } = JSON.parse(str) - console.log(`${getTime()}接收|${this.playerId || -1}|${str}`) + const json = binaryDecode(buffer) + const { name, data } = json + // console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${str}`) + console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${JSON.stringify(json)}`) if (this.server.apiMap.has(name)) { try { const cb = this.server.apiMap.get(name) @@ -49,7 +52,8 @@ export default class Connection extends EventEmitter { this.msgMap.get(name)?.forEach(cb => cb(data)) } } catch (error) { - console.log(`解析失败,${str}不是合法的JSON格式:`, error) + // console.log(`解析失败,${str}不是合法的JSON格式:`, error) + console.log(error) } }) } @@ -74,7 +78,9 @@ export default class Connection extends EventEmitter { name, data }) - console.log(`${getTime()}发送|${this.playerId || -1}|${msg}`) - this.ws.send(msg) + const view = binaryEncode(name, data) + const buffer = Buffer.from(view.buffer) + console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|${msg}`) + this.ws.send(buffer) } } \ No newline at end of file diff --git a/apps/server/src/core/index.ts b/apps/server/src/core/index.ts index 1d69ea1..9ddae88 100644 --- a/apps/server/src/core/index.ts +++ b/apps/server/src/core/index.ts @@ -1,71 +1,2 @@ -import { EventEmitter } from 'stream'; -import WebSocket, { WebSocketServer } from 'ws'; -import Connection, { ConnectionEventEnum } from './Connection'; - -export interface IMyServerOptions { - port: number -} - -export type IData = Record - -export interface ICallApiRet { - success: boolean; - error?: Error; - res?: IData -} - -export enum MyServerEventEnum { - Connect = 'Connect', - DisConnect = 'DisConnect', -} - -export default class MyServer extends EventEmitter { - wss?: WebSocketServer - port: number - connections: Set = new Set() - apiMap: Map = new Map() - - constructor({ port = 8080 }: Partial) { - super() - this.port = port - } - - start() { - return new Promise((resolve, reject) => { - this.wss = new WebSocketServer({ port: this.port }); - this.wss.on('connection', this.handleConnect.bind(this)); - - this.wss.on("error", (e) => { - reject(e) - }) - - this.wss.on("close", () => { - console.log("MyServer 服务关闭"); - }) - - this.wss.on("listening", () => { - resolve(true) - }) - }) - - } - - handleConnect(ws: WebSocket) { - //初始化 - const connection = new Connection(this, ws) - - //向外告知有人来了 - this.connections.add(connection) - this.emit(MyServerEventEnum.Connect, connection) - - //向外告知有人走了 - connection.on(ConnectionEventEnum.Close, (code: number, reason: string) => { - this.connections.delete(connection) - this.emit(MyServerEventEnum.DisConnect, connection, code, reason) - }) - } - - setApi(apiName: string, cb: Function) { - this.apiMap.set(apiName, cb) - } -} \ No newline at end of file +export * from './MyServer' +export * from './Connection' \ No newline at end of file diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 093fc84..c45650c 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,7 +1,6 @@ -import MyServer, { MyServerEventEnum } from './Core'; +import { Connection, MyServer, MyServerEventEnum } from './Core'; import PlayerManager from './Biz/PlayerManager'; import RoomManager from './Biz/RoomManager'; -import Connection from './Core/Connection'; import { getTime, symlinkCommon } from './Utils'; import { ApiMsgEnum, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common'; diff --git a/apps/server/src/utils/binary.ts b/apps/server/src/utils/binary.ts index 87fb05b..30e201c 100644 --- a/apps/server/src/utils/binary.ts +++ b/apps/server/src/utils/binary.ts @@ -82,129 +82,129 @@ * @namespace */ class Base64 { - // Base64 encoding table - private b64 = new Array(64); - // Base64 decoding table - private s64 = new Array(123); - private invalidEncoding = "invalid encoding"; + // Base64 encoding table + private b64 = new Array(64); + // Base64 decoding table + private s64 = new Array(123); + private invalidEncoding = "invalid encoding"; - constructor() { - // 65..90, 97..122, 48..57, 43, 47 - for (var i = 0; i < 64;) { - this.s64[this.b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; - } - } + constructor() { + // 65..90, 97..122, 48..57, 43, 47 + for (var i = 0; i < 64;) { + this.s64[this.b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; + } + } - /** - * Calculates the byte length of a base64 encoded string. - * @param {string} string Base64 encoded string - * @returns {number} Byte length - */ - length(string: string): number { - var p = string.length; - if (!p) - return 0; - var n = 0; - while (--p % 4 > 1 && string.charAt(p) === "=") - ++n; - return Math.ceil(string.length * 3) / 4 - n; - }; + /** + * Calculates the byte length of a base64 encoded string. + * @param {string} string Base64 encoded string + * @returns {number} Byte length + */ + length(string: string): number { + var p = string.length; + if (!p) + return 0; + var n = 0; + while (--p % 4 > 1 && string.charAt(p) === "=") + ++n; + return Math.ceil(string.length * 3) / 4 - n; + }; - /** - * Encodes a buffer to a base64 encoded string. - * @param {DataView} buffer Source buffer - * @param {number} start Source start - * @param {number} end Source end - * @returns {string} Base64 encoded string - */ - read(buffer: DataView, start: number, end: number): string { - var parts = null, - chunk = []; - var i = 0, // output index - j = 0, // goto index - t; // temporary - while (start < end) { - var b = buffer.getUint8(start++); - switch (j) { - case 0: - chunk[i++] = this.b64[b >> 2]; - t = (b & 3) << 4; - j = 1; - break; - case 1: - chunk[i++] = this.b64[t | b >> 4]; - t = (b & 15) << 2; - j = 2; - break; - case 2: - chunk[i++] = this.b64[t | b >> 6]; - chunk[i++] = this.b64[b & 63]; - j = 0; - break; - } - if (i > 8191) { - (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); - i = 0; - } - } - if (j) { - chunk[i++] = this.b64[t]; - chunk[i++] = 61; - if (j === 1) - chunk[i++] = 61; - } - if (parts) { - if (i) - parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); - return parts.join(""); - } - return String.fromCharCode.apply(String, chunk.slice(0, i)); - }; + /** + * Encodes a buffer to a base64 encoded string. + * @param {DataView} buffer Source buffer + * @param {number} start Source start + * @param {number} end Source end + * @returns {string} Base64 encoded string + */ + read(buffer: DataView, start: number, end: number): string { + var parts = null, + chunk = []; + var i = 0, // output index + j = 0, // goto index + t; // temporary + while (start < end) { + var b = buffer.getUint8(start++); + switch (j) { + case 0: + chunk[i++] = this.b64[b >> 2]; + t = (b & 3) << 4; + j = 1; + break; + case 1: + chunk[i++] = this.b64[t | b >> 4]; + t = (b & 15) << 2; + j = 2; + break; + case 2: + chunk[i++] = this.b64[t | b >> 6]; + chunk[i++] = this.b64[b & 63]; + j = 0; + break; + } + if (i > 8191) { + (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); + i = 0; + } + } + if (j) { + chunk[i++] = this.b64[t]; + chunk[i++] = 61; + if (j === 1) + chunk[i++] = 61; + } + if (parts) { + if (i) + parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); + return parts.join(""); + } + return String.fromCharCode.apply(String, chunk.slice(0, i)); + }; - /** - * Decodes a base64 encoded string to a buffer. - * @param {string} string Source string - * @param {DataView} buffer Destination buffer - * @param {number} offset Destination offset - * @returns {number} Number of bytes written - * @throws {Error} If encoding is invalid - */ - write(string: string, buffer: DataView, offset: number): number { - var start = offset; - var j = 0, // goto index - t; // temporary - for (var i = 0; i < string.length;) { - var c = string.charCodeAt(i++); - if (c === 61 && j > 1) - break; - if ((c = this.s64[c]) === undefined) - throw Error(this.invalidEncoding); - switch (j) { - case 0: - t = c; - j = 1; - break; - case 1: - buffer.setUint8(offset++, t << 2 | (c & 48) >> 4); - t = c; - j = 2; - break; - case 2: - buffer.setUint8(offset++, (t & 15) << 4 | (c & 60) >> 2); - t = c; - j = 3; - break; - case 3: - buffer.setUint8(offset++, (t & 3) << 6 | c); - j = 0; - break; - } - } - if (j === 1) - throw Error(this.invalidEncoding); - return offset - start; - }; + /** + * Decodes a base64 encoded string to a buffer. + * @param {string} string Source string + * @param {DataView} buffer Destination buffer + * @param {number} offset Destination offset + * @returns {number} Number of bytes written + * @throws {Error} If encoding is invalid + */ + write(string: string, buffer: DataView, offset: number): number { + var start = offset; + var j = 0, // goto index + t; // temporary + for (var i = 0; i < string.length;) { + var c = string.charCodeAt(i++); + if (c === 61 && j > 1) + break; + if ((c = this.s64[c]) === undefined) + throw Error(this.invalidEncoding); + switch (j) { + case 0: + t = c; + j = 1; + break; + case 1: + buffer.setUint8(offset++, t << 2 | (c & 48) >> 4); + t = c; + j = 2; + break; + case 2: + buffer.setUint8(offset++, (t & 15) << 4 | (c & 60) >> 2); + t = c; + j = 3; + break; + case 3: + buffer.setUint8(offset++, (t & 3) << 6 | c); + j = 0; + break; + } + } + if (j === 1) + throw Error(this.invalidEncoding); + return offset - start; + }; } const base64 = new Base64(); @@ -216,404 +216,404 @@ const base64 = new Base64(); * @namespace */ class UTF8 { - /** - * Calculates the UTF8 byte length of a string. - */ - length(string: string): number { - var len = 0, - c = 0; - for (var i = 0; i < string.length; ++i) { - c = string.charCodeAt(i); - if (c < 128) - len += 1; - else if (c < 2048) - len += 2; - else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { - ++i; - len += 4; - } else - len += 3; - } - return len; - }; + /** + * Calculates the UTF8 byte length of a string. + */ + length(string: string): number { + var len = 0, + c = 0; + for (var i = 0; i < string.length; ++i) { + c = string.charCodeAt(i); + if (c < 128) + len += 1; + else if (c < 2048) + len += 2; + else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { + ++i; + len += 4; + } else + len += 3; + } + return len; + }; - /** - * Reads UTF8 bytes as a string. - */ - read(buffer: DataView, start: number, end: number): string { - var len = end - start; - if (len < 1) - return ""; - var parts = null, - chunk = [], - i = 0, // char offset - t; // temporary - while (start < end) { - t = buffer.getUint8(start++); - if (t < 128) - chunk[i++] = t; - else if (t > 191 && t < 224) - chunk[i++] = (t & 31) << 6 | buffer.getUint8(start++) & 63; - else if (t > 239 && t < 365) { - t = ((t & 7) << 18 | (buffer.getUint8(start++) & 63) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63) - 0x10000; - chunk[i++] = 0xD800 + (t >> 10); - chunk[i++] = 0xDC00 + (t & 1023); - } else - chunk[i++] = (t & 15) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63; - if (i > 8191) { - (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); - i = 0; - } - } - if (parts) { - if (i) - parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); - return parts.join(""); - } - return String.fromCharCode.apply(String, chunk.slice(0, i)); - }; + /** + * Reads UTF8 bytes as a string. + */ + read(buffer: DataView, start: number, end: number): string { + var len = end - start; + if (len < 1) + return ""; + var parts = null, + chunk = [], + i = 0, // char offset + t; // temporary + while (start < end) { + t = buffer.getUint8(start++); + if (t < 128) + chunk[i++] = t; + else if (t > 191 && t < 224) + chunk[i++] = (t & 31) << 6 | buffer.getUint8(start++) & 63; + else if (t > 239 && t < 365) { + t = ((t & 7) << 18 | (buffer.getUint8(start++) & 63) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63) - 0x10000; + chunk[i++] = 0xD800 + (t >> 10); + chunk[i++] = 0xDC00 + (t & 1023); + } else + chunk[i++] = (t & 15) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63; + if (i > 8191) { + (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); + i = 0; + } + } + if (parts) { + if (i) + parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); + return parts.join(""); + } + return String.fromCharCode.apply(String, chunk.slice(0, i)); + }; - /** - * Writes a string as UTF8 bytes. - */ - write(string: string, buffer: DataView, offset: number): number { - var start = offset, - c1, // character 1 - c2; // character 2 - for (var i = 0; i < string.length; ++i) { - c1 = string.charCodeAt(i); - if (c1 < 128) { - buffer.setUint8(offset++, c1); - } else if (c1 < 2048) { - buffer.setUint8(offset++, c1 >> 6 | 192); - buffer.setUint8(offset++, c1 & 63 | 128); - } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { - c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); - ++i; - buffer.setUint8(offset++, c1 >> 18 | 240); - buffer.setUint8(offset++, c1 >> 12 & 63 | 128); - buffer.setUint8(offset++, c1 >> 6 & 63 | 128); - buffer.setUint8(offset++, c1 & 63 | 128); - } else { - buffer.setUint8(offset++, c1 >> 12 | 224); - buffer.setUint8(offset++, c1 >> 6 & 63 | 128); - buffer.setUint8(offset++, c1 & 63 | 128); - } - } - return offset - start; - }; + /** + * Writes a string as UTF8 bytes. + */ + write(string: string, buffer: DataView, offset: number): number { + var start = offset, + c1, // character 1 + c2; // character 2 + for (var i = 0; i < string.length; ++i) { + c1 = string.charCodeAt(i); + if (c1 < 128) { + buffer.setUint8(offset++, c1); + } else if (c1 < 2048) { + buffer.setUint8(offset++, c1 >> 6 | 192); + buffer.setUint8(offset++, c1 & 63 | 128); + } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { + c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); + ++i; + buffer.setUint8(offset++, c1 >> 18 | 240); + buffer.setUint8(offset++, c1 >> 12 & 63 | 128); + buffer.setUint8(offset++, c1 >> 6 & 63 | 128); + buffer.setUint8(offset++, c1 & 63 | 128); + } else { + buffer.setUint8(offset++, c1 >> 12 | 224); + buffer.setUint8(offset++, c1 >> 6 & 63 | 128); + buffer.setUint8(offset++, c1 & 63 | 128); + } + } + return offset - start; + }; } const utf8 = new UTF8(); class Encode { - private buffer: ArrayBuffer = null; - private view: DataView = null; - private index: number = 0; + private buffer: ArrayBuffer = null; + private view: DataView = null; + private index: number = 0; - constructor(length: number) { - this.buffer = new ArrayBuffer(length) - this.view = new DataView(this.buffer); - this.index = 0; - } + constructor(length: number) { + this.buffer = new ArrayBuffer(length) + this.view = new DataView(this.buffer); + this.index = 0; + } - Int8(data: number) { - if (!isNumber(data)) data = 0; - return this.view.setInt8(this.index++, data); - } + Int8(data: number) { + if (!isNumber(data)) data = 0; + return this.view.setInt8(this.index++, data); + } - Uint8(data: number) { - if (!isNumber(data)) data = 0; - return this.view.setUint8(this.index++, data); - } + Uint8(data: number) { + if (!isNumber(data)) data = 0; + return this.view.setUint8(this.index++, data); + } - Int16(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setInt16(this.index, data); - this.index += 2; - return value; - } + Int16(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setInt16(this.index, data); + this.index += 2; + return value; + } - Uint16(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setUint16(this.index, data); - this.index += 2; - return value; - } + Uint16(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setUint16(this.index, data); + this.index += 2; + return value; + } - Int32(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setInt32(this.index, data); - this.index += 4; - return value; - } + Int32(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setInt32(this.index, data); + this.index += 4; + return value; + } - Uint32(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setUint32(this.index, data); - this.index += 4; - return value; - } + Uint32(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setUint32(this.index, data); + this.index += 4; + return value; + } - Float32(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setFloat32(this.index, data); - this.index += 4; - return value; - } + Float32(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setFloat32(this.index, data); + this.index += 4; + return value; + } - Float64(data: number) { - if (!isNumber(data)) data = 0; - var value = this.view.setFloat64(this.index, data); - this.index += 8; - return value; - } + Float64(data: number) { + if (!isNumber(data)) data = 0; + var value = this.view.setFloat64(this.index, data); + this.index += 8; + return value; + } - Boolean(data) { - return this.Uint8(data ? 1 : 0); - } + Boolean(data) { + return this.Uint8(data ? 1 : 0); + } - String(string) { - if (!isString(string)) string = ''; + String(string) { + if (!isString(string)) string = ''; - const len = utf8.write(string, this.view, this.index + 2); - this.Uint16(len); - this.index += len; - } + const len = utf8.write(string, this.view, this.index + 2); + this.Uint16(len); + this.index += len; + } - Base64(string) { - if (!isBase64(string)) string = ''; + Base64(string) { + if (!isBase64(string)) string = ''; - const len = base64.write(string, this.view, this.index + 2); - this.Uint16(len); - this.index += len; - } + const len = base64.write(string, this.view, this.index + 2); + this.Uint16(len); + this.index += len; + } - Array(array) { - if (isArray(array) && !isEmpty(array)) { - return this.String(JSON.stringify(array)); - } else { - return this.String(''); - } - } + Array(array) { + if (isArray(array) && !isEmpty(array)) { + return this.String(JSON.stringify(array)); + } else { + return this.String(''); + } + } - Object(obj) { - if (isMap(obj) && !isEmpty(obj)) { - return this.String(JSON.stringify(obj)); - } else { - return this.String(''); - } - } + Object(obj) { + if (isMap(obj) && !isEmpty(obj)) { + return this.String(JSON.stringify(obj)); + } else { + return this.String(''); + } + } - Buffer() { - return this.buffer; - } + Buffer() { + return this.buffer; + } } class Decode { - private view: DataView = null; - private index: number = 0; + private view: DataView = null; + private index: number = 0; - constructor(buffer: ArrayBuffer) { - this.view = new DataView(buffer); - this.index = 0; - } + constructor(buffer: ArrayBuffer) { + this.view = new DataView(buffer); + this.index = 0; + } - Int8() { - return this.view.getInt8(this.index++); - } + Int8() { + return this.view.getInt8(this.index++); + } - Uint8() { - return this.view.getUint8(this.index++); - } + Uint8() { + return this.view.getUint8(this.index++); + } - Int16() { - const value = this.view.getInt16(this.index); - this.index += 2; - return value; - } + Int16() { + const value = this.view.getInt16(this.index); + this.index += 2; + return value; + } - Uint16() { - const value = this.view.getUint16(this.index); - this.index += 2; - return value; - } + Uint16() { + const value = this.view.getUint16(this.index); + this.index += 2; + return value; + } - Int32() { - const value = this.view.getInt32(this.index); - this.index += 4; - return value; - } + Int32() { + const value = this.view.getInt32(this.index); + this.index += 4; + return value; + } - Uint32() { - const value = this.view.getUint32(this.index); - this.index += 4; - return value; - } + Uint32() { + const value = this.view.getUint32(this.index); + this.index += 4; + return value; + } - Float32() { - const value = this.view.getFloat32(this.index); - this.index += 4; - return value; - } + Float32() { + const value = this.view.getFloat32(this.index); + this.index += 4; + return value; + } - Float64() { - const value = this.view.getFloat64(this.index); - this.index += 8; - return value; - } + Float64() { + const value = this.view.getFloat64(this.index); + this.index += 8; + return value; + } - Boolean() { - return !!this.Uint8(); - } + Boolean() { + return !!this.Uint8(); + } - String() { - const len = this.Uint16(); - this.index += len; - return utf8.read(this.view, this.index - len, this.index); - } + String() { + const len = this.Uint16(); + this.index += len; + return utf8.read(this.view, this.index - len, this.index); + } - Base64() { - const len = this.Uint16(); - this.index += len; - return base64.read(this.view, this.index - len, this.index); - } + Base64() { + const len = this.Uint16(); + this.index += len; + return base64.read(this.view, this.index - len, this.index); + } - Array() { - const str = this.String(); - return str ? JSON.parse(str) : []; - } + Array() { + const str = this.String(); + return str ? JSON.parse(str) : []; + } - Object() { - const str = this.String(); - return str ? JSON.parse(str) : {}; - } + Object() { + const str = this.String(); + return str ? JSON.parse(str) : {}; + } } const getType = function (param) { - return Object.prototype.toString.call(param).slice(8, -1).toLowerCase(); + return Object.prototype.toString.call(param).slice(8, -1).toLowerCase(); } const isObject = function (param) { - return param && typeof param === 'object'; + return param && typeof param === 'object'; } const isArray = function (param) { - return getType(param) === 'array'; + return getType(param) === 'array'; } const isMap = function (param) { - return getType(param) === 'object'; + return getType(param) === 'object'; } const isString = function (param) { - return getType(param) === 'string'; + return getType(param) === 'string'; } const isNumber = function (param) { - return getType(param) === 'number'; + return getType(param) === 'number'; } const isBoolean = function (param) { - return getType(param) === 'boolean'; + return getType(param) === 'boolean'; } const isBase64 = function (param) { - return isString(param) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(param); + return isString(param) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(param); } function stringStartsWith(str1: string, str2: string) { - if (str1 === str2) { - return true; - } - for (let index = 0; index < str2.length; index++) { - if (str1[index] !== str2[index]) { - return false; - } - } - return true; + if (str1 === str2) { + return true; + } + for (let index = 0; index < str2.length; index++) { + if (str1[index] !== str2[index]) { + return false; + } + } + return true; } function isEmpty(obj) { - if (isArray(obj)) { - return !obj.length; - } else if (isMap(obj)) { - for (const key in obj) { - return false; - } - } - return true; + if (isArray(obj)) { + return !obj.length; + } else if (isMap(obj)) { + for (const key in obj) { + return false; + } + } + return true; } function compareStr(str1: string, str2: string) { - if (str1 === str2) { - return 0; - } - if (str1.length > str2.length) { - return 1; - } - if (str1.length < str2.length) { - return -1; - } + if (str1 === str2) { + return 0; + } + if (str1.length > str2.length) { + return 1; + } + if (str1.length < str2.length) { + return -1; + } - for (let i = 0, code1 = 0, code2 = 0; i < str1.length; i++) { - if (str2.length <= i) { - return 1; - } else { - code1 = str1.charCodeAt(i); - code2 = str2.charCodeAt(i); - if (code1 > code2) { - return 1; - } else if (code1 < code2) { - return -1; - } - } - } - return 0; + for (let i = 0, code1 = 0, code2 = 0; i < str1.length; i++) { + if (str2.length <= i) { + return 1; + } else { + code1 = str1.charCodeAt(i); + code2 = str2.charCodeAt(i); + if (code1 > code2) { + return 1; + } else if (code1 < code2) { + return -1; + } + } + } + return 0; } function sortKeys(obj) { - if (isMap(obj)) { - let index = 0; - const keys: string[] = []; - for (const key in obj) { - for (index = keys.length - 1; index >= 0; index--) { - if (compareStr(key, keys[index]) >= 0) { - break; - } - } - if (index === keys.length - 1) { - keys.push(key); - } else { - keys.splice(index + 1, 0, key); - } - } - return keys; - } else if (isArray(obj)) { - return obj.map(function (v, k) { - return k; - }) - } + if (isMap(obj)) { + let index = 0; + const keys: string[] = []; + for (const key in obj) { + for (index = keys.length - 1; index >= 0; index--) { + if (compareStr(key, keys[index]) >= 0) { + break; + } + } + if (index === keys.length - 1) { + keys.push(key); + } else { + keys.splice(index + 1, 0, key); + } + } + return keys; + } else if (isArray(obj)) { + return obj.map(function (v, k) { + return k; + }) + } - return []; + return []; } function realType(type) { - if (isObject(type)) { - return type; - } - return protoCache[type] || type; + if (isObject(type)) { + return type; + } + return protoCache[type] || type; } const singleArrayPrefix = 'SingleArray'; function isSingleArray(str: string) { - return isString(str) && stringStartsWith(str, singleArrayPrefix); + return isString(str) && stringStartsWith(str, singleArrayPrefix); } function SingleArrayProto(str: string) { - const stringify = str.slice(singleArrayPrefix.length + 1, -1); - return JSON.parse(stringify); + const stringify = str.slice(singleArrayPrefix.length + 1, -1); + return JSON.parse(stringify); } /** @@ -621,138 +621,138 @@ function SingleArrayProto(str: string) { * @param proto */ export const singleArray = function (proto) { - return `${singleArrayPrefix}(${JSON.stringify(proto)})`; + return `${singleArrayPrefix}(${JSON.stringify(proto)})`; } function DataLen(data: any, proto: any) { - proto = realType(proto); + proto = realType(proto); - let length = 0; - if (isMap(proto)) { - if (!isMap(data)) data = {}; - for (const key in proto) { - length += DataLen(data[key], proto[key]); - } - } else if (isArray(proto)) { - if (!isArray(data)) data = []; - proto.forEach(function (type, index) { - length += DataLen(data[index], type); - }) - } else if (proto === 'String') { - // 如果是String的话,固定开头有2字节记录字符串长度 - length += 2; - if (isString(data)) length += utf8.length(data); - } else if (proto === 'Object' || proto === 'Array') { - // Object和Array类型也会将数据通过JSON.stringify转成String格式 - length += 2; - if (!isEmpty(data)) length += utf8.length(JSON.stringify(data)); - } else if (proto === 'Base64') { - // 如果是Base64的话,固定开头有2字节记录字符串长度 - length += 2; - if (isBase64(data)) length += base64.length(data); - } else if (isSingleArray(proto)) { - // 如果是SingleArray的话,固定开头有2字节记录数组长度 - length += 2; - if (!isArray(data)) data = []; - proto = realType(SingleArrayProto(proto)); - data.forEach(function (value) { - length += DataLen(value, proto); - }) - } else if (TypeByte[proto]) { - length += TypeByte[proto]; - } else { - throw new Error("'proto' is bad"); - } + let length = 0; + if (isMap(proto)) { + if (!isMap(data)) data = {}; + for (const key in proto) { + length += DataLen(data[key], proto[key]); + } + } else if (isArray(proto)) { + if (!isArray(data)) data = []; + proto.forEach(function (type, index) { + length += DataLen(data[index], type); + }) + } else if (proto === 'String') { + // 如果是String的话,固定开头有2字节记录字符串长度 + length += 2; + if (isString(data)) length += utf8.length(data); + } else if (proto === 'Object' || proto === 'Array') { + // Object和Array类型也会将数据通过JSON.stringify转成String格式 + length += 2; + if (!isEmpty(data)) length += utf8.length(JSON.stringify(data)); + } else if (proto === 'Base64') { + // 如果是Base64的话,固定开头有2字节记录字符串长度 + length += 2; + if (isBase64(data)) length += base64.length(data); + } else if (isSingleArray(proto)) { + // 如果是SingleArray的话,固定开头有2字节记录数组长度 + length += 2; + if (!isArray(data)) data = []; + proto = realType(SingleArrayProto(proto)); + data.forEach(function (value) { + length += DataLen(value, proto); + }) + } else if (TypeByte[proto]) { + length += TypeByte[proto]; + } else { + throw new Error("'proto' is bad"); + } - return length; + return length; } function encodeData(encode: Encode, data: any, proto: any) { - proto = realType(proto); + proto = realType(proto); - if (isMap(proto)) { - if (!isMap(data)) data = {}; - sortKeys(proto).forEach(function (key) { - encodeData(encode, data[key], proto[key]); - }) - } else if (isArray(proto)) { - if (!isArray(data)) data = []; - proto.forEach(function (type, index) { - encodeData(encode, data[index], type); - }) - } else if (isSingleArray(proto)) { - if (!isArray(data)) data = []; - encode.Uint16(data.length); - proto = realType(SingleArrayProto(proto)); - data.forEach(function (value) { - encodeData(encode, value, proto); - }) - } else { - encode[proto](data); - } + if (isMap(proto)) { + if (!isMap(data)) data = {}; + sortKeys(proto).forEach(function (key) { + encodeData(encode, data[key], proto[key]); + }) + } else if (isArray(proto)) { + if (!isArray(data)) data = []; + proto.forEach(function (type, index) { + encodeData(encode, data[index], type); + }) + } else if (isSingleArray(proto)) { + if (!isArray(data)) data = []; + encode.Uint16(data.length); + proto = realType(SingleArrayProto(proto)); + data.forEach(function (value) { + encodeData(encode, value, proto); + }) + } else { + encode[proto](data); + } } function decodeData(decode: Decode, proto: any) { - proto = realType(proto); + proto = realType(proto); - if (isMap(proto)) { - const obj = {}; - sortKeys(proto).forEach(function (key) { - obj[key] = decodeData(decode, proto[key]); - }); - return obj; - } else if (isArray(proto)) { - return proto.map(function (type) { - return decodeData(decode, type); - }); - } else if (isSingleArray(proto)) { - const arr = []; - const len = decode.Uint16(); - proto = realType(SingleArrayProto(proto)); - for (let index = 0; index < len; index++) { - arr.push(decodeData(decode, proto)); - } - return arr; - } else { - return decode[proto](); - } + if (isMap(proto)) { + const obj = {}; + sortKeys(proto).forEach(function (key) { + obj[key] = decodeData(decode, proto[key]); + }); + return obj; + } else if (isArray(proto)) { + return proto.map(function (type) { + return decodeData(decode, type); + }); + } else if (isSingleArray(proto)) { + const arr = []; + const len = decode.Uint16(); + proto = realType(SingleArrayProto(proto)); + for (let index = 0; index < len; index++) { + arr.push(decodeData(decode, proto)); + } + return arr; + } else { + return decode[proto](); + } } const TypeByte = { - 'Int8': 1, - 'Uint8': 1, - 'Int16': 2, - 'Uint16': 2, - 'Int32': 4, - 'Uint32': 4, - 'Float32': 4, - 'Float64': 8, - 'BigInt64': 8, - 'BigUint64': 8, - 'Boolean': 1, - 'String': 1, - 'Base64': 1, - 'Array': 1, - 'Object': 1 + 'Int8': 1, + 'Uint8': 1, + 'Int16': 2, + 'Uint16': 2, + 'Int32': 4, + 'Uint32': 4, + 'Float32': 4, + 'Float64': 8, + 'BigInt64': 8, + 'BigUint64': 8, + 'Boolean': 1, + 'String': 1, + 'Base64': 1, + 'Array': 1, + 'Object': 1 } export const Type = { - 'Int8': 'Int8', // 1byte -128 to 127 - 'Uint8': 'Uint8', // 1byte 0 to 255 - 'Uint8Clamped': 'Uint8', // 1byte 0 to 255 - 'Int16': 'Int16', // 2byte -32768 to 32767 - 'Uint16': 'Uint16', // 2byte 0 to 65535 - 'Int32': 'Int32', // 4byte -2147483648 to 2147483647 - 'Uint32': 'Uint32', // 4byte 0 to 4294967295 - 'Float32': 'Float32', // 4byte 1.2x10^-38 to 3.4x10^38 - 'Float64': 'Float64', // 8byte 5.0x10^-324 to 1.8x10^308 - 'BigInt64': 'BigInt64', // 8byte -2^63 to (2^63)-1 - 'BigUint64': 'BigUint64', // 8byte 0 to (2^64)-1 - 'Boolean': 'Boolean', // 1byte 0 to 255 - 'String': 'String', // 1byte 0 to 255 - 'Base64': 'Base64', // 1byte 0 to 255 - 'Array': 'Array', // 1byte 0 to 255 - 'Object': 'Object' // 1byte 0 to 255 + 'Int8': 'Int8', // 1byte -128 to 127 + 'Uint8': 'Uint8', // 1byte 0 to 255 + 'Uint8Clamped': 'Uint8', // 1byte 0 to 255 + 'Int16': 'Int16', // 2byte -32768 to 32767 + 'Uint16': 'Uint16', // 2byte 0 to 65535 + 'Int32': 'Int32', // 4byte -2147483648 to 2147483647 + 'Uint32': 'Uint32', // 4byte 0 to 4294967295 + 'Float32': 'Float32', // 4byte 1.2x10^-38 to 3.4x10^38 + 'Float64': 'Float64', // 8byte 5.0x10^-324 to 1.8x10^308 + 'BigInt64': 'BigInt64', // 8byte -2^63 to (2^63)-1 + 'BigUint64': 'BigUint64', // 8byte 0 to (2^64)-1 + 'Boolean': 'Boolean', // 1byte 0 to 255 + 'String': 'String', // 1byte 0 to 255 + 'Base64': 'Base64', // 1byte 0 to 255 + 'Array': 'Array', // 1byte 0 to 255 + 'Object': 'Object' // 1byte 0 to 255 } /** @@ -760,16 +760,16 @@ export const Type = { * 开头2字节用来存储proto的id */ export const encode = function (obj: Object, id: number | string) { - const proto = protoCache[id]; - if (proto) { - const len = DataLen(obj, proto); - const encode = new Encode(len + 2); - encode.Uint16(Number(id)); - encodeData(encode, obj, proto); - return encode.Buffer(); - } else { - throw new Error("encode error: 'id' is bad"); - } + const proto = protoCache[id]; + if (proto) { + const len = DataLen(obj, proto); + const encode = new Encode(len + 2); + encode.Uint16(Number(id)); + encodeData(encode, obj, proto); + return encode.Buffer(); + } else { + throw new Error("encode error: 'id' is bad"); + } } /** @@ -777,14 +777,14 @@ export const encode = function (obj: Object, id: number | string) { * 开头2字节代表proto的id */ export const decode = function (buffer: ArrayBuffer) { - const decode = new Decode(buffer); - const id = decode.Uint16(); - const proto = protoCache[id]; - if (proto) { - return decodeData(decode, proto); - } else { - throw new Error("decode error: 'buffer' is bad"); - } + const decode = new Decode(buffer); + const id = decode.Uint16(); + const proto = protoCache[id]; + if (proto) { + return decodeData(decode, proto); + } else { + throw new Error("decode error: 'buffer' is bad"); + } } /** @@ -797,25 +797,25 @@ const protoCache = {} * id: 必须是个正整数(或正整数字符串), 取值范围[0,65535] */ export const registerProto = function (id: number | string, proto: any) { - if (typeof id === 'string') id = Number(id); + if (typeof id === 'string') id = Number(id); - if (isNumber(id) && Math.floor(id) === id && id >= 0 && id <= 65535 && !Type[id]) { - protoCache[id] = proto; - } else { - throw new Error("registerProto error: 'id' is bad"); - } + if (isNumber(id) && Math.floor(id) === id && id >= 0 && id <= 65535 && !Type[id]) { + protoCache[id] = proto; + } else { + throw new Error("registerProto error: 'id' is bad"); + } } export const registerProtoMap = function (protoMap: any) { - if (isMap(protoMap)) { - for (const id in protoMap) { - registerProto(id, protoMap[id]); - } - } else { - throw new Error("registerProtoMap error: 'protoMap' is bad"); - } + if (isMap(protoMap)) { + for (const id in protoMap) { + registerProto(id, protoMap[id]); + } + } else { + throw new Error("registerProtoMap error: 'protoMap' is bad"); + } } export const protoToJson = function () { - return JSON.stringify(protoCache); + return JSON.stringify(protoCache); } \ No newline at end of file diff --git a/apps/server/src/utils/index.ts b/apps/server/src/utils/index.ts index 69dbb06..d5a5868 100644 --- a/apps/server/src/utils/index.ts +++ b/apps/server/src/utils/index.ts @@ -1,5 +1,6 @@ import fs from 'fs-extra' import path from 'path' +import { ApiMsgEnum, InputTypeEnum, strdecode } from '../Common' export const getTime = () => new Date().toLocaleString().split("├")[0] @@ -34,4 +35,79 @@ export const copyCommon = async () => { // copy await fs.copy(src, dst) console.log('同步成功!') +} + +export const binaryDecode = (buffer: Buffer) => { + let index = 0 + const type = buffer.readUint8(index++) + + if (type === ApiMsgEnum.MsgClientSync) { + const inputType = buffer.readUint8(index++) + if (inputType === InputTypeEnum.ActorMove) { + const id = buffer.readUint8(index++) + const directionX = buffer.readFloatBE(index) + index += 4 + const directionY = buffer.readFloatBE(index) + index += 4 + const dt = buffer.readFloatBE(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.ActorMove, + id, + direction: { + x: directionX, + y: directionY, + }, + dt + } + } + + return msg + } else if (inputType === InputTypeEnum.WeaponShoot) { + const id = buffer.readUint8(index++) + const positionX = buffer.readFloatBE(index) + index += 4 + const positionY = buffer.readFloatBE(index) + index += 4 + const directionX = buffer.readFloatBE(index) + index += 4 + const directionY = buffer.readFloatBE(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.WeaponShoot, + id, + position: { + x: positionX, + y: positionY, + }, + direction: { + x: directionX, + y: directionY, + }, + } + } + + return msg + } else { + const dt = buffer.readFloatBE(index) + index += 4 + const msg = { + name: ApiMsgEnum.MsgClientSync, + data: { + type: InputTypeEnum.TimePast, + dt, + } + } + return msg + } + } else { + return { + name: type, + data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1)))) + } + } } \ No newline at end of file diff --git a/test.js b/test.js index 42a0feb..016d796 100644 --- a/test.js +++ b/test.js @@ -1,2 +1,30 @@ -const a = new Date() -console.log(a.toLocaleString().split("├")[0]); \ No newline at end of file +const msg = JSON.stringify({ + a: 123, + b: true, + c: "456" +}) + +const strencode = (str) => { + let byteArray = []; + for (let i = 0; i < str.length; i++) { + let charCode = str.charCodeAt(i); + if (charCode <= 0x7f) { + byteArray.push(charCode); + } else if (charCode <= 0x7ff) { + byteArray.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f)); + } else if (charCode <= 0xffff) { + byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)); + } else { + byteArray.push(0xf0 | (charCode >> 18), 0x80 | ((charCode & 0x3f000) >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)); + } + } + return byteArray; +} + +var arr = strencode(msg) +var buffer = Buffer.from(msg) + +console.log(buffer) +for (let i = 0; i < arr.length; i++) { + console.log(buffer[i], arr[i]); +}