File size: 3,571 Bytes
970fc3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { seedScene, validateScene } from "../shared/scene.mjs";
import { readFile, writeFile, rename } from "node:fs/promises";
import { validateCapturePlan } from "../shared/capture-boundary.mjs";
import { withKnownAssemblies } from "../shared/transforms.mjs";
import { reconcileBehaviors } from "../shared/behaviors.mjs";
async function createSceneStore(appRuntime) {
  let state = { revision: 0, scene: seedScene(), source: "sample", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), savedAt: null, saveMode: null };
  try {
    const saved = JSON.parse(await readFile(appRuntime.sceneLocation.file, "utf8"));
    validateScene(saved.scene);
    if (saved.capturePlan) validateCapturePlan(saved.capturePlan);
    state = { ...saved, savedAt: saved.savedAt || saved.updatedAt, saveMode: saved.saveMode || "auto" };
  } catch (error) {
    if (appRuntime.sceneLocation.id) throw error;
    if (error.code !== "ENOENT") console.warn("Saved scene invalid; using sample:", error.message);
  }
  const history = [];
  const clients = /* @__PURE__ */ new Set();
  const assembled = withKnownAssemblies(state.scene);
  if (JSON.stringify(assembled) !== JSON.stringify(state.scene)) {
    state = { ...state, scene: assembled, revision: state.revision + 1 };
    await persistScene(state);
  }
  let mutationBusy = false;
  const broadcast = () => {
    for (const res of clients) res.write(`data: ${JSON.stringify(state)}

`);
  };
  async function persistScene(next) {
    await writeFile(appRuntime.sceneLocation.temporary, JSON.stringify(next, null, 2));
    await rename(appRuntime.sceneLocation.temporary, appRuntime.sceneLocation.file);
  }
  async function commit(scene, source, recordHistory = true, metadata = state, beforePersist, productionReady = false) {
    if (mutationBusy) throw new Error("Scene is saving. Try again shortly.");
    mutationBusy = true;
    try {
      scene = reconcileBehaviors(scene);
      if ("productionReady" in scene) scene = { ...scene, productionReady };
      validateScene(scene);
      const now = (/* @__PURE__ */ new Date()).toISOString(), next = { ...metadata, revision: state.revision + 1, scene, source, updatedAt: now, savedAt: now, saveMode: "auto" };
      if (beforePersist) await beforePersist();
      await persistScene(next);
      if (recordHistory) {
        history.push(structuredClone(state));
        if (history.length > 30) history.shift();
      }
      state = next;
      broadcast();
      return state;
    } finally {
      mutationBusy = false;
    }
  }
  async function saveCurrentScene(revision) {
    revisionCheck(revision);
    if (mutationBusy) throw new Error("Scene is saving. Try again shortly.");
    mutationBusy = true;
    try {
      const next = { ...state, savedAt: (/* @__PURE__ */ new Date()).toISOString(), saveMode: "manual" };
      await persistScene(next);
      state = next;
      broadcast();
      return state;
    } finally {
      mutationBusy = false;
    }
  }
  function revisionCheck(revision) {
    if (revision !== state.revision) throw new Error("Scene changed. Retry using the current version.");
  }
  function selectionCheck(ids) {
    if (!Array.isArray(ids) || ids.length > 180 || ids.some((id) => typeof id !== "string" || !state.scene.objects.some((o) => o.id === id))) throw new Error("Selection contains an invalid object");
  }
  return { get state() {
    return state;
  }, get mutationBusy() {
    return mutationBusy;
  }, history, clients, broadcast, commit, saveCurrentScene, revisionCheck, selectionCheck };
}
export {
  createSceneStore
};