mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
Cache object properties to avoid extra RPC
Serialize non-function properties of server-side objects as cachedProps so simple property reads don't require additional RPCs. Added cachedProps to SerializedValue, have RpcServer.storeObjectRef serialize and attach cachedProps (skipping functions), updated serializer to deserialize cachedProps and pass them to proxyCreator, and updated client proxy creation to accept cachedProps and return cached top-level properties directly. Tests updated to expect direct property access for serialized/simple objects and arrays.
This commit is contained in:
parent
3bead89d46
commit
74781fda0a
@ -48,7 +48,7 @@ export function createDeepProxy<T = unknown> (options: DeepProxyOptions): T {
|
||||
if (!cb) throw new Error(`Nested callback not found: ${id}`);
|
||||
return cb;
|
||||
},
|
||||
proxyCreator: (path, proxyRefId) => createProxyAtPath(path, proxyRefId),
|
||||
proxyCreator: (path, proxyRefId, cachedProps) => createProxyAtPath(path, proxyRefId, cachedProps),
|
||||
}));
|
||||
const result = await callback(...args);
|
||||
return serialize(result, { callbackRegistry });
|
||||
@ -57,8 +57,11 @@ export function createDeepProxy<T = unknown> (options: DeepProxyOptions): T {
|
||||
|
||||
/**
|
||||
* 在指定路径创建代理
|
||||
* @param path 路径
|
||||
* @param refId 远程对象引用 ID
|
||||
* @param cachedProps 缓存的属性值(避免属性访问需要 RPC)
|
||||
*/
|
||||
function createProxyAtPath (path: PropertyKey[], refId?: string): unknown {
|
||||
function createProxyAtPath (path: PropertyKey[], refId?: string, cachedProps?: Record<string, unknown>): unknown {
|
||||
const proxyMeta: ProxyMeta = {
|
||||
path: [...path],
|
||||
isProxy: true,
|
||||
@ -80,7 +83,12 @@ export function createDeepProxy<T = unknown> (options: DeepProxyOptions): T {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 返回新的子路径代理(继承 refId)
|
||||
// 检查缓存属性(仅顶层代理,即 path 为空时)
|
||||
if (path.length === 0 && cachedProps && typeof prop === 'string' && prop in cachedProps) {
|
||||
return cachedProps[prop];
|
||||
}
|
||||
|
||||
// 返回新的子路径代理(继承 refId,不继承 cachedProps)
|
||||
return createProxyAtPath([...path, prop], refId);
|
||||
},
|
||||
|
||||
@ -203,7 +211,7 @@ export function createDeepProxy<T = unknown> (options: DeepProxyOptions): T {
|
||||
if (!cb) throw new Error(`Callback not found: ${id}`);
|
||||
return cb;
|
||||
},
|
||||
proxyCreator: (proxyPath, proxyRefId) => createProxyAtPath(proxyPath, proxyRefId),
|
||||
proxyCreator: (proxyPath, proxyRefId, cachedProps) => createProxyAtPath(proxyPath, proxyRefId, cachedProps),
|
||||
});
|
||||
return deserialized;
|
||||
}
|
||||
@ -214,7 +222,7 @@ export function createDeepProxy<T = unknown> (options: DeepProxyOptions): T {
|
||||
if (!cb) throw new Error(`Callback not found: ${id}`);
|
||||
return cb;
|
||||
},
|
||||
proxyCreator: (proxyPath, proxyRefId) => createProxyAtPath(proxyPath, proxyRefId),
|
||||
proxyCreator: (proxyPath, proxyRefId, cachedProps) => createProxyAtPath(proxyPath, proxyRefId, cachedProps),
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ export interface DeserializeContext {
|
||||
/** 回调解析器 */
|
||||
callbackResolver?: (id: string) => Function;
|
||||
/** 代理创建器 */
|
||||
proxyCreator?: (path: PropertyKey[], refId?: string) => unknown;
|
||||
proxyCreator?: (path: PropertyKey[], refId?: string, cachedProps?: Record<string, unknown>) => unknown;
|
||||
/** 对象引用解析器 */
|
||||
refResolver?: (refId: string) => unknown;
|
||||
}
|
||||
@ -368,7 +368,15 @@ export function deserialize (data: SerializedValue, context: DeserializeContext
|
||||
}
|
||||
// 否则创建代理(客户端场景)
|
||||
if (proxyCreator) {
|
||||
return proxyCreator([], data.refId);
|
||||
// 反序列化缓存的属性
|
||||
let cachedValues: Record<string, unknown> | undefined;
|
||||
if (data.cachedProps) {
|
||||
cachedValues = {};
|
||||
for (const [key, val] of Object.entries(data.cachedProps)) {
|
||||
cachedValues[key] = deserialize(val, context);
|
||||
}
|
||||
}
|
||||
return proxyCreator([], data.refId, cachedValues);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
@ -155,15 +155,36 @@ export class RpcServer {
|
||||
|
||||
/**
|
||||
* 存储对象引用并返回序列化的引用
|
||||
* 同时序列化可序列化的属性值,避免属性访问需要额外 RPC
|
||||
*/
|
||||
private storeObjectRef (value: unknown): SerializedValue {
|
||||
const refId = generateRefId();
|
||||
this.objectRefs.set(refId, value);
|
||||
const className = value?.constructor?.name;
|
||||
|
||||
// 序列化非函数属性
|
||||
const cachedProps: Record<string, SerializedValue> = {};
|
||||
if (value && typeof value === 'object') {
|
||||
for (const key of Object.keys(value)) {
|
||||
const propValue = (value as Record<string, unknown>)[key];
|
||||
// 跳过函数(方法需要远程调用)
|
||||
if (typeof propValue === 'function') {
|
||||
continue;
|
||||
}
|
||||
// 序列化属性值
|
||||
try {
|
||||
cachedProps[key] = serialize(propValue, { callbackRegistry: this.localCallbacks });
|
||||
} catch {
|
||||
// 序列化失败的属性跳过,让客户端通过 RPC 获取
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: SerializedValueType.OBJECT_REF,
|
||||
refId,
|
||||
className: className !== 'Object' ? className : undefined,
|
||||
cachedProps: Object.keys(cachedProps).length > 0 ? cachedProps : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -84,6 +84,8 @@ export interface SerializedValue {
|
||||
elements?: SerializedValue[];
|
||||
/** 远程对象引用 ID(用于保持代理能力) */
|
||||
refId?: string;
|
||||
/** 缓存的属性值(OBJECT_REF 时使用,避免属性访问需要 RPC) */
|
||||
cachedProps?: Record<string, SerializedValue>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -786,9 +786,9 @@ describe('napcat-rpc RPC', () => {
|
||||
});
|
||||
|
||||
const user = await api.fetchUser(123);
|
||||
// 对象返回代理
|
||||
expect(await user.id).toBe(123);
|
||||
expect(await user.name).toBe('User123');
|
||||
// 对象返回代理,属性已缓存,可直接访问
|
||||
expect(user.id).toBe(123);
|
||||
expect(user.name).toBe('User123');
|
||||
});
|
||||
});
|
||||
|
||||
@ -866,16 +866,15 @@ describe('napcat-rpc RPC', () => {
|
||||
});
|
||||
|
||||
const items = await client.getItems();
|
||||
// 数组返回代理
|
||||
const item0 = await items[0];
|
||||
const item1 = await items[1];
|
||||
expect(await item0!.id).toBe(1);
|
||||
expect(await item0!.name).toBe('Item1');
|
||||
expect(await item1!.id).toBe(2);
|
||||
// 数组直接序列化,元素可直接访问
|
||||
expect(items[0]!.id).toBe(1);
|
||||
expect(items[0]!.name).toBe('Item1');
|
||||
expect(items[1]!.id).toBe(2);
|
||||
|
||||
const item = await client.getItemById(2);
|
||||
expect(await item!.id).toBe(2);
|
||||
expect(await item!.name).toBe('Item2');
|
||||
// 普通对象(无方法)直接序列化
|
||||
expect(item!.id).toBe(2);
|
||||
expect(item!.name).toBe('Item2');
|
||||
});
|
||||
|
||||
it('should handle Map and Set in nested structures', async () => {
|
||||
@ -1095,9 +1094,9 @@ describe('napcat-rpc RPC', () => {
|
||||
expect(greeting).toBe('Hi, I am Alice');
|
||||
|
||||
const info = await user.getInfo();
|
||||
// 对象返回代理
|
||||
expect(await info.name).toBe('Alice');
|
||||
expect(await info.age).toBe(25);
|
||||
// getInfo 返回普通对象,直接序列化,属性可直接访问
|
||||
expect(info.name).toBe('Alice');
|
||||
expect(info.age).toBe(25);
|
||||
|
||||
// 更新远程对象状态
|
||||
const newAge = await user.updateAge(30);
|
||||
@ -1105,7 +1104,7 @@ describe('napcat-rpc RPC', () => {
|
||||
|
||||
// 验证状态被更新
|
||||
const updatedInfo = await user.getInfo();
|
||||
expect(await updatedInfo.age).toBe(30);
|
||||
expect(updatedInfo.age).toBe(30);
|
||||
});
|
||||
|
||||
it('should support chained method calls on returned class instance', async () => {
|
||||
@ -1218,7 +1217,7 @@ describe('napcat-rpc RPC', () => {
|
||||
});
|
||||
|
||||
it('should proxy simple objects and allow property access', async () => {
|
||||
// 现在所有对象都返回代理,访问属性需要通过 RPC
|
||||
// 简单对象(无方法)直接序列化,属性可直接访问
|
||||
const { client } = createRpcPair({
|
||||
getSimpleData () {
|
||||
return { id: 1, name: 'test', active: true };
|
||||
@ -1226,10 +1225,10 @@ describe('napcat-rpc RPC', () => {
|
||||
});
|
||||
|
||||
const data = await client.getSimpleData();
|
||||
// 对象现在也是代理,访问属性返回代理,await 后获取值
|
||||
expect(await data.id).toBe(1);
|
||||
expect(await data.name).toBe('test');
|
||||
expect(await data.active).toBe(true);
|
||||
// 普通对象直接序列化,属性可直接访问
|
||||
expect(data.id).toBe(1);
|
||||
expect(data.name).toBe('test');
|
||||
expect(data.active).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle async methods in returned class', async () => {
|
||||
@ -1288,9 +1287,10 @@ describe('napcat-rpc RPC', () => {
|
||||
|
||||
const factory = await client.getFactory();
|
||||
|
||||
// 调用返回代理上的方法
|
||||
// 调用返回代理上的方法,返回带方法的对象被代理
|
||||
const obj = await factory.create('widget1');
|
||||
expect(await obj.name).toBe('widget1');
|
||||
// 属性已缓存,可直接访问
|
||||
expect(obj.name).toBe('widget1');
|
||||
|
||||
// 在返回代理上调用构造函数
|
||||
const Widget = await factory.Widget;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user