fix(canvas): 预演合并 - 场景时间戳采用服务端快照,带 id 数组按条目合并
每次预演变更都会改 updatedAt,不再把它当成用户编辑冲突;带 id 的对象数组按条目合并,仅刷新场景时保留本地视口。
This commit is contained in:
@@ -24,6 +24,26 @@ function equal(a: unknown, b: unknown): boolean {
|
||||
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => equal(item, b[index]));
|
||||
}
|
||||
|
||||
function identifiedArray(value: unknown): value is Record<string, unknown>[] {
|
||||
return Array.isArray(value) && value.length > 0 && value.every((item) => record(item) && typeof item.id === "string" && item.id.length > 0);
|
||||
}
|
||||
|
||||
function mergeIdentifiedArray(current: unknown, before: unknown, after: unknown): unknown[] | undefined {
|
||||
const values = [current, before, after];
|
||||
if (!values.every((value) => Array.isArray(value))) return undefined;
|
||||
const arrays = values as unknown[][];
|
||||
if (!arrays.some((items) => items.length > 0) || !arrays.every((items) => items.length === 0 || identifiedArray(items))) return undefined;
|
||||
|
||||
const maps = arrays.map((items) => new Map(items.map((item) => [(item as Record<string, unknown>).id as string, item as Record<string, unknown>])));
|
||||
const ids = [...new Set(arrays.flatMap((items) => items.map((item) => (item as Record<string, unknown>).id as string)))];
|
||||
const merged: unknown[] = [];
|
||||
for (const id of ids) {
|
||||
const value = mergeThreeWayValue(maps[0].get(id), maps[1].get(id), maps[2].get(id));
|
||||
if (value !== undefined) merged.push(value);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
// Three-way merge: untouched local fields (e.g. a drag or prompt edit) survive.
|
||||
// A concurrently changed field or a deleted node is a conflict, never an overwrite.
|
||||
export function mergeThreeWayValue(current: unknown, before: unknown, after: unknown): unknown {
|
||||
@@ -33,12 +53,23 @@ export function mergeThreeWayValue(current: unknown, before: unknown, after: unk
|
||||
const next = { ...current };
|
||||
for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
|
||||
if (["__proto__", "constructor", "prototype"].includes(key)) throw new Error("无效的画布增量字段");
|
||||
const value = mergeThreeWayValue(current[key], before[key], after[key]);
|
||||
let value: unknown;
|
||||
try {
|
||||
value = mergeThreeWayValue(current[key], before[key], after[key]);
|
||||
} catch (error) {
|
||||
// Scene timestamps are persistence bookkeeping emitted for every
|
||||
// Previs mutation, not user-editable content. If both sides
|
||||
// touched only that timestamp, the server snapshot wins.
|
||||
if (key !== "updatedAt") throw error;
|
||||
value = after[key];
|
||||
}
|
||||
if (value === undefined) delete next[key];
|
||||
else next[key] = value;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
const mergedArray = mergeIdentifiedArray(current, before, after);
|
||||
if (mergedArray !== undefined) return mergedArray;
|
||||
throw new Error("Agent 画布增量与本地内容冲突,需要校准;已保留本地编辑");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user