Skip to content

Commit e7e7bb0

Browse files
committed
更新session信息存储
1 parent 5d9b9a0 commit e7e7bb0

File tree

16 files changed

+204
-13
lines changed

16 files changed

+204
-13
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ G-Framework 不仅提供了优秀的实体组件系统(ECS)核心,还结
3535
- [客户端插值](docs/interpolation.md) 提供插值算法,优化客户端帧同步下的状态表现,如平滑移动效果
3636
- [输入管理器](docs/input-manager.md) 管理游戏输入,如键盘、鼠标和触摸事件,并在合适的时机同步输入状态
3737
- [游戏同步策略](docs/sync-strategy.md) 针对游戏同步提供了多种策略,如状态插值、状态压缩等,按需选择使用。
38+
- [对象池](docs/object-pool.md) 提供了一种高效的对象管理方式,通过重用对象来避免频繁的对象创建和销毁,可以用于管理游戏中的实体,如子弹、角色等。
3839

3940
G-Framework 通过提供这些实用模块,让您在游戏开发过程中专注于逻辑实现,更快速地满足项目需求。这些模块针对帧同步做了大量优化,确保您的项目在采用帧同步时获得较佳的性能表现。
4041

client-test/dist/gs-client.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ playerEntity.addComponent(VelocityComponent);
109109
var moveSystem = new MoveSystem(core.entityManager, 0);
110110
core.systemManager.registerSystem(moveSystem);
111111
// 使用你的服务器URL实例化网络适配器
112-
var networkAdapter = new gs.GNetworkAdapter('ws://localhost:8080', "admin", "admin1");
112+
var networkAdapter = new gs.GNetworkAdapter('wss://8080-esengine-gframework-i1149hh1fae.ws-us101.gitpod.io\n', "test", "test");
113113
// 添加网络适配器到EntityManager
114114
core.entityManager.getNetworkManager().setNetworkAdapter(networkAdapter);
115115
var lastTimestamp = performance.now();

client-test/lib/gs.js

+1
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,7 @@ var gs;
20222022
_this.authentication.handleAuthenticationMessage(message);
20232023
}
20242024
else {
2025+
console.warn("[g-client]: \u672A\u77E5\u7684\u6D88\u606F\u7C7B\u578B: " + message.type);
20252026
}
20262027
});
20272028
};

client-test/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const moveSystem = new MoveSystem(core.entityManager, 0);
1313
core.systemManager.registerSystem(moveSystem);
1414

1515
// 使用你的服务器URL实例化网络适配器
16-
let networkAdapter = new gs.GNetworkAdapter('ws://localhost:8080', "admin", "admin1");
16+
let networkAdapter = new gs.GNetworkAdapter('wss://8080-esengine-gframework-i1149hh1fae.ws-us101.gitpod.io\n', "test", "test");
1717
// 添加网络适配器到EntityManager
1818
core.entityManager.getNetworkManager().setNetworkAdapter(networkAdapter);
1919

docs/object-pool.md

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# `ObjectPool`类的使用指南
2+
3+
## 简介
4+
5+
`ObjectPool`是一个通用的对象池实现,它可以帮助你管理和重复利用对象,从而避免频繁的对象创建和销毁带来的性能开销。在游戏开发、资源管理等需要高效利用内存的场景中,`ObjectPool`可以发挥很大的作用。
6+
7+
## 使用方法
8+
9+
以下是如何使用`ObjectPool`类的详细步骤:
10+
11+
### 1. 创建对象池
12+
13+
首先,你需要提供两个函数:一个用于创建新对象,一个用于重置对象。然后你就可以创建一个`ObjectPool`实例了。
14+
15+
```ts
16+
const pool = new gs.ObjectPool(
17+
() => new SomeClass(), // createFn: 用于创建新的SomeClass对象
18+
(obj) => obj.reset() // resetFn: 用于重置SomeClass对象的状态
19+
);
20+
```
21+
22+
在上面的例子中,SomeClass是你想要管理的对象的类。请确保该类有一个可以重置对象状态的方法(在这个例子中是reset方法)。
23+
24+
### 2. 获取对象
25+
当你需要一个对象时,可以使用acquire方法来获取。如果对象池中有可用的对象,acquire方法会取出一个,重置它的状态,然后返回。如果对象池中没有可用的对象,acquire方法会创建一个新的对象然后返回。
26+
27+
```ts
28+
const obj = pool.acquire();
29+
```
30+
31+
### 3. 归还对象
32+
当你不再需要一个对象时,你可以使用release方法将其归还到对象池中,以便稍后重复使用。
33+
34+
```ts
35+
pool.release(obj);
36+
```
37+
请注意,一旦你调用了release方法归还了一个对象,你就不应再使用该对象,除非你再次通过acquire方法获取到它。
38+
39+
## 示例
40+
假设你有一个名为Bullet的类,它代表一颗子弹。子弹在游戏中经常被创建和销毁,所以你决定使用ObjectPool来管理它们。
41+
42+
```ts
43+
class Bullet {
44+
x: number;
45+
y: number;
46+
isAlive: boolean;
47+
48+
constructor() {
49+
this.reset();
50+
}
51+
52+
reset() {
53+
this.x = 0;
54+
this.y = 0;
55+
this.isAlive = false;
56+
}
57+
58+
fire(x: number, y: number) {
59+
this.x = x;
60+
this.y = y;
61+
this.isAlive = true;
62+
}
63+
}
64+
65+
const bulletPool = new gs.ObjectPool(
66+
() => new Bullet(), // 创建Bullet对象
67+
(bullet) => bullet.reset() // 重置Bullet对象
68+
);
69+
70+
// 当你需要发射一颗子弹时
71+
const bullet = bulletPool.acquire();
72+
bullet.fire(player.x, player.y);
73+
74+
// 当子弹超出屏幕范围时
75+
if (bullet.x < 0 || bullet.x > screenWidth || bullet.y < 0 || bullet.y > screenHeight) {
76+
bulletPool.release(bullet);
77+
}
78+
```
79+
80+
在这个例子中,Bullet类有一个reset方法,用于重置子弹的状态。你创建了一个用于管理Bullet对象的ObjectPool,并提供了创建和重置Bullet对象的函数。当你需要发射一颗子弹时,你从对象池中获取一个Bullet对象,并调用它的fire方法。当子弹超出屏幕范围时,你将其归还到对象池中。
81+
82+
## 提示
83+
- 为了获取最佳性能,建议在可能的情况下尽量复用对象池中的对象,而不是频繁地创建和销毁对象。
84+
- 当你的resetFn函数被调用时,你应确保该对象的状态被完全重置,以便它可以被安全地重复使用。
85+
- 虽然ObjectPool类可以帮你管理对象的生命周期,但你仍需要自己负责管理和跟踪哪些对象正在使用,哪些对象可以归还到对象池中。

server/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ services.init({
4444
port: 8080,
4545
heartbeatInterval: 10000,
4646
heartbeatTimeout: 30000,
47+
connectDBStr: 'mongodb://127.0.0.1:27017/',
48+
sessionExpireTime: 54000
4749
}).then(()=>{
4850
services.start();
4951
});

server/src/Communication/Connection.ts

+2
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,6 @@ export interface Connection {
8585
* 连接所在的房间 ID。
8686
*/
8787
roomId?: string;
88+
89+
sessionId: string;
8890
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {Connection} from "./Connection";
2+
3+
export class SessionManager {
4+
private sessions: Map<string, Connection> = new Map();
5+
6+
public createSession(connection: Connection): string {
7+
const sessionId = this.generateSessionId();
8+
connection.sessionId = sessionId;
9+
this.sessions.set(sessionId, connection);
10+
return sessionId;
11+
}
12+
13+
public getSession(sessionId: string): Connection | undefined {
14+
return this.sessions.get(sessionId);
15+
}
16+
17+
public deleteSession(sessionId: string): void {
18+
this.sessions.delete(sessionId);
19+
}
20+
21+
private generateSessionId(): string {
22+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
23+
}
24+
}

server/src/Communication/WebSocketServerConfig.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ export interface WebSocketServerConfig {
1818
* 数据库连接字符串。
1919
*/
2020
connectDBStr: string;
21+
22+
sessionExpireTime: number;
2123
}

server/src/Data/Database.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as bcrypt from 'bcrypt';
2-
import {MongoClient} from 'mongodb';
2+
import {MongoClient, ServerApiVersion} from 'mongodb';
33
import logger from "../ErrorAndLog/Logger";
44
import {UserNotExistError, WrongPasswordError} from "../ErrorAndLog/GError";
55

@@ -18,7 +18,13 @@ export class Database {
1818
* @param collectionName - 集合名
1919
*/
2020
constructor(connectionStr: string, dbName: string, collectionName: string) {
21-
this.db = new MongoClient(connectionStr);
21+
this.db = new MongoClient(connectionStr, {
22+
serverApi: {
23+
version: ServerApiVersion.v1,
24+
strict: true,
25+
deprecationErrors: true,
26+
}
27+
});
2228
this.dbName = dbName;
2329
this.collectionName = collectionName;
2430
}

server/src/Service/GServices.ts

+37-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {ErrorHandler} from "../ErrorAndLog/ErrorHandler";
1515
import {RoomManager} from "../GameLogic/RoomManager";
1616
import {UserNotExistError, WrongPasswordError} from "../ErrorAndLog/GError";
1717
import {Player} from "../GameLogic/Player"
18+
import {SessionManager} from "../Communication/SessionManager";
1819

1920
/**
2021
* GServices 类,用于管理服务器的各种服务和功能。
@@ -29,6 +30,7 @@ export class GServices {
2930
private httpServer!: HTTPServer;
3031
private webSocketServer!: WebSocketServer;
3132
private authentication!: Authentication;
33+
private sessionManager!: SessionManager;
3234

3335
private static _services: GServices;
3436
public static I() {
@@ -71,6 +73,7 @@ export class GServices {
7173
process.on('SIGINT', this.shutdown.bind(this));
7274
process.on('SIGTERM', this.shutdown.bind(this));
7375

76+
this.sessionManager = new SessionManager();
7477
this.authentication = new Authentication(config);
7578
await this.authentication.DataBase.createConnection();
7679

@@ -125,6 +128,9 @@ export class GServices {
125128
* @param connection - 连接对象。
126129
*/
127130
public handleClientConnect(connection: Connection): void {
131+
const sessionId = this.sessionManager.createSession(connection);
132+
// 发送sessionId给客户端
133+
WebSocketUtils.sendToConnection(connection, { type: 'sessionId', payload: sessionId });
128134
// 调用用户自定义的连接建立处理方法
129135
this.invokeExtensionMethod('onClientConnect', connection);
130136
}
@@ -134,6 +140,9 @@ export class GServices {
134140
* @param connection - 连接对象。
135141
*/
136142
public handleClientDisconnect(connection: Connection): void {
143+
// 当客户端断开连接时,我们不立即删除其session
144+
// 可以设置一个定时器,在一段时间后删除session
145+
setTimeout(() => this.sessionManager.deleteSession(connection.sessionId), this.config.sessionExpireTime);
137146
// 调用用户自定义的连接断开处理方法
138147
this.invokeExtensionMethod('onClientDisconnect', connection);
139148
}
@@ -176,6 +185,12 @@ export class GServices {
176185
*/
177186
public async handleMessage(connection: Connection, message: Message) {
178187
try {
188+
// 对重连请求进行特殊处理
189+
if (message.type === 'reconnect') {
190+
this.handleReconnect(connection, message.payload);
191+
return;
192+
}
193+
179194
if (!connection.isAuthenticated) {
180195
// 如果连接还没有经过身份验证,那么它只能发送身份验证消息
181196
if (message.type !== 'authentication') {
@@ -199,6 +214,10 @@ export class GServices {
199214
} else {
200215
// 身份验证通过
201216
connection.isAuthenticated = true;
217+
// 获取sessionId
218+
const sessionId = connection.sessionId;
219+
// 发送身份验证消息,带有sessionId
220+
WebSocketUtils.sendToConnection(connection, { type: 'authentication', payload: { sessionId: sessionId }});
202221
return;
203222
}
204223

@@ -214,10 +233,6 @@ export class GServices {
214233
// 处理数据消息
215234
this.handleDataMessage(connection, message.payload);
216235
break;
217-
case 'authentication':
218-
// 处理身份验证消息
219-
this.authentication.handleAuthenticationMessage(connection, message);
220-
break;
221236
case 'stateUpdate':
222237
this.handleStateUpdate(connection, message);
223238
break;
@@ -250,6 +265,24 @@ export class GServices {
250265
this.invokeExtensionMethod('onMessageReceived', connection, message);
251266
}
252267

268+
private handleReconnect(connection: Connection, payload: any): void {
269+
const { sessionId } = payload;
270+
const oldConnection = this.sessionManager.getSession(sessionId);
271+
if (oldConnection) {
272+
// 重新绑定sessionId到新连接
273+
connection.sessionId = sessionId;
274+
this.sessionManager.createSession(connection);
275+
// 恢复状态
276+
connection.state = oldConnection.state;
277+
connection.roomId = oldConnection.roomId;
278+
// 发送重连成功消息
279+
WebSocketUtils.sendToConnection(connection, { type: 'reconnectSuccess', payload: null });
280+
} else {
281+
// sessionId无效,发送重连失败消息
282+
WebSocketUtils.sendToConnection(connection, { type: 'reconnectFail', payload: null });
283+
}
284+
}
285+
253286
/**
254287
* 处理状态更新消息。
255288
* @param connection - 连接对象。

server/test/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ services.init({
66
port: 8080,
77
heartbeatInterval: 1000,
88
heartbeatTimeout: 5000,
9-
connectDBStr: 'mongodb://127.0.0.1:27017/'
9+
connectDBStr: 'mongodb://127.0.0.1:27017/',
10+
sessionExpireTime: 54000
1011
}).then(()=>{
1112
services.start();
1213
});

source/bin/gs.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,8 @@ declare module gs {
747747
private maxReconnectionAttempts;
748748
private connection;
749749
private authentication;
750+
private sessionId;
751+
private lastKnownState;
750752
constructor(serverUrl: string, username: string, password: string);
751753
private connect;
752754
sendInput(frameNumber: number, inputData: any): void;

source/bin/gs.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,8 @@ var gs;
19911991
this.serverUrl = serverUrl;
19921992
this.reconnectionAttempts = 0;
19931993
this.maxReconnectionAttempts = 10;
1994+
this.sessionId = null;
1995+
this.lastKnownState = null;
19941996
this.connection = new gs.Connection(serverUrl);
19951997
this.authentication = new gs.Authentication(this.connection);
19961998
this.connect(username, password);
@@ -2001,7 +2003,15 @@ var gs;
20012003
this.socket.addEventListener('open', function () {
20022004
console.info('[g-client]: 连接到服务器');
20032005
_this.reconnectionAttempts = 0;
2004-
_this.authentication.startAuthentication(username, password);
2006+
if (_this.sessionId) {
2007+
// 发送断线重连请求
2008+
var reconnectMsg = { type: 'reconnect', sessionId: _this.sessionId, lastKnownState: _this.lastKnownState };
2009+
_this.socket.send(JSON.stringify(reconnectMsg));
2010+
}
2011+
else {
2012+
// 开始身份验证
2013+
_this.authentication.startAuthentication(username, password);
2014+
}
20052015
});
20062016
this.socket.addEventListener('error', function (error) {
20072017
console.error('[g-client]: 发生错误:', error);
@@ -2019,9 +2029,17 @@ var gs;
20192029
this.socket.addEventListener('message', function (event) {
20202030
var message = JSON.parse(event.data);
20212031
if (message.type === 'authentication') {
2032+
_this.sessionId = message.payload.sessionId; // 存储sessionId
20222033
_this.authentication.handleAuthenticationMessage(message);
20232034
}
2035+
else if (message.type === 'sessionId') {
2036+
_this.sessionId = message.payload;
2037+
}
2038+
else if (message.type === 'stateUpdate') {
2039+
_this.lastKnownState = message.payload; // 更新lastKnownState
2040+
}
20242041
else {
2042+
console.warn("[g-client]: \u672A\u77E5\u7684\u6D88\u606F\u7C7B\u578B: " + message.type);
20252043
}
20262044
});
20272045
};

source/bin/gs.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/src/Network/GNetworkAdapter.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module gs {
55
private maxReconnectionAttempts: number = 10;
66
private connection: Connection;
77
private authentication: Authentication;
8+
private sessionId: string | null = null;
9+
private lastKnownState: any = null;
810

911
constructor(private serverUrl: string, username: string, password: string) {
1012
this.connection = new Connection(serverUrl);
@@ -19,7 +21,14 @@ module gs {
1921
console.info('[g-client]: 连接到服务器');
2022
this.reconnectionAttempts = 0;
2123

22-
this.authentication.startAuthentication(username, password);
24+
if (this.sessionId) {
25+
// 发送断线重连请求
26+
const reconnectMsg = { type: 'reconnect', sessionId: this.sessionId, lastKnownState: this.lastKnownState };
27+
this.socket.send(JSON.stringify(reconnectMsg));
28+
} else {
29+
// 开始身份验证
30+
this.authentication.startAuthentication(username, password);
31+
}
2332
});
2433

2534
this.socket.addEventListener('error', (error: Event) => {
@@ -39,9 +48,14 @@ module gs {
3948
this.socket.addEventListener('message', (event) => {
4049
const message: Message = JSON.parse(event.data);
4150
if (message.type === 'authentication') {
51+
this.sessionId = message.payload.sessionId; // 存储sessionId
4252
this.authentication.handleAuthenticationMessage(message);
53+
} else if (message.type === 'sessionId') {
54+
this.sessionId = message.payload;
55+
} else if (message.type === 'stateUpdate') {
56+
this.lastKnownState = message.payload; // 更新lastKnownState
4357
} else {
44-
58+
console.warn(`[g-client]: 未知的消息类型: ${message.type}`);
4559
}
4660
});
4761
}

0 commit comments

Comments
 (0)