feat(rpc,network): 新增 RPC 库并迁移网络模块 (#364)
* feat(rpc,network): 新增 RPC 库并迁移网络模块 ## @esengine/rpc (新增) - 新增类型安全的 RPC 库,支持 WebSocket 通信 - 新增 RpcClient 类:connect/disconnect, call/send/on/off/once 方法 - 新增 RpcServer 类:Node.js WebSocket 服务端 - 新增编解码系统:支持 JSON 和 MessagePack - 新增 TextEncoder/TextDecoder polyfill,兼容微信小游戏平台 - 新增 WebSocketAdapter 接口,支持跨平台 WebSocket 抽象 ## @esengine/network (重构) - 重构 NetworkService:拆分为 RpcService 基类和 GameNetworkService - 新增 gameProtocol:类型安全的 API 和消息定义 - 新增类型安全便捷方法:sendInput(), onSync(), onSpawn(), onDespawn() - 更新 NetworkPlugin 使用新的服务架构 - 移除 TSRPC 依赖,改用 @esengine/rpc ## 文档 - 新增 RPC 模块文档(中英文) - 更新 Network 模块文档(中英文) - 更新侧边栏导航 * fix(network,cli): 修复 CI 构建和更新 CLI 适配器 ## 修复 - 在 tsconfig.build.json 添加 rpc 引用,修复类型声明生成 ## CLI 更新 - 更新 nodejs 适配器使用新的 @esengine/rpc - 生成的服务器代码使用 RpcServer 替代旧的 GameServer - 添加 ws 和 @types/ws 依赖 - 更新 README 模板中的客户端连接示例 * chore: 添加 CLI changeset * fix(ci): add @esengine/rpc to build and check scripts - Add rpc package to CI build step (must build before network) - Add rpc to type-check:framework, lint:framework, test:ci:framework * fix(rpc,network): fix tsconfig for declaration generation - Remove composite mode from rpc (not needed, causes CI issues) - Remove rpc from network project references (resolves via node_modules) - Remove unused references from network tsconfig.build.json
This commit is contained in:
9
packages/framework/rpc/src/codec/index.ts
Normal file
9
packages/framework/rpc/src/codec/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @zh 编解码器模块
|
||||
* @en Codec Module
|
||||
*/
|
||||
|
||||
export type { Codec } from './types'
|
||||
export { json } from './json'
|
||||
export { msgpack } from './msgpack'
|
||||
export { textEncode, textDecode } from './polyfill'
|
||||
30
packages/framework/rpc/src/codec/json.ts
Normal file
30
packages/framework/rpc/src/codec/json.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @zh JSON 编解码器
|
||||
* @en JSON Codec
|
||||
*/
|
||||
|
||||
import type { Packet } from '../types'
|
||||
import type { Codec } from './types'
|
||||
import { textDecode } from './polyfill'
|
||||
|
||||
/**
|
||||
* @zh 创建 JSON 编解码器
|
||||
* @en Create JSON codec
|
||||
*
|
||||
* @zh 适用于开发调试,可读性好
|
||||
* @en Suitable for development, human-readable
|
||||
*/
|
||||
export function json(): Codec {
|
||||
return {
|
||||
encode(packet: Packet): string {
|
||||
return JSON.stringify(packet)
|
||||
},
|
||||
|
||||
decode(data: string | Uint8Array): Packet {
|
||||
const str = typeof data === 'string'
|
||||
? data
|
||||
: textDecode(data)
|
||||
return JSON.parse(str) as Packet
|
||||
},
|
||||
}
|
||||
}
|
||||
34
packages/framework/rpc/src/codec/msgpack.ts
Normal file
34
packages/framework/rpc/src/codec/msgpack.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @zh MessagePack 编解码器
|
||||
* @en MessagePack Codec
|
||||
*/
|
||||
|
||||
import { Packr, Unpackr } from 'msgpackr'
|
||||
import type { Packet } from '../types'
|
||||
import type { Codec } from './types'
|
||||
import { textEncode } from './polyfill'
|
||||
|
||||
/**
|
||||
* @zh 创建 MessagePack 编解码器
|
||||
* @en Create MessagePack codec
|
||||
*
|
||||
* @zh 适用于生产环境,体积更小、速度更快
|
||||
* @en Suitable for production, smaller size and faster speed
|
||||
*/
|
||||
export function msgpack(): Codec {
|
||||
const encoder = new Packr({ structuredClone: true })
|
||||
const decoder = new Unpackr({ structuredClone: true })
|
||||
|
||||
return {
|
||||
encode(packet: Packet): Uint8Array {
|
||||
return encoder.pack(packet)
|
||||
},
|
||||
|
||||
decode(data: string | Uint8Array): Packet {
|
||||
const buf = typeof data === 'string'
|
||||
? textEncode(data)
|
||||
: data
|
||||
return decoder.unpack(buf) as Packet
|
||||
},
|
||||
}
|
||||
}
|
||||
112
packages/framework/rpc/src/codec/polyfill.ts
Normal file
112
packages/framework/rpc/src/codec/polyfill.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @zh 平台兼容性 polyfill
|
||||
* @en Platform compatibility polyfill
|
||||
*
|
||||
* @zh 为微信小游戏等不支持原生 TextEncoder/TextDecoder 的平台提供兼容层
|
||||
* @en Provides compatibility layer for platforms like WeChat Mini Games that don't support native TextEncoder/TextDecoder
|
||||
*/
|
||||
|
||||
/**
|
||||
* @zh 获取全局 TextEncoder 实现
|
||||
* @en Get global TextEncoder implementation
|
||||
*/
|
||||
function getTextEncoder(): { encode(str: string): Uint8Array } {
|
||||
if (typeof TextEncoder !== 'undefined') {
|
||||
return new TextEncoder()
|
||||
}
|
||||
return {
|
||||
encode(str: string): Uint8Array {
|
||||
const utf8: number[] = []
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let charCode = str.charCodeAt(i)
|
||||
if (charCode < 0x80) {
|
||||
utf8.push(charCode)
|
||||
} else if (charCode < 0x800) {
|
||||
utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f))
|
||||
} else if (charCode >= 0xd800 && charCode <= 0xdbff) {
|
||||
i++
|
||||
const low = str.charCodeAt(i)
|
||||
charCode = 0x10000 + ((charCode - 0xd800) << 10) + (low - 0xdc00)
|
||||
utf8.push(
|
||||
0xf0 | (charCode >> 18),
|
||||
0x80 | ((charCode >> 12) & 0x3f),
|
||||
0x80 | ((charCode >> 6) & 0x3f),
|
||||
0x80 | (charCode & 0x3f)
|
||||
)
|
||||
} else {
|
||||
utf8.push(
|
||||
0xe0 | (charCode >> 12),
|
||||
0x80 | ((charCode >> 6) & 0x3f),
|
||||
0x80 | (charCode & 0x3f)
|
||||
)
|
||||
}
|
||||
}
|
||||
return new Uint8Array(utf8)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取全局 TextDecoder 实现
|
||||
* @en Get global TextDecoder implementation
|
||||
*/
|
||||
function getTextDecoder(): { decode(data: Uint8Array): string } {
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder()
|
||||
}
|
||||
return {
|
||||
decode(data: Uint8Array): string {
|
||||
let str = ''
|
||||
let i = 0
|
||||
while (i < data.length) {
|
||||
const byte1 = data[i++]
|
||||
if (byte1 < 0x80) {
|
||||
str += String.fromCharCode(byte1)
|
||||
} else if ((byte1 & 0xe0) === 0xc0) {
|
||||
const byte2 = data[i++]
|
||||
str += String.fromCharCode(((byte1 & 0x1f) << 6) | (byte2 & 0x3f))
|
||||
} else if ((byte1 & 0xf0) === 0xe0) {
|
||||
const byte2 = data[i++]
|
||||
const byte3 = data[i++]
|
||||
str += String.fromCharCode(
|
||||
((byte1 & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
|
||||
)
|
||||
} else if ((byte1 & 0xf8) === 0xf0) {
|
||||
const byte2 = data[i++]
|
||||
const byte3 = data[i++]
|
||||
const byte4 = data[i++]
|
||||
const codePoint =
|
||||
((byte1 & 0x07) << 18) |
|
||||
((byte2 & 0x3f) << 12) |
|
||||
((byte3 & 0x3f) << 6) |
|
||||
(byte4 & 0x3f)
|
||||
const offset = codePoint - 0x10000
|
||||
str += String.fromCharCode(
|
||||
0xd800 + (offset >> 10),
|
||||
0xdc00 + (offset & 0x3ff)
|
||||
)
|
||||
}
|
||||
}
|
||||
return str
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const encoder = getTextEncoder()
|
||||
const decoder = getTextDecoder()
|
||||
|
||||
/**
|
||||
* @zh 将字符串编码为 UTF-8 字节数组
|
||||
* @en Encode string to UTF-8 byte array
|
||||
*/
|
||||
export function textEncode(str: string): Uint8Array {
|
||||
return encoder.encode(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 将 UTF-8 字节数组解码为字符串
|
||||
* @en Decode UTF-8 byte array to string
|
||||
*/
|
||||
export function textDecode(data: Uint8Array): string {
|
||||
return decoder.decode(data)
|
||||
}
|
||||
24
packages/framework/rpc/src/codec/types.ts
Normal file
24
packages/framework/rpc/src/codec/types.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @zh 编解码器类型定义
|
||||
* @en Codec Type Definitions
|
||||
*/
|
||||
|
||||
import type { Packet } from '../types'
|
||||
|
||||
/**
|
||||
* @zh 编解码器接口
|
||||
* @en Codec interface
|
||||
*/
|
||||
export interface Codec {
|
||||
/**
|
||||
* @zh 编码数据包
|
||||
* @en Encode packet
|
||||
*/
|
||||
encode(packet: Packet): string | Uint8Array
|
||||
|
||||
/**
|
||||
* @zh 解码数据包
|
||||
* @en Decode packet
|
||||
*/
|
||||
decode(data: string | Uint8Array): Packet
|
||||
}
|
||||
Reference in New Issue
Block a user