mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
PageScene: SaveManager takes Deps, no Editor reference
Resolves the longstanding `// TODO Refactor remove editor.` Drops the Editor import; constructor takes a SaveManagerDeps surface inline-built by Editor (Phase 2 idiom — same shape as CameraControllerDeps, GizmoControllerDeps, etc.). Deps surface (10 fields, single-purpose): - getVersion / setVersion — engine version (load may upgrade) - getActiveScene — proxy serialization target - getRenderer — for save thumbnail - removeTransformControls — pre-save cleanup - getCamera / refreshCamObj / changeRenderCameraAspectRatio — camera - setPositivePrompt — restore prompt on load - saveSceneState / loadSceneState — backend API current_scene_media_token moves into SaveManager as private state (only the load path used it; nothing outside SaveManager read it). The field on Editor is dropped along with the two `= null` writes in the constructor and initialize. CameraController gains refreshCamObj(scene) — re-anchors cam_obj to the named entity in the scene + sets layers. Replaces the inline post-load setup that SaveManager used to do directly. Editor.initialize also routes through the same method now (was inline at line ~410).
This commit is contained in:
1 parent
8780935b62
commit
3c8fa9010b
3 files changed
+130
-102
No files matched your search
@@ -67,8 +67,6 @@ class Editor {
|
||||
frames: number;
|
||||
lastFrameTime: number;
|
||||
|
||||
current_scene_media_token: string | null;
|
||||
|
||||
can_initialize: boolean;
|
||||
|
||||
positive_prompt: string;
|
||||
@@ -198,7 +196,25 @@ class Editor {
|
||||
this.shouldRender = false;
|
||||
|
||||
this.utils = new SceneUtils(this, this.activeScene);
|
||||
this.save_manager = new SaveManager(this);
|
||||
this.save_manager = new SaveManager({
|
||||
getVersion: () => this.version,
|
||||
setVersion: (v) => {
|
||||
this.version = v;
|
||||
},
|
||||
getActiveScene: () => this.activeScene,
|
||||
getRenderer: () => this.renderer,
|
||||
removeTransformControls: () => this.utils.removeTransformControls(),
|
||||
getCamera: () => this.cameraController.camera,
|
||||
refreshCamObj: () =>
|
||||
this.cameraController.refreshCamObj(this.activeScene.scene),
|
||||
changeRenderCameraAspectRatio: (ratio) =>
|
||||
this.cameraController.changeRenderCameraAspectRatio(ratio),
|
||||
setPositivePrompt: (prompt) => {
|
||||
this.positive_prompt = prompt;
|
||||
},
|
||||
saveSceneState: (args) => this.api_manager.saveSceneState(args),
|
||||
loadSceneState: (token) => this.api_manager.loadSceneState(token),
|
||||
});
|
||||
this.viewport = new ViewportController({
|
||||
getCamera: () => this.cameraController.camera,
|
||||
getRenderCamera: () => this.cameraController.render_camera,
|
||||
@@ -212,9 +228,6 @@ class Editor {
|
||||
// apply/revert + dependencies. HistoryManager just stores them.
|
||||
this.history = new HistoryManager({ capacity: 64 });
|
||||
|
||||
// Scene State
|
||||
this.current_scene_media_token = null;
|
||||
|
||||
this.positive_prompt =
|
||||
"((masterpiece, best quality, 8K, detailed)), colorful, epic, fantasy, (fox, red fox:1.2), no humans, 1other, ((koi pond)), outdoors, pond, rocks, stones, koi fish, ((watercolor))), lilypad, fish swimming around.";
|
||||
|
||||
@@ -391,12 +404,7 @@ class Editor {
|
||||
|
||||
this.viewport.setupResizeObserver();
|
||||
|
||||
// saving state of the scene
|
||||
this.current_scene_media_token = null;
|
||||
|
||||
this.cameraController.cam_obj = this.activeScene.get_object_by_name(
|
||||
this.cameraController.camera_name,
|
||||
);
|
||||
this.cameraController.refreshCamObj(this.activeScene.scene);
|
||||
|
||||
this.mouse_controls = new MouseControls(
|
||||
this.cameraController.camera,
|
||||
|
||||
@@ -63,6 +63,17 @@ export class CameraController {
|
||||
this.render_height = dims.height;
|
||||
}
|
||||
|
||||
// Re-anchor the camera-person object to the named entity in the scene
|
||||
// and put it on layer 1 (along with its children). Used after
|
||||
// load/restore flows when a fresh scene replaces the previous one.
|
||||
refreshCamObj(scene: THREE.Scene): void {
|
||||
this.cam_obj = scene.getObjectByName(this.camera_name);
|
||||
this.cam_obj?.layers.set(1);
|
||||
this.cam_obj?.children.forEach((child) => {
|
||||
child.layers.set(1);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggles between free-camera (edit) mode and the active render
|
||||
// camera's perspective. Lifted from SceneUtils.switchCameraView so
|
||||
// the camera state stays in one place.
|
||||
|
||||
@@ -1,38 +1,80 @@
|
||||
import * as THREE from "three";
|
||||
import { Camera } from "@storyteller/common";
|
||||
|
||||
import { SceneGenereationMetaData } from "../models/sceneGenerationMetadata";
|
||||
import { StoryTellerProxyScene } from "../proxy/storyteller_proxy_scene";
|
||||
|
||||
import { usePageSceneStore } from "../PageSceneStore";
|
||||
import { CameraAspectRatio } from "../enums";
|
||||
import type Scene from "./scene";
|
||||
|
||||
const showEditorLoader = (message?: string) =>
|
||||
usePageSceneStore.getState().showEditorLoader(message);
|
||||
const hideEditorLoader = () =>
|
||||
usePageSceneStore.getState().hideEditorLoader();
|
||||
import Editor from "./editor";
|
||||
import { Camera } from "@storyteller/common";
|
||||
|
||||
export type EditorInitializeConfig = {
|
||||
sceneToken: string;
|
||||
};
|
||||
|
||||
// need to move this into the
|
||||
export type SaveSceneStateArgs = {
|
||||
saveJson: string;
|
||||
sceneTitle: string;
|
||||
sceneToken?: string;
|
||||
sceneThumbnail: Blob | undefined;
|
||||
};
|
||||
|
||||
// Narrow contract between SaveManager and the rest of the engine. The
|
||||
// manager doesn't import Editor — every cross-subsystem reach is a
|
||||
// callback or getter on this deps object (Phase 2 idiom).
|
||||
export type SaveManagerDeps = {
|
||||
// Engine version. Read on save, written on load (older formats may
|
||||
// upgrade themselves through this).
|
||||
getVersion: () => number;
|
||||
setVersion: (v: number) => void;
|
||||
|
||||
// Active scene reference for proxy serialization.
|
||||
getActiveScene: () => Scene;
|
||||
|
||||
// Renderer canvas — only used to capture the save-thumbnail.
|
||||
getRenderer: () => THREE.WebGLRenderer | undefined;
|
||||
|
||||
// Yank the transform gizmo before snapshot so it doesn't end up in
|
||||
// the saved scene.
|
||||
removeTransformControls: () => void;
|
||||
|
||||
// Camera state. getCamera reads pose for save + load-restore;
|
||||
// refreshCamObj re-anchors the camera-person object after a load
|
||||
// replaces the scene; changeRenderCameraAspectRatio applies a saved
|
||||
// aspect ratio.
|
||||
getCamera: () => THREE.PerspectiveCamera | null;
|
||||
refreshCamObj: () => void;
|
||||
changeRenderCameraAspectRatio: (ratio: CameraAspectRatio) => void;
|
||||
|
||||
// Editor field setter used during load.
|
||||
setPositivePrompt: (prompt: string) => void;
|
||||
|
||||
// Backend API.
|
||||
saveSceneState: (args: SaveSceneStateArgs) => Promise<string>;
|
||||
loadSceneState: (token: string) => Promise<unknown>;
|
||||
};
|
||||
|
||||
export class SaveManager {
|
||||
editor: Editor;
|
||||
constructor(editor: Editor) {
|
||||
this.editor = editor;
|
||||
}
|
||||
// Token of the most-recently loaded scene. Internal load tracking
|
||||
// — nothing outside SaveManager reads it.
|
||||
private currentSceneMediaToken: string | null = null;
|
||||
|
||||
constructor(private readonly deps: SaveManagerDeps) {}
|
||||
|
||||
public getSceneJson({
|
||||
sceneGenerationMetadata,
|
||||
}: {
|
||||
sceneGenerationMetadata: SceneGenereationMetaData;
|
||||
}) {
|
||||
const proxyScene = new StoryTellerProxyScene(
|
||||
this.editor.version,
|
||||
this.editor.activeScene,
|
||||
);
|
||||
const scene_json = proxyScene.saveToScene(this.editor.version);
|
||||
console.log(scene_json);
|
||||
const version = this.deps.getVersion();
|
||||
const scene = this.deps.getActiveScene();
|
||||
const camera = this.deps.getCamera();
|
||||
const proxyScene = new StoryTellerProxyScene(version, scene);
|
||||
const scene_json = proxyScene.saveToScene(version);
|
||||
|
||||
const sceneState = usePageSceneStore.getState();
|
||||
const camerasData = sceneState.cameras.map((cam: Camera) => ({
|
||||
@@ -44,24 +86,21 @@ export class SaveManager {
|
||||
lookAt: cam.lookAt,
|
||||
}));
|
||||
|
||||
const save_data = {
|
||||
version: this.editor.version,
|
||||
return {
|
||||
version,
|
||||
scene: scene_json,
|
||||
...sceneGenerationMetadata,
|
||||
timeline: "",
|
||||
skybox: this.editor.activeScene.skybox,
|
||||
skybox: scene.skybox,
|
||||
camera_data: {
|
||||
position: this.editor.cameraController.camera?.position,
|
||||
rotation: this.editor.cameraController.camera?.rotation,
|
||||
position: camera?.position,
|
||||
rotation: camera?.rotation,
|
||||
},
|
||||
cameras: camerasData,
|
||||
selectedCameraId: sceneState.selectedCameraId,
|
||||
};
|
||||
|
||||
return save_data;
|
||||
}
|
||||
|
||||
// TODO Move this function into scene manager.
|
||||
public async saveScene({
|
||||
sceneTitle,
|
||||
sceneToken,
|
||||
@@ -71,44 +110,55 @@ export class SaveManager {
|
||||
sceneToken?: string;
|
||||
sceneGenerationMetadata: SceneGenereationMetaData;
|
||||
}): Promise<string> {
|
||||
// remove controls when saving scene.
|
||||
this.editor.utils.removeTransformControls();
|
||||
this.deps.removeTransformControls();
|
||||
showEditorLoader();
|
||||
|
||||
const sceneJson = this.getSceneJson({
|
||||
sceneGenerationMetadata,
|
||||
});
|
||||
const sceneJson = this.getSceneJson({ sceneGenerationMetadata });
|
||||
|
||||
// TODO turn scene information into and object ...
|
||||
let sceneThumbnail = undefined;
|
||||
|
||||
if (this.editor.renderer) {
|
||||
const imgData = this.editor.renderer.domElement.toDataURL();
|
||||
const response = await fetch(imgData); // Fetch the data URL
|
||||
sceneThumbnail = await response.blob(); // Convert to Blob
|
||||
let sceneThumbnail: Blob | undefined = undefined;
|
||||
const renderer = this.deps.getRenderer();
|
||||
if (renderer) {
|
||||
const imgData = renderer.domElement.toDataURL();
|
||||
const response = await fetch(imgData);
|
||||
sceneThumbnail = await response.blob();
|
||||
}
|
||||
|
||||
const result = await this.editor.api_manager.saveSceneState({
|
||||
const result = await this.deps.saveSceneState({
|
||||
saveJson: JSON.stringify(sceneJson),
|
||||
sceneTitle,
|
||||
sceneToken,
|
||||
sceneThumbnail,
|
||||
});
|
||||
|
||||
// this means error going to have to fix the plumbing on this because not using
|
||||
// the api manager
|
||||
|
||||
hideEditorLoader();
|
||||
|
||||
console.debug("Save Scene Result: ", result);
|
||||
return result; // if this is an empty string it is an error. need to migrate to api manager.
|
||||
return result;
|
||||
}
|
||||
|
||||
public async loadCache(cacheJson: string) {
|
||||
showEditorLoader();
|
||||
const scene_json = JSON.parse(cacheJson);
|
||||
await this.loadFromJson(scene_json);
|
||||
hideEditorLoader();
|
||||
}
|
||||
|
||||
public async loadScene(scene_media_token: string) {
|
||||
showEditorLoader();
|
||||
this.currentSceneMediaToken = scene_media_token;
|
||||
const scene_json = await this.deps
|
||||
.loadSceneState(this.currentSceneMediaToken)
|
||||
.catch((err) => {
|
||||
hideEditorLoader();
|
||||
throw err;
|
||||
});
|
||||
await this.loadFromJson(scene_json);
|
||||
hideEditorLoader();
|
||||
}
|
||||
|
||||
private async loadFromJson(scene_json: any) {
|
||||
const proxyScene = new StoryTellerProxyScene(
|
||||
this.editor.version,
|
||||
this.editor.activeScene,
|
||||
);
|
||||
const version = this.deps.getVersion();
|
||||
const scene = this.deps.getActiveScene();
|
||||
const proxyScene = new StoryTellerProxyScene(version, scene);
|
||||
|
||||
await proxyScene.loadFromSceneJson(
|
||||
scene_json["scene"],
|
||||
@@ -117,11 +167,10 @@ export class SaveManager {
|
||||
);
|
||||
|
||||
const camera_data = scene_json["camera_data"];
|
||||
const liveCamera = this.editor.cameraController.camera;
|
||||
const liveCamera = this.deps.getCamera();
|
||||
if (camera_data && liveCamera) {
|
||||
const camera_position: THREE.Vector3 = camera_data["position"];
|
||||
const camera_rotation: THREE.Euler = camera_data["rotation"];
|
||||
|
||||
liveCamera.position.copy(camera_position);
|
||||
liveCamera.rotation.copy(camera_rotation);
|
||||
}
|
||||
@@ -144,57 +193,17 @@ export class SaveManager {
|
||||
.setSelectedCameraId(scene_json.selectedCameraId);
|
||||
}
|
||||
|
||||
// Restore prompt text into the editor (PromptBox3D will pick it up
|
||||
// through the editor's positive_prompt field).
|
||||
if (scene_json.positivePrompt) {
|
||||
this.editor.positive_prompt = scene_json.positivePrompt;
|
||||
this.deps.setPositivePrompt(scene_json.positivePrompt);
|
||||
}
|
||||
if (scene_json.cameraAspectRatio) {
|
||||
this.editor.cameraController.changeRenderCameraAspectRatio(
|
||||
scene_json.cameraAspectRatio,
|
||||
);
|
||||
this.deps.changeRenderCameraAspectRatio(scene_json.cameraAspectRatio);
|
||||
usePageSceneStore
|
||||
.getState()
|
||||
.setCameraAspectRatio(scene_json.cameraAspectRatio);
|
||||
}
|
||||
|
||||
this.editor.version = scene_json["version"];
|
||||
const camCtrl = this.editor.cameraController;
|
||||
camCtrl.cam_obj = this.editor.activeScene.get_object_by_name(
|
||||
camCtrl.camera_name,
|
||||
);
|
||||
|
||||
camCtrl.cam_obj?.layers.set(1);
|
||||
camCtrl.cam_obj?.children.forEach((child) => {
|
||||
child.layers.set(1);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public async loadCache(cacheJson: string) {
|
||||
showEditorLoader();
|
||||
|
||||
const scene_json = JSON.parse(cacheJson);
|
||||
await this.loadFromJson(scene_json);
|
||||
|
||||
hideEditorLoader();
|
||||
}
|
||||
|
||||
// TODO Refactor remove editor.
|
||||
public async loadScene(scene_media_token: string) {
|
||||
showEditorLoader();
|
||||
|
||||
this.editor.current_scene_media_token = scene_media_token;
|
||||
|
||||
const scene_json = await this.editor.api_manager
|
||||
.loadSceneState(this.editor.current_scene_media_token)
|
||||
.catch((err) => {
|
||||
hideEditorLoader();
|
||||
throw err;
|
||||
});
|
||||
|
||||
await this.loadFromJson(scene_json);
|
||||
|
||||
hideEditorLoader();
|
||||
this.deps.setVersion(scene_json["version"]);
|
||||
this.deps.refreshCamObj();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user