This commit is contained in:
sli97
2022-12-07 22:24:46 +08:00
parent b7c95c5ca9
commit c5acb09642
24 changed files with 280 additions and 176 deletions

View File

@@ -1,4 +1,10 @@
import { IPlayer, IRoom } from "./Model"
export interface IPlayer {
id: number, nickname: string, rid: number
}
export interface IRoom {
id: number, players: Array<IPlayer>
}
export interface IApiPlayerListReq {
}
@@ -46,4 +52,10 @@ export interface IApiGameStartReq {
rid: number
}
export interface IApiGameStartRes { }
export interface IApiGameStartRes { }
export interface IApiGameEndReq {
rid: number
}
export interface IApiGameEndRes { }

View File

@@ -22,10 +22,12 @@ export enum ApiMsgEnum {
ApiRoomJoin,
ApiRoomLeave,
ApiGameStart,
ApiGameEnd,
MsgPlayerList,
MsgRoomList,
MsgRoom,
MsgGameStart,
MsgGameEnd,
MsgClientSync,
MsgServerSync,
}

View File

@@ -1,9 +1,50 @@
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 IPlayer {
id: number, nickname: string, rid: number
}
export interface IRoom {
id: number, players: Array<IPlayer>
export interface IModel {
api: {
[ApiMsgEnum.ApiPlayerJoin]: {
req: IApiPlayerJoinReq,
res: IApiPlayerJoinRes,
}
[ApiMsgEnum.ApiPlayerList]: {
req: IApiPlayerListReq,
res: IApiPlayerListRes,
}
[ApiMsgEnum.ApiRoomList]: {
req: IApiRoomListReq,
res: IApiRoomListRes,
}
[ApiMsgEnum.ApiRoomCreate]: {
req: IApiRoomCreateReq,
res: IApiRoomCreateRes,
}
[ApiMsgEnum.ApiRoomJoin]: {
req: IApiRoomJoinReq,
res: IApiRoomJoinRes,
}
[ApiMsgEnum.ApiRoomLeave]: {
req: IApiRoomLeaveReq,
res: IApiRoomLeaveRes,
}
[ApiMsgEnum.ApiGameStart]: {
req: IApiGameStartReq,
res: IApiGameStartRes,
},
[ApiMsgEnum.ApiGameEnd]: {
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,
}
}

View File

@@ -1,4 +1,4 @@
import { IPlayer, IRoom } from "./Model"
import { IPlayer, IRoom } from "./Api"
import { IClientInput, IState } from "./State"
export interface IMsgPlayerList {
@@ -22,6 +22,10 @@ export interface IMsgGameStart {
state: IState
}
export interface IMsgGameEnd {
}
export type IMsgClientSync = IClientInput
export type IMsgServerSync = Array<IClientInput>

View File

@@ -7,36 +7,43 @@ export default class Room {
id: number
players: Set<Player> = new Set()
lastSyncTime?: number
timers: NodeJS.Timer[] = []
private timers: NodeJS.Timer[] = []
private inputs: Array<IClientInput> = []
constructor(rid: number) {
this.id = rid
}
join(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid)
if (player) {
player.rid = this.id
this.players.add(player)
}
}
leave(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid)
if (player) {
player.rid = -1
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this)
this.players.delete(player)
if (!this.players.size) {
this.timers.forEach(t => clearInterval(t))
RoomManager.Instance.closeRoom(this.id)
}
}
}
close() {
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)
}
this.players.clear()
}
sync() {
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgRoom, {
@@ -71,10 +78,10 @@ export default class Room {
player.connection.sendMsg(ApiMsgEnum.MsgGameStart, {
state
})
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this)
}
this.listenPlayer()
let t1 = setInterval(() => {
this.syncInput()
this.sendInput()
}, 100)
let t2 = setInterval(() => {
this.timePast()
@@ -82,19 +89,14 @@ export default class Room {
this.timers = [t1, t2]
}
listenPlayer() {
for (const player of this.players) {
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, (input) => {
this.inputs.push(input)
})
}
getInput(input: IClientInput) {
this.inputs.push(input)
}
syncInput() {
sendInput() {
const inputs = this.inputs
this.inputs = []
for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs)
}

View File

@@ -37,6 +37,7 @@ export default class RoomManager extends Singleton {
closeRoom(rid: number) {
const room = this.getRoomById(rid)
if (room) {
room.close()
this.rooms.delete(room)
this.idMapRoom.delete(rid)
}

View File

@@ -1,6 +1,3 @@
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'
export * from './Api'
export * from './Msg'
export * from './Enum'
@@ -9,44 +6,3 @@ export * from './State'
export * from './Utils'
export * from './Binary'
export interface IModel {
api: {
[ApiMsgEnum.ApiPlayerJoin]: {
req: IApiPlayerJoinReq,
res: IApiPlayerJoinRes,
}
[ApiMsgEnum.ApiPlayerList]: {
req: IApiPlayerListReq,
res: IApiPlayerListRes,
}
[ApiMsgEnum.ApiRoomList]: {
req: IApiRoomListReq,
res: IApiRoomListRes,
}
[ApiMsgEnum.ApiRoomCreate]: {
req: IApiRoomCreateReq,
res: IApiRoomCreateRes,
}
[ApiMsgEnum.ApiRoomJoin]: {
req: IApiRoomJoinReq,
res: IApiRoomJoinRes,
}
[ApiMsgEnum.ApiRoomLeave]: {
req: IApiRoomLeaveReq,
res: IApiRoomLeaveRes,
}
[ApiMsgEnum.ApiGameStart]: {
req: IApiGameStartReq,
res: IApiGameStartRes,
}
},
msg: {
[ApiMsgEnum.MsgPlayerList]: IMsgPlayerList
[ApiMsgEnum.MsgRoomList]: IMsgRoomList,
[ApiMsgEnum.MsgRoom]: IMsgRoom,
[ApiMsgEnum.MsgGameStart]: IMsgGameStart,
[ApiMsgEnum.MsgClientSync]: IMsgClientSync,
[ApiMsgEnum.MsgServerSync]: IMsgServerSync,
}
}

View File

@@ -9,10 +9,15 @@ export enum ConnectionEventEnum {
Close = 'Close',
}
interface IItem {
cb: Function;
ctx: unknown;
}
export class Connection extends EventEmitter {
server: MyServer
ws: WebSocket
msgMap: Map<ApiMsgEnum, Function[]> = new Map()
msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map()
playerId?: number;
constructor(server: MyServer, ws: WebSocket) {
@@ -34,7 +39,7 @@ export class Connection extends EventEmitter {
if (this.server.apiMap.has(name)) {
try {
const cb = this.server.apiMap.get(name)
const res = cb?.(this, data)
const res = cb.call(null, data)
this.sendMsg(name, {
success: true,
res,
@@ -45,31 +50,34 @@ export class Connection extends EventEmitter {
error: (error as Error)?.message,
})
}
return
}
if (this.msgMap.has(name)) {
this.msgMap.get(name)?.forEach(cb => cb(data))
} else {
try {
if (this.msgMap.has(name)) {
this.msgMap.get(name)?.forEach(({ cb, ctx }) => cb.call(ctx, data))
}
} catch (error) {
console.log(error)
}
}
} catch (error) {
// console.log(`解析失败,${str}不是合法的JSON格式`, error)
console.log(error)
console.log(`解析失败,不是合法的JSON格式`, error)
}
})
}
listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void) {
listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
if (this.msgMap.has(name)) {
this.msgMap.get(name)?.push(cb)
this.msgMap.get(name)?.push({ cb, ctx })
} else {
this.msgMap.set(name, [cb])
this.msgMap.set(name, [{ cb, ctx }])
}
}
unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void) {
unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
if (this.msgMap.has(name)) {
const index = this.msgMap.get(name)?.indexOf(cb) || -1
index > -1 && this.msgMap.get(name)?.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)
}
}
@@ -80,7 +88,7 @@ export class Connection extends EventEmitter {
})
const view = binaryEncode(name, data)
const buffer = Buffer.from(view.buffer)
console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|${msg}`)
console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB|${msg}`)
this.ws.send(buffer)
}
}

View File

@@ -2,7 +2,7 @@ import { Connection, MyServer, MyServerEventEnum } from './Core';
import PlayerManager from './Biz/PlayerManager';
import RoomManager from './Biz/RoomManager';
import { getTime, symlinkCommon } from './Utils';
import { ApiMsgEnum, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common';
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 })
@@ -31,7 +31,6 @@ server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, { nickname }: I
}
})
server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IApiRoomListReq): IApiRoomListRes => {
return { list: RoomManager.Instance.getRoomsView() }
})
@@ -114,6 +113,28 @@ server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameSt
}
})
server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => {
if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId)
if (player) {
const rid = player.rid
if (rid) {
RoomManager.Instance.closeRoom(rid)
PlayerManager.Instance.syncPlayers()
RoomManager.Instance.syncRooms()
return {}
} else {
throw new Error("ApiGameEnd 玩家不在房间")
}
} else {
throw new Error("ApiGameEnd 玩家不存在")
}
} else {
throw new Error("ApiGameEnd 玩家未登录")
}
})
// start!!
server.start().then(() => {
symlinkCommon()

View File

@@ -72,7 +72,7 @@
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */