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:
手瓜一十雪
2026-02-02 18:59:23 +08:00
parent a27df71f5d
commit ba473d57f0
5 changed files with 68 additions and 29 deletions

View File

@@ -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),
});
})();
}

View File

@@ -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 {};

View File

@@ -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,
};
}

View File

@@ -84,6 +84,8 @@ export interface SerializedValue {
elements?: SerializedValue[];
/** 远程对象引用 ID用于保持代理能力 */
refId?: string;
/** 缓存的属性值OBJECT_REF 时使用,避免属性访问需要 RPC */
cachedProps?: Record<string, SerializedValue>;
}
/**