This commit is contained in:
sli97
2022-12-08 21:14:02 +08:00
parent 29104d4bed
commit 81a9a5a2f8
51 changed files with 1694 additions and 2815 deletions

View File

@@ -1,61 +1,60 @@
export interface IPlayer {
id: number, nickname: string, rid: number
id: number;
nickname: string;
rid: number;
}
export interface IRoom {
id: number, players: Array<IPlayer>
id: number;
players: Array<IPlayer>;
}
export interface IApiPlayerListReq {
}
export interface IApiPlayerListReq {}
export interface IApiPlayerListRes {
list: Array<IPlayer>
list: Array<IPlayer>;
}
export interface IApiPlayerJoinReq {
nickname: string
nickname: string;
}
export interface IApiPlayerJoinRes {
player: IPlayer
player: IPlayer;
}
export interface IApiRoomListReq {
}
export interface IApiRoomListReq {}
export interface IApiRoomListRes {
list: Array<IRoom>
list: Array<IRoom>;
}
export interface IApiRoomCreateReq {
}
export interface IApiRoomCreateReq {}
export interface IApiRoomCreateRes {
room: IRoom
room: IRoom;
}
export interface IApiRoomJoinReq {
rid: number
rid: number;
}
export interface IApiRoomJoinRes {
room: IRoom
room: IRoom;
}
export interface IApiRoomLeaveReq {
}
export interface IApiRoomLeaveReq {}
export interface IApiRoomLeaveRes { }
export interface IApiRoomLeaveRes {}
export interface IApiGameStartReq {
rid: number
rid: number;
}
export interface IApiGameStartRes { }
export interface IApiGameStartRes {}
export interface IApiGameEndReq {
rid: number
rid: number;
}
export interface IApiGameEndRes { }
export interface IApiGameEndRes {}

View File

@@ -1,91 +1,100 @@
import { ApiMsgEnum, InputTypeEnum } from "./Enum";
import { strdecode, strencode, toFixed } from "./Utils";
const encodeActorMove = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => {
view.setUint8(index++, data.type)
view.setUint8(index++, data.id)
view.setFloat32(index, data.direction.x)
const encodeActorMove = (input: any, view: DataView, index: number) => {
view.setUint8(index++, input.type)
view.setUint8(index++, input.id)
view.setFloat32(index, input.direction.x)
index += 4
view.setFloat32(index, data.direction.y)
view.setFloat32(index, input.direction.y)
index += 4
view.setFloat32(index, data.dt)
view.setFloat32(index, input.dt)
index += 4
}
const encodeWeaponShoot = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => {
view.setUint8(index++, data.type)
view.setUint8(index++, data.owner)
view.setFloat32(index, data.position.x)
const encodeWeaponShoot = (input: any, view: DataView, index: number) => {
view.setUint8(index++, input.type)
view.setUint8(index++, input.owner)
view.setFloat32(index, input.position.x)
index += 4
view.setFloat32(index, data.position.y)
view.setFloat32(index, input.position.y)
index += 4
view.setFloat32(index, data.direction.x)
view.setFloat32(index, input.direction.x)
index += 4
view.setFloat32(index, data.direction.y)
view.setFloat32(index, input.direction.y)
index += 4
}
export const encdoeTimePast = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => {
view.setUint8(index++, data.type)
view.setFloat32(index, data.dt)
export const encodeTimePast = (input: any, view: DataView, index: number) => {
view.setUint8(index++, input.type)
view.setFloat32(index, input.dt)
index += 4
}
export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => {
if (proto === ApiMsgEnum.MsgClientSync) {
if (data.type === InputTypeEnum.ActorMove) {
export const binaryEncode = (name: ApiMsgEnum, data: any): DataView => {
if (name === ApiMsgEnum.MsgClientSync) {
//name 1字节 + frameId 4字节 + 数据长度 n 字节
const { frameId, input } = data
if (input.type === InputTypeEnum.ActorMove) {
let index = 0
const ab = new ArrayBuffer(3 + 12)
const ab = new ArrayBuffer(1 + 4 + 14)
const view = new DataView(ab)
view.setUint8(index++, proto)
encodeActorMove(proto, data, view, index)
view.setUint8(index++, name)
view.setUint32(index, frameId)
index += 4
encodeActorMove(input, view, index)
return view
} else if (data.type === InputTypeEnum.WeaponShoot) {
} else if (input.type === InputTypeEnum.WeaponShoot) {
let index = 0
const ab = new ArrayBuffer(3 + 16)
const ab = new ArrayBuffer(1 + 4 + 18)
const view = new DataView(ab)
view.setUint8(index++, proto)
encodeWeaponShoot(proto, data, view, index)
view.setUint8(index++, name)
view.setUint32(index, frameId)
index += 4
encodeWeaponShoot(input, view, index)
return view
} else {
let index = 0
const ab = new ArrayBuffer(2 + 4)
const ab = new ArrayBuffer(1 + 4 + 5)
const view = new DataView(ab)
view.setUint8(index++, proto)
encdoeTimePast(proto, data, view, index)
view.setUint8(index++, name)
view.setUint32(index, frameId)
index += 4
encodeTimePast(input, view, index)
return view
}
} else if (proto === ApiMsgEnum.MsgServerSync) {
} else if (name === ApiMsgEnum.MsgServerSync) {
const { lastFrameId, inputs } = data
let total = 0
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.type === InputTypeEnum.ActorMove) {
for (const input of inputs) {
if (input.type === InputTypeEnum.ActorMove) {
total += 14
} else if (item.type === InputTypeEnum.WeaponShoot) {
} else if (input.type === InputTypeEnum.WeaponShoot) {
total += 18
} else {
total += 5
}
}
const ab = new ArrayBuffer(1 + 1 + total)
//name 1字节 + lastFrameId 4字节 + 数组长度 1字节 + 数据长度 n 字节
const ab = new ArrayBuffer(1 + 4 + 1 + total)
const view = new DataView(ab)
let index = 0
view.setUint8(index++, proto)
view.setUint8(index++, data.length)
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.type === InputTypeEnum.ActorMove) {
encodeActorMove(proto, item, view, index)
view.setUint8(index++, name)
view.setUint32(index, lastFrameId)
index += 4
view.setUint8(index++, inputs.length)
for (const input of inputs) {
if (input.type === InputTypeEnum.ActorMove) {
encodeActorMove(input, view, index)
index += 14
} else if (item.type === InputTypeEnum.WeaponShoot) {
encodeWeaponShoot(proto, item, view, index)
} else if (input.type === InputTypeEnum.WeaponShoot) {
encodeWeaponShoot(input, view, index)
index += 18
} else {
encdoeTimePast(proto, item, view, index)
encodeTimePast(input, view, index)
index += 5
}
}
return view
} else {
let index = 0
@@ -93,7 +102,7 @@ export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => {
const ta = strencode(str)
const ab = new ArrayBuffer(ta.length + 1)
const view = new DataView(ab)
view.setUint8(index++, proto)
view.setUint8(index++, name)
for (let i = 0; i < ta.length; i++) {
view.setUint8(index++, ta[i])
}
@@ -109,20 +118,17 @@ const decodeActorMove = (view: DataView, index: number) => {
index += 4
const dt = toFixed(view.getFloat32(index))
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.ActorMove,
id,
direction: {
x: directionX,
y: directionY,
},
dt
}
const input = {
type: InputTypeEnum.ActorMove,
id,
direction: {
x: directionX,
y: directionY,
},
dt
}
return msg
return input
}
const decodeWeaponShoot = (view: DataView, index: number) => {
@@ -135,76 +141,96 @@ const decodeWeaponShoot = (view: DataView, index: number) => {
index += 4
const directionY = toFixed(view.getFloat32(index))
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.WeaponShoot,
owner,
position: {
x: positionX,
y: positionY,
},
direction: {
x: directionX,
y: directionY,
},
}
const input = {
type: InputTypeEnum.WeaponShoot,
owner,
position: {
x: positionX,
y: positionY,
},
direction: {
x: directionX,
y: directionY,
},
}
return msg
return input
}
const decodeTimePast = (view: DataView, index: number) => {
const dt = toFixed(view.getFloat32(index))
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.TimePast,
dt,
}
const input = {
type: InputTypeEnum.TimePast,
dt,
}
return msg
return input
}
export const binaryDecode = (buffer: ArrayBuffer) => {
let index = 0
const view = new DataView(buffer)
const proto = view.getUint8(index++)
const name = view.getUint8(index++)
if (proto === ApiMsgEnum.MsgClientSync) {
if (name === ApiMsgEnum.MsgClientSync) {
const frameId = view.getUint32(index)
index += 4
const inputType = view.getUint8(index++)
if (inputType === InputTypeEnum.ActorMove) {
return decodeActorMove(view, index)
const input = decodeActorMove(view, index)
return {
name,
data: {
frameId,
input
}
}
} else if (inputType === InputTypeEnum.WeaponShoot) {
return decodeWeaponShoot(view, index)
const input = decodeWeaponShoot(view, index)
return {
name,
data: {
frameId,
input
}
}
} else {
return decodeTimePast(view, index)
const input = decodeTimePast(view, index)
return {
name,
data: {
frameId,
input
}
}
}
} else if (proto === ApiMsgEnum.MsgServerSync) {
} else if (name === ApiMsgEnum.MsgServerSync) {
const lastFrameId = view.getUint32(index)
index += 4
const len = view.getUint8(index++)
const res = []
const inputs = []
for (let i = 0; i < len; i++) {
const inputType = view.getUint8(index++)
if (inputType === InputTypeEnum.ActorMove) {
res.push(decodeActorMove(view, index).data)
inputs.push(decodeActorMove(view, index))
index += 13
} else if (inputType === InputTypeEnum.WeaponShoot) {
res.push(decodeWeaponShoot(view, index).data)
inputs.push(decodeWeaponShoot(view, index))
index += 17
} else {
res.push(decodeTimePast(view, index).data)
inputs.push(decodeTimePast(view, index))
index += 4
}
}
return {
name: ApiMsgEnum.MsgServerSync,
data: res
data: {
lastFrameId,
inputs
}
}
} else {
return {
name: proto,
name: name,
data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1))))
}
}

View File

@@ -38,7 +38,6 @@ export enum ApiMsgEnum {
// TimePast = 'TimePast',
// }
export enum InputTypeEnum {
ActorMove,
WeaponShoot,
@@ -46,14 +45,14 @@ export enum InputTypeEnum {
}
export enum EntityTypeEnum {
Map1 = 'Map1',
Actor1 = 'Actor1',
Actor2 = 'Actor2',
Weapon1 = 'Weapon1',
Weapon2 = 'Weapon2',
Bullet1 = 'Bullet1',
Bullet2 = 'Bullet2',
Explosion = 'Explosion',
JoyStick = 'JoyStick',
Shoot = 'Shoot',
}
Map1 = "Map1",
Actor1 = "Actor1",
Actor2 = "Actor2",
Weapon1 = "Weapon1",
Weapon2 = "Weapon2",
Bullet1 = "Bullet1",
Bullet2 = "Bullet2",
Explosion = "Explosion",
JoyStick = "JoyStick",
Shoot = "Shoot",
}

View File

@@ -1,50 +1,66 @@
import { IApiGameEndReq, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IApiGameEndRes } from './Api'
import { ApiMsgEnum } from './Enum'
import { IMsgClientSync, IMsgGameEnd, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg'
import {
IApiGameEndReq,
IApiGameStartReq,
IApiGameStartRes,
IApiPlayerJoinReq,
IApiPlayerJoinRes,
IApiPlayerListReq,
IApiPlayerListRes,
IApiRoomCreateReq,
IApiRoomCreateRes,
IApiRoomJoinReq,
IApiRoomJoinRes,
IApiRoomLeaveReq,
IApiRoomLeaveRes,
IApiRoomListReq,
IApiRoomListRes,
IApiGameEndRes,
} from "./Api";
import { ApiMsgEnum } from "./Enum";
import { IMsgClientSync, IMsgGameEnd, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from "./Msg";
export interface IModel {
api: {
[ApiMsgEnum.ApiPlayerJoin]: {
req: IApiPlayerJoinReq,
res: IApiPlayerJoinRes,
}
req: IApiPlayerJoinReq;
res: IApiPlayerJoinRes;
};
[ApiMsgEnum.ApiPlayerList]: {
req: IApiPlayerListReq,
res: IApiPlayerListRes,
}
req: IApiPlayerListReq;
res: IApiPlayerListRes;
};
[ApiMsgEnum.ApiRoomList]: {
req: IApiRoomListReq,
res: IApiRoomListRes,
}
req: IApiRoomListReq;
res: IApiRoomListRes;
};
[ApiMsgEnum.ApiRoomCreate]: {
req: IApiRoomCreateReq,
res: IApiRoomCreateRes,
}
req: IApiRoomCreateReq;
res: IApiRoomCreateRes;
};
[ApiMsgEnum.ApiRoomJoin]: {
req: IApiRoomJoinReq,
res: IApiRoomJoinRes,
}
req: IApiRoomJoinReq;
res: IApiRoomJoinRes;
};
[ApiMsgEnum.ApiRoomLeave]: {
req: IApiRoomLeaveReq,
res: IApiRoomLeaveRes,
}
req: IApiRoomLeaveReq;
res: IApiRoomLeaveRes;
};
[ApiMsgEnum.ApiGameStart]: {
req: IApiGameStartReq,
res: IApiGameStartRes,
},
req: IApiGameStartReq;
res: IApiGameStartRes;
};
[ApiMsgEnum.ApiGameEnd]: {
req: IApiGameEndReq,
res: IApiGameEndRes,
}
},
req: IApiGameEndReq;
res: IApiGameEndRes;
};
};
msg: {
[ApiMsgEnum.MsgPlayerList]: IMsgPlayerList
[ApiMsgEnum.MsgRoomList]: IMsgRoomList,
[ApiMsgEnum.MsgRoom]: IMsgRoom,
[ApiMsgEnum.MsgGameStart]: IMsgGameStart,
[ApiMsgEnum.MsgGameEnd]: IMsgGameEnd,
[ApiMsgEnum.MsgClientSync]: IMsgClientSync,
[ApiMsgEnum.MsgServerSync]: IMsgServerSync,
}
[ApiMsgEnum.MsgPlayerList]: IMsgPlayerList;
[ApiMsgEnum.MsgRoomList]: IMsgRoomList;
[ApiMsgEnum.MsgRoom]: IMsgRoom;
[ApiMsgEnum.MsgGameStart]: IMsgGameStart;
[ApiMsgEnum.MsgGameEnd]: IMsgGameEnd;
[ApiMsgEnum.MsgClientSync]: IMsgClientSync;
[ApiMsgEnum.MsgServerSync]: IMsgServerSync;
};
}

View File

@@ -1,31 +1,34 @@
import { IPlayer, IRoom } from "./Api"
import { IClientInput, IState } from "./State"
import { IPlayer, IRoom } from "./Api";
import { IClientInput, IState } from "./State";
export interface IMsgPlayerList {
list: Array<IPlayer>
list: Array<IPlayer>;
}
export interface IMsgRoomList {
list: Array<IRoom>
list: Array<IRoom>;
}
export interface IMsgRoom {
room: IRoom
room: IRoom;
}
export interface IMsgGameStart {
state: IState
state: IState;
}
export interface IMsgGameStart {
state: IState
state: IState;
}
export interface IMsgGameEnd {
export interface IMsgGameEnd {}
export interface IMsgClientSync {
frameId: number;
input: IClientInput;
}
export type IMsgClientSync = IClientInput
export type IMsgServerSync = Array<IClientInput>
export interface IMsgServerSync {
lastFrameId: number;
inputs: Array<IClientInput>;
}

View File

@@ -1,50 +1,50 @@
import { EntityTypeEnum, InputTypeEnum } from "./Enum"
import { EntityTypeEnum, InputTypeEnum } from "./Enum";
export interface IActor {
id: number
nickname: string
type: EntityTypeEnum
weaponType: EntityTypeEnum
bulletType: EntityTypeEnum
id: number;
nickname: string;
type: EntityTypeEnum;
weaponType: EntityTypeEnum;
bulletType: EntityTypeEnum;
//动态数据
hp: number
position: IVec2
direction: IVec2
hp: number;
position: IVec2;
direction: IVec2;
}
export interface IBullet {
id: number
owner: number
type: EntityTypeEnum
id: number;
owner: number;
type: EntityTypeEnum;
//动态数据
position: IVec2
direction: IVec2
position: IVec2;
direction: IVec2;
}
export interface IVec2 {
x: number;
y: number
y: number;
}
export interface IState {
players: IActor[],
bullets: IBullet[],
nextBulletId: number
actors: IActor[];
bullets: IBullet[];
nextBulletId: number;
}
export type IClientInput = IActorMove | IWeaponShoot | ITimePast
export type IClientInput = IActorMove | IWeaponShoot | ITimePast;
export interface IActorMove {
type: InputTypeEnum.ActorMove
type: InputTypeEnum.ActorMove;
id: number;
direction: IVec2;
dt: number;
}
export interface IWeaponShoot {
type: InputTypeEnum.WeaponShoot
type: InputTypeEnum.WeaponShoot;
owner: number;
position: IVec2;
direction: IVec2;
@@ -52,5 +52,5 @@ export interface IWeaponShoot {
export interface ITimePast {
type: InputTypeEnum.TimePast;
dt: number
}
dt: number;
}

View File

@@ -1,4 +1,4 @@
export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit
export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit;
export const strencode = (str: string) => {
let byteArray: number[] = [];
@@ -11,11 +11,16 @@ export const strencode = (str: string) => {
} 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));
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[] = [];
@@ -33,10 +38,14 @@ export const strdecode = (bytes: Uint8Array) => {
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);
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);
}
};

View File

@@ -1,64 +1,63 @@
import { EventEmitter } from 'stream';
import WebSocket, { WebSocketServer } from 'ws';
import { ApiMsgEnum } from '../Common';
import { Connection, ConnectionEventEnum } from './Connection';
import { EventEmitter } from "stream";
import WebSocket, { WebSocketServer } from "ws";
import { ApiMsgEnum } from "../Common";
import { Connection, ConnectionEventEnum } from "./Connection";
export interface IMyServerOptions {
port: number
port: number;
}
export enum MyServerEventEnum {
Connect = 'Connect',
DisConnect = 'DisConnect',
Connect = "Connect",
DisConnect = "DisConnect",
}
export class MyServer extends EventEmitter {
wss?: WebSocketServer
port: number
connections: Set<Connection> = new Set()
apiMap: Map<ApiMsgEnum, Function> = new Map()
wss?: WebSocketServer;
port: number;
connections: Set<Connection> = new Set();
apiMap: Map<ApiMsgEnum, Function> = new Map();
constructor({ port = 8080 }: Partial<IMyServerOptions>) {
super()
this.port = port
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("connection", this.handleConnect.bind(this));
this.wss.on("error", (e) => {
reject(e)
})
reject(e);
});
this.wss.on("close", () => {
console.log("MyServer 服务关闭");
})
});
this.wss.on("listening", () => {
resolve(true)
})
})
resolve(true);
});
});
}
handleConnect(ws: WebSocket) {
//初始化
const connection = new Connection(this, ws)
const connection = new Connection(this, ws);
//向外告知有人来了
this.connections.add(connection)
this.emit(MyServerEventEnum.Connect, connection)
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)
})
this.connections.delete(connection);
this.emit(MyServerEventEnum.DisConnect, connection, code, reason);
});
}
setApi(apiName: ApiMsgEnum, cb: Function) {
this.apiMap.set(apiName, cb)
this.apiMap.set(apiName, cb);
}
}
}

View File

@@ -1,13 +1,12 @@
export default class Singleton {
private static _instance: any = null
private static _instance: any = null;
static GetInstance<T>(): T {
if (this._instance === null) {
this._instance = new this()
this._instance = new this();
}
return this._instance
return this._instance;
}
protected constructor() {
}
}
protected constructor() {}
}

View File

@@ -1,16 +1,16 @@
import { Connection } from "../Core"
import { Connection } from "../Core";
export default class Player {
id: number
nickname: string
connection: Connection
rid: number
id: number;
nickname: string;
connection: Connection;
rid: number;
constructor({ id, nickname, connection }: Pick<Player, 'id' | 'nickname' | 'connection'>) {
this.id = id
this.nickname = nickname
this.connection = connection
this.connection.playerId = this.id
this.rid = -1
constructor({ id, nickname, connection }: Pick<Player, "id" | "nickname" | "connection">) {
this.id = id;
this.nickname = nickname;
this.connection = connection;
this.connection.playerId = this.id;
this.rid = -1;
}
}

View File

@@ -1,54 +1,55 @@
import Singleton from '../Base/Singleton'
import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common'
import { Connection } from '../Core'
import Player from './Player'
import RoomManager from './RoomManager'
import Singleton from "../Base/Singleton";
import { ApiMsgEnum, IApiPlayerJoinReq } from "../Common";
import { Connection } from "../Core";
import Player from "./Player";
import RoomManager from "./RoomManager";
export default class PlayerManager extends Singleton {
static get Instance() {
return super.GetInstance<PlayerManager>()
return super.GetInstance<PlayerManager>();
}
playerId = 1
players: Set<Player> = new Set()
idMapPlayer: Map<number, Player> = new Map()
players: Set<Player> = new Set();
private nextPlayerId = 1;
private idMapPlayer: Map<number, Player> = new Map();
createPlayer({ connection, nickname }: IApiPlayerJoinReq & { connection: Connection }) {
const player = new Player({ id: this.playerId++, connection, nickname })
this.players.add(player)
this.idMapPlayer.set(player.id, player)
return player
const player = new Player({ id: this.nextPlayerId++, connection, nickname });
this.players.add(player);
this.idMapPlayer.set(player.id, player);
return player;
}
removePlayer(uid: number) {
const player = this.idMapPlayer.get(uid)
const player = this.idMapPlayer.get(uid);
if (player) {
const rid = player.rid
const rid = player.rid;
if (rid !== undefined) {
RoomManager.Instance.leaveRoom(rid, uid);
RoomManager.Instance.syncRooms();
RoomManager.Instance.syncRoom(rid);
}
this.players.delete(player)
this.idMapPlayer.delete(uid)
this.syncPlayers()
this.players.delete(player);
this.idMapPlayer.delete(uid);
this.syncPlayers();
}
}
getPlayerById(uid: number) {
return this.idMapPlayer.get(uid)
return this.idMapPlayer.get(uid);
}
syncPlayers() {
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgPlayerList, { list: this.getPlayersView() })
player.connection.sendMsg(ApiMsgEnum.MsgPlayerList, { list: this.getPlayersView() });
}
}
getPlayersView(players: Set<Player> = this.players) {
return [...players].map((player) => this.getPlayerView(player))
return [...players].map((player) => this.getPlayerView(player));
}
getPlayerView({ id, nickname, rid }: Player) {
return { id, nickname, rid }
return { id, nickname, rid };
}
}

View File

@@ -1,60 +1,63 @@
import { ApiMsgEnum, EntityTypeEnum, IClientInput, InputTypeEnum, IState, toFixed } from '../Common'
import type Player from './Player'
import PlayerManager from './PlayerManager'
import RoomManager from './RoomManager'
import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgClientSync, InputTypeEnum, IState, toFixed } from "../Common";
import { Connection } from "../Core";
import type Player from "./Player";
import PlayerManager from "./PlayerManager";
import RoomManager from "./RoomManager";
export default class Room {
id: number
players: Set<Player> = new Set()
lastSyncTime?: number
id: number;
players: Set<Player> = new Set();
private timers: NodeJS.Timer[] = []
private inputs: Array<IClientInput> = []
private lastTime?: number;
private timers: NodeJS.Timer[] = [];
private pendingInput: Array<IClientInput> = [];
private lastPlayerFrameIdMap: Map<number, number> = new Map();
constructor(rid: number) {
this.id = rid
this.id = rid;
}
join(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid)
const player = PlayerManager.Instance.getPlayerById(uid);
if (player) {
player.rid = this.id
this.players.add(player)
player.rid = this.id;
this.players.add(player);
}
}
leave(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid)
const player = PlayerManager.Instance.getPlayerById(uid);
if (player) {
player.rid = -1
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this)
this.players.delete(player)
player.rid = -1;
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
this.players.delete(player);
if (!this.players.size) {
RoomManager.Instance.closeRoom(this.id)
RoomManager.Instance.closeRoom(this.id);
}
}
}
close() {
this.timers.forEach(t => clearInterval(t))
this.timers.forEach((t) => clearInterval(t));
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {})
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this)
player.rid = -1;
player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {});
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
}
this.players.clear()
this.players.clear();
}
sync() {
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgRoom, {
room: RoomManager.Instance.getRoomView(this)
})
room: RoomManager.Instance.getRoomView(this),
});
}
}
start() {
const state: IState = {
players: [...this.players].map((player, index) => ({
actors: [...this.players].map((player, index) => ({
id: player.id,
nickname: player.nickname,
position: {
@@ -63,52 +66,56 @@ export default class Room {
},
direction: {
x: 1,
y: 0
y: 0,
},
hp: 100,
type: EntityTypeEnum.Actor2,
weaponType: EntityTypeEnum.Weapon2,
bulletType: EntityTypeEnum.Bullet2,
type: index === 0 ? EntityTypeEnum.Actor1 : EntityTypeEnum.Actor2,
weaponType: index === 0 ? EntityTypeEnum.Weapon1 : EntityTypeEnum.Weapon2,
bulletType: index === 0 ? EntityTypeEnum.Bullet1 : EntityTypeEnum.Bullet2,
})),
bullets: [],
nextBulletId: 1
}
nextBulletId: 1,
};
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgGameStart, {
state
})
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this)
state,
});
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
}
let t1 = setInterval(() => {
this.sendInput()
}, 100)
this.sendServerMsg();
}, 100);
let t2 = setInterval(() => {
this.timePast()
}, 16)
this.timers = [t1, t2]
this.timePast();
}, 16);
this.timers = [t1, t2];
}
getInput(input: IClientInput) {
this.inputs.push(input)
getClientMsg(connection: Connection, { frameId, input }: IMsgClientSync) {
this.lastPlayerFrameIdMap.set(connection.playerId, frameId);
this.pendingInput.push(input);
}
sendInput() {
const inputs = this.inputs
this.inputs = []
sendServerMsg() {
const pendingInput = this.pendingInput;
this.pendingInput = [];
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs)
player.connection.sendMsg(ApiMsgEnum.MsgServerSync, {
lastFrameId: this.lastPlayerFrameIdMap.get(player.id) ?? 0,
inputs: pendingInput,
});
}
}
timePast() {
let now = process.uptime();
const dt = now - (this.lastSyncTime ?? now)
this.inputs.push({
const dt = now - (this.lastTime ?? now);
this.pendingInput.push({
type: InputTypeEnum.TimePast,
dt: toFixed(dt)
})
this.lastSyncTime = now;
dt: toFixed(dt),
});
this.lastTime = now;
}
}

View File

@@ -1,77 +1,77 @@
import Singleton from '../Base/Singleton'
import { ApiMsgEnum } from '../Common'
import PlayerManager from './PlayerManager'
import Room from './Room'
import Singleton from "../Base/Singleton";
import { ApiMsgEnum } from "../Common";
import PlayerManager from "./PlayerManager";
import Room from "./Room";
export default class RoomManager extends Singleton {
static get Instance() {
return super.GetInstance<RoomManager>()
return super.GetInstance<RoomManager>();
}
roomId = 1
rooms: Set<Room> = new Set()
idMapRoom: Map<number, Room> = new Map()
nextRoomId = 1;
rooms: Set<Room> = new Set();
idMapRoom: Map<number, Room> = new Map();
createRoom() {
const room = new Room(this.roomId++)
this.rooms.add(room)
this.idMapRoom.set(room.id, room)
return room
const room = new Room(this.nextRoomId++);
this.rooms.add(room);
this.idMapRoom.set(room.id, room);
return room;
}
joinRoom(rid: number, uid: number) {
const room = this.getRoomById(rid)
const room = this.getRoomById(rid);
if (room) {
room.join(uid)
return room
room.join(uid);
return room;
}
}
leaveRoom(rid: number, uid: number) {
const room = this.getRoomById(rid)
const room = this.getRoomById(rid);
if (room) {
room.leave(uid)
room.leave(uid);
}
}
closeRoom(rid: number) {
const room = this.getRoomById(rid)
const room = this.getRoomById(rid);
if (room) {
room.close()
this.rooms.delete(room)
this.idMapRoom.delete(rid)
room.close();
this.rooms.delete(room);
this.idMapRoom.delete(rid);
}
}
startRoom(rid: number) {
const room = this.getRoomById(rid)
const room = this.getRoomById(rid);
if (room) {
room.start()
room.start();
}
}
getRoomById(id: number) {
return this.idMapRoom.get(id)
return this.idMapRoom.get(id);
}
syncRooms() {
for (const player of PlayerManager.Instance.players) {
player.connection.sendMsg(ApiMsgEnum.MsgRoomList, { list: this.getRoomsView() })
player.connection.sendMsg(ApiMsgEnum.MsgRoomList, { list: this.getRoomsView() });
}
}
syncRoom(rid: number) {
const room = this.idMapRoom.get(rid)
const room = this.idMapRoom.get(rid);
if (room) {
room.sync()
room.sync();
}
}
getRoomsView(rooms: Set<Room> = this.rooms) {
return [...rooms].map((room) => this.getRoomView(room))
return [...rooms].map((room) => this.getRoomView(room));
}
getRoomView({ id, players }: Room) {
return { id, players: PlayerManager.Instance.getPlayersView(players) }
return { id, players: PlayerManager.Instance.getPlayersView(players) };
}
}

View File

@@ -1,8 +1,7 @@
export * from './Api'
export * from './Msg'
export * from './Enum'
export * from './Model'
export * from './State'
export * from './Utils'
export * from './Binary'
export * from "./Api";
export * from "./Msg";
export * from "./Enum";
export * from "./Model";
export * from "./State";
export * from "./Utils";
export * from "./Binary";

View File

@@ -1,12 +1,12 @@
import WebSocket from 'ws';
import { EventEmitter } from 'stream';
import { MyServer } from './MyServer';
import { getTime, toArrayBuffer } from '../Utils';
import { ApiMsgEnum, IModel } from '../Common';
import { binaryEncode, binaryDecode } from '../Common/Binary';
import WebSocket from "ws";
import { EventEmitter } from "stream";
import { MyServer } from "./MyServer";
import { getTime, buffer2ArrayBuffer } from "../Utils";
import { ApiMsgEnum, IModel } from "../Common";
import { binaryEncode, binaryDecode } from "../Common/Binary";
export enum ConnectionEventEnum {
Close = 'Close',
Close = "Close",
}
interface IItem {
@@ -15,80 +15,84 @@ interface IItem {
}
export class Connection extends EventEmitter {
server: MyServer
ws: WebSocket
msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map()
server: MyServer;
ws: WebSocket;
msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map();
playerId?: number;
constructor(server: MyServer, ws: WebSocket) {
super()
super();
this.server = server
this.ws = ws
this.ws.on('close', (code: number, reason: Buffer) => {
this.emit(ConnectionEventEnum.Close, code, reason.toString())
})
this.server = server;
this.ws = ws;
this.ws.on("close", (code: number, reason: Buffer) => {
this.emit(ConnectionEventEnum.Close, code, reason.toString());
});
this.ws.on('message', (buffer: Buffer) => {
this.ws.on("message", (buffer: Buffer) => {
// const str = buffer.toString()
try {
const json = binaryDecode(toArrayBuffer(buffer))
const { name, data } = json
const json = binaryDecode(buffer2ArrayBuffer(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)}`)
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)
const res = cb.call(null, data)
const cb = this.server.apiMap.get(name);
const res = cb.call(null, this, data);
this.sendMsg(name, {
success: true,
res,
})
});
} catch (error) {
this.sendMsg(name, {
success: false,
error: (error as Error)?.message,
})
});
}
} else {
try {
if (this.msgMap.has(name)) {
this.msgMap.get(name)?.forEach(({ cb, ctx }) => cb.call(ctx, data))
this.msgMap.get(name).forEach(({ cb, ctx }) => cb.call(ctx, this, data));
}
} catch (error) {
console.log(error)
console.log(error);
}
}
} catch (error) {
console.log(`解析失败不是合法的JSON格式`, error)
console.log(`解析失败不是合法的JSON格式`, error);
}
})
});
}
listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
listenMsg<T extends keyof IModel["msg"]>(name: T, cb: (connection: Connection, arg: IModel["msg"][T]) => void, ctx: unknown) {
if (this.msgMap.has(name)) {
this.msgMap.get(name)?.push({ cb, ctx })
this.msgMap.get(name).push({ cb, ctx });
} else {
this.msgMap.set(name, [{ cb, ctx }])
this.msgMap.set(name, [{ cb, ctx }]);
}
}
unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
unlistenMsg<T extends keyof IModel["msg"]>(name: T, cb: (connection: Connection, arg: IModel["msg"][T]) => void, ctx: unknown) {
if (this.msgMap.has(name)) {
const items = this.msgMap.get(name)
const index = items.findIndex(i => cb === i.cb && i.ctx === ctx);
index > -1 && items.splice(index, 1)
const items = this.msgMap.get(name);
const index = items.findIndex((i) => cb === i.cb && i.ctx === ctx);
index > -1 && items.splice(index, 1);
}
}
sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) {
sendMsg<T extends keyof IModel["msg"]>(name: T, data: IModel["msg"][T]) {
const msg = JSON.stringify({
name,
data
})
const view = binaryEncode(name, data)
const buffer = Buffer.from(view.buffer)
console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB|${msg}`)
this.ws.send(buffer)
data,
});
const view = binaryEncode(name, data);
const buffer = Buffer.from(view.buffer);
console.log(
`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(
2
)}MB|${msg}`
);
this.ws.send(buffer);
}
}
}

View File

@@ -1,2 +1,2 @@
export * from './MyServer'
export * from './Connection'
export * from "./MyServer";
export * from "./Connection";

View File

@@ -1,144 +1,165 @@
import { Connection, MyServer, MyServerEventEnum } from './Core';
import PlayerManager from './Biz/PlayerManager';
import RoomManager from './Biz/RoomManager';
import { getTime, symlinkCommon } from './Utils';
import { ApiMsgEnum, IApiGameEndReq, IApiGameEndRes, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common';
import { Connection, MyServer, MyServerEventEnum } from "./Core";
import PlayerManager from "./Biz/PlayerManager";
import RoomManager from "./Biz/RoomManager";
import { getTime, symlinkCommon } from "./Utils";
import {
ApiMsgEnum,
IApiGameEndReq,
IApiGameEndRes,
IApiGameStartReq,
IApiGameStartRes,
IApiPlayerJoinReq,
IApiPlayerJoinRes,
IApiPlayerListReq,
IApiPlayerListRes,
IApiRoomCreateReq,
IApiRoomCreateRes,
IApiRoomJoinReq,
IApiRoomJoinRes,
IApiRoomLeaveReq,
IApiRoomLeaveRes,
IApiRoomListReq,
IApiRoomListRes,
IModel,
} from "./Common";
const server = new MyServer({ port: 8888 })
const server = new MyServer({ port: 8888 });
// event
server.on(MyServerEventEnum.Connect, (connection: Connection) => {
console.log(`${getTime()}来人|人数|${server.connections.size}`)
})
console.log(`${getTime()}来人|人数|${server.connections.size}`);
});
server.on(MyServerEventEnum.DisConnect, (connection: Connection) => {
console.log(`${getTime()}走人|人数|${server.connections.size}`)
console.log(`${getTime()}走人|人数|${server.connections.size}`);
if (connection.playerId) {
PlayerManager.Instance.removePlayer(connection.playerId)
PlayerManager.Instance.removePlayer(connection.playerId);
}
})
});
// api
server.setApi(ApiMsgEnum.ApiPlayerList, (connection: Connection, data: IApiPlayerListReq): IApiPlayerListRes => {
return { list: PlayerManager.Instance.getPlayersView() }
})
return { list: PlayerManager.Instance.getPlayersView() };
});
server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, { nickname }: IApiPlayerJoinReq): IApiPlayerJoinRes => {
const player = PlayerManager.Instance.createPlayer({ connection, nickname })
PlayerManager.Instance.syncPlayers()
const player = PlayerManager.Instance.createPlayer({ connection, nickname });
PlayerManager.Instance.syncPlayers();
return {
player: PlayerManager.Instance.getPlayerView(player)
}
})
player: PlayerManager.Instance.getPlayerView(player),
};
});
server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IApiRoomListReq): IApiRoomListRes => {
return { list: RoomManager.Instance.getRoomsView() }
})
return { list: RoomManager.Instance.getRoomsView() };
});
server.setApi(ApiMsgEnum.ApiRoomCreate, (connection: Connection, data: IApiRoomCreateReq): IApiRoomCreateRes => {
if (connection.playerId) {
const room = RoomManager.Instance.joinRoom(RoomManager.Instance.createRoom().id, connection.playerId)
const room = RoomManager.Instance.joinRoom(RoomManager.Instance.createRoom().id, connection.playerId);
if (room) {
RoomManager.Instance.syncRooms()
PlayerManager.Instance.syncPlayers()
RoomManager.Instance.syncRooms();
PlayerManager.Instance.syncPlayers();
return {
room: RoomManager.Instance.getRoomView(room)
}
room: RoomManager.Instance.getRoomView(room),
};
} else {
throw new Error("ApiRoomCreate room不存在")
throw new Error("ApiRoomCreate room不存在");
}
} else {
throw new Error("ApiRoomCreate 玩家未登录")
throw new Error("ApiRoomCreate 玩家未登录");
}
})
});
server.setApi(ApiMsgEnum.ApiRoomJoin, (connection: Connection, data: IApiRoomJoinReq): IApiRoomJoinRes => {
if (connection.playerId) {
const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId)
const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId);
if (room) {
RoomManager.Instance.syncRooms()
PlayerManager.Instance.syncPlayers()
RoomManager.Instance.syncRoom(room.id)
RoomManager.Instance.syncRooms();
PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRoom(room.id);
return {
room: RoomManager.Instance.getRoomView(room)
}
room: RoomManager.Instance.getRoomView(room),
};
} else {
throw new Error("ApiRoomJoin room不存在")
throw new Error("ApiRoomJoin room不存在");
}
} else {
throw new Error("ApiRoomJoin 玩家未登录")
throw new Error("ApiRoomJoin 玩家未登录");
}
})
});
server.setApi(ApiMsgEnum.ApiRoomLeave, (connection: Connection, data: IApiRoomLeaveReq): IApiRoomLeaveRes => {
if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId)
const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) {
const rid = player.rid
const rid = player.rid;
if (rid) {
RoomManager.Instance.leaveRoom(rid, player.id)
PlayerManager.Instance.syncPlayers()
RoomManager.Instance.syncRooms()
RoomManager.Instance.syncRoom(rid)
return {}
RoomManager.Instance.leaveRoom(rid, player.id);
PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRooms();
RoomManager.Instance.syncRoom(rid);
return {};
} else {
throw new Error("ApiRoomLeave 玩家不在房间")
throw new Error("ApiRoomLeave 玩家不在房间");
}
} else {
throw new Error("ApiRoomLeave 玩家不存在")
throw new Error("ApiRoomLeave 玩家不存在");
}
} else {
throw new Error("ApiRoomLeave 玩家未登录")
throw new Error("ApiRoomLeave 玩家未登录");
}
})
});
server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameStartReq): IApiGameStartRes => {
if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId)
const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) {
const rid = player.rid
const rid = player.rid;
if (rid) {
RoomManager.Instance.startRoom(rid)
// PlayerManager.Instance.syncPlayers()
// RoomManager.Instance.syncRooms()
return {}
RoomManager.Instance.startRoom(rid);
PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRooms();
return {};
} else {
throw new Error("ApiRoomLeave 玩家不在房间")
throw new Error("ApiRoomLeave 玩家不在房间");
}
} else {
throw new Error("ApiRoomLeave 玩家不存在")
throw new Error("ApiRoomLeave 玩家不存在");
}
} else {
throw new Error("ApiRoomLeave 玩家未登录")
throw new Error("ApiRoomLeave 玩家未登录");
}
})
});
server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => {
if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId)
const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) {
const rid = player.rid
const rid = player.rid;
if (rid) {
RoomManager.Instance.closeRoom(rid)
PlayerManager.Instance.syncPlayers()
RoomManager.Instance.syncRooms()
return {}
RoomManager.Instance.closeRoom(rid);
PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRooms();
return {};
} else {
throw new Error("ApiGameEnd 玩家不在房间")
throw new Error("ApiGameEnd 玩家不在房间");
}
} else {
throw new Error("ApiGameEnd 玩家不存在")
throw new Error("ApiGameEnd 玩家不存在");
}
} else {
throw new Error("ApiGameEnd 玩家未登录")
throw new Error("ApiGameEnd 玩家未登录");
}
})
});
// start!!
server.start().then(() => {
symlinkCommon()
console.log("服务启动!")
}).catch((e) => {
console.log("服务异常", e)
})
server
.start()
.then(() => {
symlinkCommon();
console.log("服务启动!");
})
.catch((e) => {
console.log("服务异常", e);
});

View File

@@ -1,821 +0,0 @@
/**
* 类似于protobuffer但此库及其精简且专为js打造
* @author zp
* @version 1.1.0
*/
/**
* [注] nodejs环境下通过Uint8Array将ArrayBuffer和Buffer互相转换
* @example
* // Buffer ---> ArrayBuffer
* function toArrayBuffer(buf) {
* var ab = new ArrayBuffer(buf.length);
* var view = new Uint8Array(ab);
* for (var i = 0; i < buf.length; ++i) {
* view[i] = buf[i];
* }
* return ab;
* }
* // ArrayBuffer ---> Buffer
* function toBuffer(ab) {
* var buf = new Buffer(ab.byteLength);
* var view = new Uint8Array(ab);
* for (var i = 0; i < buf.length; ++i) {
* buf[i] = view[i];
* }
* return buf;
* }
*/
/**
* @example
* var { registerProto, Type, encode, decode, singleArray } = binary;
* registerProto(1001, {
* name: Type.String,
* age: Type.Uint8,
* sex: Type.Uint8
* })
* registerProto(1002, {
* info: 1001,
* gold: Type.Uint16,
* items: [Type.Uint16, Type.String]
* })
* registerProto(1003, {
* array0: Type.Array,
* array1: singleArray(1002),
* array2: singleArray([1001, 1002]),
* array3: singleArray(Type.Uint16),
* array4: singleArray([Type.Uint16, Type.String])
* })
* var buffer = encode({ name: 'Mary', age: 18, sex: 0 }, 1001);
* decode(buffer);
* var buffer = encode({ info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }, 1002);
* decode(buffer);
* var buffer = encode({
* array0: ['你好啊','我很好'],
* array1: [{ info: { name: 'James', age: 30, sex: 1 }, gold: 10, array: [100, 2, 3] }],
* array2: [[{}, { info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }]],
* array3: [568],
* array4: [[0, '零'], [1, '一'], [2, '二'], [3, '三']]
* }, 1003);
* decode(buffer);
*/
/**
* https://segmentfault.com/a/1190000014533505 中提到如果服务器开启了压缩了话需要进行解压操作并推荐了pako.js
* (具体也不知道这个压缩怎么回事,测试的时候也没遇到这个问题,先写注释记下来)
* @see https://github.com/nodeca/pako/edit/master/dist/pako.js
* @example
* let compressdata = new Uint8Array(buffer, byteOff, length);
* let uncompress = pako.inflate(compressdata);//解压数据
* let uncompressdata = uncompress.buffer;// ArrayBuffer {}
* let dataViewData = new DataView(uncompressdata, 0);//解压后数据
*/
/**
* A minimal base64 implementation for number arrays.
* @memberof util
* @namespace
*/
class Base64 {
// 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++;
}
}
/**
* 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));
};
/**
* 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();
/**
* A minimal UTF8 implementation for number arrays.
* @memberof util
* @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;
};
/**
* 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;
};
}
const utf8 = new UTF8();
class Encode {
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;
}
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);
}
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;
}
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;
}
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;
}
Boolean(data) {
return this.Uint8(data ? 1 : 0);
}
String(string) {
if (!isString(string)) string = '';
const len = utf8.write(string, this.view, this.index + 2);
this.Uint16(len);
this.index += len;
}
Base64(string) {
if (!isBase64(string)) string = '';
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('');
}
}
Object(obj) {
if (isMap(obj) && !isEmpty(obj)) {
return this.String(JSON.stringify(obj));
} else {
return this.String('');
}
}
Buffer() {
return this.buffer;
}
}
class Decode {
private view: DataView = null;
private index: number = 0;
constructor(buffer: ArrayBuffer) {
this.view = new DataView(buffer);
this.index = 0;
}
Int8() {
return this.view.getInt8(this.index++);
}
Uint8() {
return this.view.getUint8(this.index++);
}
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;
}
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;
}
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;
}
Boolean() {
return !!this.Uint8();
}
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);
}
Array() {
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();
}
const isObject = function (param) {
return param && typeof param === 'object';
}
const isArray = function (param) {
return getType(param) === 'array';
}
const isMap = function (param) {
return getType(param) === 'object';
}
const isString = function (param) {
return getType(param) === 'string';
}
const isNumber = function (param) {
return getType(param) === 'number';
}
const isBoolean = function (param) {
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);
}
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;
}
function isEmpty(obj) {
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;
}
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;
})
}
return [];
}
function realType(type) {
if (isObject(type)) {
return type;
}
return protoCache[type] || type;
}
const singleArrayPrefix = 'SingleArray';
function isSingleArray(str: string) {
return isString(str) && stringStartsWith(str, singleArrayPrefix);
}
function SingleArrayProto(str: string) {
const stringify = str.slice(singleArrayPrefix.length + 1, -1);
return JSON.parse(stringify);
}
/**
* 标记单一类型的数组
* @param proto
*/
export const singleArray = function (proto) {
return `${singleArrayPrefix}(${JSON.stringify(proto)})`;
}
function DataLen(data: any, proto: any) {
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");
}
return length;
}
function encodeData(encode: Encode, data: any, proto: any) {
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);
}
}
function decodeData(decode: Decode, proto: any) {
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]();
}
}
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
}
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
}
/**
* 序列化
* 开头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");
}
}
/**
* 反序列化
* 开头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");
}
}
/**
* proto缓存
*/
const protoCache = {}
/**
* 注册proto
* id: 必须是个正整数(或正整数字符串), 取值范围[0,65535]
*/
export const registerProto = function (id: number | string, proto: any) {
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");
}
}
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");
}
}
export const protoToJson = function () {
return JSON.stringify(protoCache);
}

View File

@@ -1,402 +0,0 @@
/**
* @author zp
*/
/**
* 多平台一致精确计算库比decimal更轻量更快
* [Math.round、Math.min、Math.maxMath.floor、Math.ceil这些系统方法一般情况下是可以放心使用的]
*/
const exactMath = Object.create(null);
module.exports = exactMath;
// 计算精度
// sin、cos、tan方法的误差小数点后16位
const ACCURACY_SIN_ERROR = 1e-16;
const ACCURACY_COS_ERROR = 1e-16;
const ACCURACY_TAN_ERROR = 1e-16;
// 角度弧度常量
const DEG = 57.29577951308232;
const RAD = 0.017453292519943295;
// 系统常量
exactMath.PI = 3.141592653589793;
exactMath.E = 2.718281828459045;
exactMath.LN2 = 0.6931471805599453;
exactMath.LN10 = 2.302585092994046;
exactMath.LOG2E = 1.4426950408889634;
exactMath.LOG10E = 0.4342944819032518;
exactMath.SQRT1_2 = 0.7071067811865476;
exactMath.SQRT2 = 1.4142135623730951;
/**
* 链式调用
* @example
* const value = exactMath.value(10).add(20.123).mul(2).sqrt().value;
*/
let chain = null;
exactMath.value = function (value) {
if (!chain) {
chain = {
value: 0,
valueOf() { return this.value; },
toString() { return String(this.value); }
}
for (const key in exactMath) {
if (key !== 'value' && typeof exactMath[key] === 'function') {
chain[key] = function (...args) {
this.value = exactMath[key].call(exactMath, this.value, ...args);
return this;
}
}
}
}
chain.value = value;
return chain;
}
/****************************************************基础****************************************************/
/**
* 获得小数位数
* @param {Number} num 浮点数
* @returns {Number}
*/
exactMath.getDecimalPlace = function (num) {
if (num && num !== Math.floor(num)) {
for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
temp = num * m;
if (temp == Math.floor(temp)) return n;
}
return 20;
} else {
return 0;
}
}
/**
* 保留n为小数并四舍五入
* @example
* (2.335).toFixed(2)
* exactMath.toFixed(2.335, 2)
* @param {Number} num 浮点数
* @param {Number} n 整数
* @returns {Number}
*/
exactMath.toFixed = function (num, n = 0) {
if (n == 0) {
return Math.round(num);
} else {
const m = Math.pow(10, n);
return Math.round(num * (m * 10) / 10) / m;
}
}
exactMath.abs = function (x) {
return Math.abs(x);
}
exactMath.round = function (x) {
return Math.round(x);
}
exactMath.ceil = function (x) {
return Math.ceil(x)
}
exactMath.floor = function (x) {
return Math.floor(x)
}
exactMath.min = function (...args) {
return Math.min(...args);
}
exactMath.max = function (...args) {
return Math.max(...args);
}
/**
* 小数相加
* @param {Number} num1 浮点数
* @param {Number} num2 浮点数
* @returns {Number}
*/
exactMath.add = function (...args) {
if (args.length === 2) {
const num1 = args[0];
const num2 = args[1];
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
} else {
return args.reduce((a, b) => this.add(a, b))
}
};
/**
* 小数相减
* @param {Number} num1 浮点数
* @param {Number} num2 浮点数
* @returns {Number}
*/
exactMath.sub = function (...args) {
if (args.length === 2) {
const num1 = args[0];
const num2 = args[1];
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
} else {
return args.reduce((a, b) => this.sub(a, b))
}
};
/**
* 小数相乘
* @param {Number} num1 浮点数
* @param {Number} num2 浮点数
* @returns {Number}
*/
exactMath.mul = function (...args) {
if (args.length === 2) {
let num1 = args[0];
let num2 = args[1];
// 方案1
// 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
// const n1 = this.getDecimalPlace(num1);
// const n2 = this.getDecimalPlace(num2);
// const m1 = Math.pow(10, n1);
// const m2 = Math.pow(10, n2);
// return (n1 * m1) * (n2 * m2) / (m1 * m2);
// 方案2
// 用除法实现乘法,不会存在过大中间值
let n1 = this.getDecimalPlace(num1);
let n2 = this.getDecimalPlace(num2);
let m = Math.pow(10, n2);
num2 = m / this.toFixed(num2 * m);
m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
m = this.toFixed(num1 * m) / this.toFixed(num2 * m);
let n = Math.min(this.getDecimalPlace(m), n1 + n2);
return this.toFixed(m, n);
} else {
return args.reduce((a, b) => this.mul(a, b))
}
};
/**
* 小数相除法
* @param {Number} num1 浮点数
* @param {Number} num2 浮点数
* @returns {Number}
*/
exactMath.div = function (...args) {
if (args.length === 2) {
const num1 = args[0];
const num2 = args[1];
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
return this.toFixed(num1 * m) / this.toFixed(num2 * m);
} else {
return args.reduce((a, b) => this.div(a, b))
}
};
/**
* 取余
* @param {Number} num1 浮点数
* @param {Number} num2 浮点数
* @returns {Number}
*/
exactMath.rem = function (...args) {
if (args.length === 2) {
const num1 = args[0];
const num2 = args[1];
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
} else {
return args.reduce((a, b) => this.rem(a, b))
}
};
/**
* n次方仅支持整数次方(正负都可以)
* @param {Number} num 浮点数
* @param {Number} n 整数
*/
exactMath.pow = function (num, n) {
if (num == 0 && n == 0) {
return 1;
}
if (num == 0 && n > 0) {
return 0
}
if (num == 0 && n < 0) {
return Infinity;
}
// num为负数n为负小数返回NaN
if (num < 0 && n < 0 && Math.round(n) != n) {
return NaN;
}
if (Math.round(n) != n) {
throw new Error('n must be an integer');
}
let result = 1;
if (n > 0) {
for (let index = 0; index < n; index++) {
result = this.mul(result, num);
}
} else if (n < 0) {
for (let index = 0, len = Math.abs(n); index < len; index++) {
result = this.div(result, num);
}
}
return result;
};
/**
* 开方运算【牛顿迭代法】
*
* @param {Number} n
* @returns
*/
exactMath.sqrt = function (n) {
if (n < 0) return NaN;
if (n === 0) return 0;
if (n === 1) return 1;
let last = 0;
let res = 1;
let c = 50;
while (res != last && --c >= 0) {
last = res;
res = this.div(this.add(res, this.div(n, res)), 2)
}
return res;
// float InvSqrt(float x)
// {
// float xhalf = 0.5f * x;
// int i = * (int *) & x; // get bits for floating VALUE
// i = 0x5f375a86 - (i >> 1); // gives initial guess y0
// x = * (float *) & i; // convert bits BACK to float
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
// return 1 / x;
// }
};
/****************************************************随机****************************************************/
function getSeed(seed) {
if (isNaN(seed)) {
seed = Math.floor(Math.random() * 233280);
} else {
seed = Math.floor(seed % 233280);
}
return seed;
}
let randomSeed = getSeed();
/**
* 设置随机种子
*/
exactMath.setSeed = function (seed) {
randomSeed = getSeed(seed);
};
/**
* 随机
*/
exactMath.random = function () {
randomSeed = (randomSeed * 9301 + 49297) % 233280;
return randomSeed / 233280.0;
};
/**
* 根据随机种子随机
* @param {number} seed
*/
exactMath.randomBySeed = function (seed) {
seed = getSeed(seed);
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280.0;
};
/****************************************************角度弧度转换****************************************************/
/**
* 弧度数转角度数
* @param {Number} radians 浮点数
* @returns {Numbe} 浮点数
*/
exactMath.radiansToDegrees = function (radians) {
return this.div(radians, RAD);
};
/**
* 角度数转弧度数
* @param {Number} degrees 浮点数
* @returns {Numbe} 浮点数
*/
exactMath.degreesToRadians = function (degrees) {
return this.div(degrees, DEG);
};
/**
* 将角度值转换到[0, 360)范围内
* @param {Number} angle 浮点数
* @returns {Number} 整数
*/
exactMath.get0To360Angle = function (angle) {
if (angle === 0) {
return 0;
} else if (angle < 0) {
return this.add(this.rem(angle, 360), 360);
} else {
return this.rem(angle, 360);
}
};
/****************************************************三角函数****************************************************/
/**
* 查表
*/
exactMath._sin = {};
exactMath._cos = {};
exactMath._tan = {};
/**
* 3个三角函数根据需求自行添加
* 为了效率,应该尽量使用查表法
* 表内查不到的目前使用系统方法的结果并取前4位小数
*/
exactMath.sin = function (x) {
if (this._sin.hasOwnProperty(x)) {
return this._sin[x];
}
// if (x == 0) {
// return 0;
// } else if (x == 90) {
// return 1;
// }
// let n = x, sum = 0, i = 1;
// do {
// i++;
// sum = this.add(sum, n);
// // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
// n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
// } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
// return sum;
return this.toFixed(Math.sin(x), 4);
};
exactMath.cos = function (x) {
if (this._cos.hasOwnProperty(x)) {
return this._cos[x];
}
return this.toFixed(Math.cos(x), 4);
};
exactMath.tan = function (x) {
if (this._tan.hasOwnProperty(x)) {
return this._tan[x];
}
return this.toFixed(Math.tan(x), 4);
};

View File

@@ -1,46 +1,54 @@
import fs from 'fs-extra'
import path from 'path'
import fs from "fs-extra";
import path from "path";
export const getTime = () => new Date().toLocaleString().split("├")[0]
export const getTime = () => new Date().toLocaleString().split("├")[0];
//symlink同步
export const symlinkCommon = async () => {
const src = path.resolve(__dirname, '../Common')
const dst = path.resolve(__dirname, '../../../client/assets/Scripts/Common')
const src = path.resolve(__dirname, "../Common");
const dst = path.resolve(__dirname, "../../../client/assets/Scripts/Common");
if (await fs.lstat(dst).then(v => v.isSymbolicLink()).catch(() => false) && await fs.readlink(dst) === src) {
console.log('同步成功!')
if (
(await fs
.lstat(dst)
.then((v) => v.isSymbolicLink())
.catch(() => false)) &&
(await fs.readlink(dst)) === src
) {
console.log("同步成功!");
} else {
fs.symlink(src, dst).then(() => {
console.log('同步成功!')
}).catch((e) => {
console.log('同步失败!', e)
})
fs.symlink(src, dst)
.then(() => {
console.log("同步成功!");
})
.catch((e) => {
console.log("同步失败!", e);
});
}
}
};
//copy同步
export const copyCommon = async () => {
const src = path.resolve(__dirname, '../Common')
const dst = path.resolve(__dirname, '../../../client/assets/Scripts/Common')
const src = path.resolve(__dirname, "../Common");
const dst = path.resolve(__dirname, "../../../client/assets/Scripts/Common");
console.log(src, dst);
// clean
await fs.remove(dst)
await fs.remove(dst);
//create
await fs.ensureDir(dst)
await fs.ensureDir(dst);
// copy
await fs.copy(src, dst)
console.log('同步成功!')
}
await fs.copy(src, dst);
console.log("同步成功!");
};
export const toArrayBuffer = (buffer: Buffer) => {
export const buffer2ArrayBuffer = (buffer: Buffer) => {
var ab = new ArrayBuffer(buffer.length);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
};