fix(studio): Team Create — синхрон скриптов (upsertScript/removeScript)

Раньше синхронизировались только примитивы/модели/блоки, а скрипты нет —
у соавтора было 0 скриптов. Перехватываем scene.upsertScript/removeScript,
шлём op scriptUpsert/scriptRemove; applyRemoteOp применяет их у соавтора
(список в Hierarchy обновляется штатным setInterval). Лимит соавторов 5
(MAX_COLLABORATORS в realtime StudioRoom, задеплоен на VM110).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-08 06:28:45 +03:00
parent 9c990bb80c
commit 58edaef2ab

View File

@ -161,6 +161,48 @@ export class StudioCollab {
return r;
};
}
// Скрипты — синхрон создания/редактирования (upsertScript) и удаления
// (removeScript). Скрипты живут в scene._scripts[] ({id, code, target,
// name}); UI зовёт scene.upsertScript / scene.removeScript.
const sc = this.scene;
if (sc && !sc.__collabScriptsPatched) {
sc.__collabScriptsPatched = true;
if (typeof sc.upsertScript === 'function') {
const origUpsert = sc.upsertScript.bind(sc);
sc.upsertScript = function (id, code, target, name) {
const r = origUpsert(id, code, target, name);
if (!self._applyingRemote) {
// id может быть сгенерён внутри upsertScript, если был null —
// достаём фактический из _scripts (последний с этим code).
let realId = id;
if (realId == null) {
const arr = sc._scripts || [];
realId = arr.length ? arr[arr.length - 1].id : null;
}
const rec = (sc._scripts || []).find((s) => s.id === realId);
if (rec) {
self.sendOp({
op: 'scriptUpsert',
id: rec.id,
code: rec.code,
target: rec.target ?? null,
name: rec.name ?? null,
});
}
}
return r;
};
}
if (typeof sc.removeScript === 'function') {
const origRemoveScript = sc.removeScript.bind(sc);
sc.removeScript = function (id) {
const r = origRemoveScript(id);
if (!self._applyingRemote) self.sendOp({ op: 'scriptRemove', id });
return r;
};
}
}
}
/** Снять обёртки (при выходе из коллаба восстановить оригиналы — простой флаг). */
@ -387,6 +429,17 @@ export function applyRemoteOp(scene, op) {
case 'blockRemove':
bm?.removeBlock?.(op.x, op.y, op.z);
return;
case 'scriptUpsert':
// Создание/редактирование скрипта у соавтора. _applyingRemote уже
// выставлен (см. _applyRemoteOp) → обёртка upsertScript не зашлёт
// эхо обратно. _onSceneChange внутри обновит React-панели.
scene.upsertScript?.(op.id, op.code, op.target ?? null, op.name ?? null);
scene._onCollabScriptsChange?.();
return;
case 'scriptRemove':
scene.removeScript?.(op.id);
scene._onCollabScriptsChange?.();
return;
}
switch (t) {
case 'add': {