Resolve container content from the template instead of copying it
A scene now stores only the container object; its content is resolved from the template on every read, with deviations kept as sparse instanceEdits on the container object. This drops the propagation bookkeeping entirely -- templateSync.ts is gone and the three structural gaps (add, remove, reparent) cannot diverge any more. Values stay editable on an instance, structure does not; Unpack writes the content into the scene, keeping ids. Play mode bakes containers into the throwaway copy before entities are built. No migration: formatVersion is 2 and the seeders write the new format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9d93020bd8
commit
ead649eec3
37 files changed
+1878
-1006
No files matched your search
@@ -1,46 +1,10 @@
|
||||
import { newId } from '../ids';
|
||||
import type { ContainerAsset } from './Asset';
|
||||
import { chainOf, type ObjectTree } from './hierarchy';
|
||||
import { findAsset, objectsWhere, type Project, type SceneObjectRef } from './Project';
|
||||
import type { SceneObject } from './SceneObject';
|
||||
|
||||
export interface TemplateLink {
|
||||
containerObject: SceneObject;
|
||||
asset: ContainerAsset;
|
||||
template: SceneObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* The nearest container ancestor whose asset holds the template `object` was instantiated from;
|
||||
* undefined when it has no template id or the link is stale.
|
||||
*/
|
||||
export function templateLinkOf(
|
||||
project: Project,
|
||||
tree: ObjectTree,
|
||||
object: SceneObject,
|
||||
): TemplateLink | undefined {
|
||||
if (!object.templateId) return undefined;
|
||||
for (const containerObject of chainOf(tree, object).slice(1)) {
|
||||
const asset = findAsset(project, containerObject.assetId);
|
||||
if (asset?.type !== 'container') continue;
|
||||
const template = asset.objects.find((t) => t.id === object.templateId);
|
||||
if (template) return { containerObject, asset, template };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every placed copy of a template object. Template objects themselves never carry a template id, so
|
||||
* one is never mistaken for a copy of itself even though the search covers container assets too.
|
||||
*/
|
||||
export function instancesOf(project: Project, templateId: string): SceneObjectRef[] {
|
||||
return objectsWhere(project, (o) => o.templateId === templateId);
|
||||
}
|
||||
import { findAsset, type Project } from './Project';
|
||||
|
||||
/**
|
||||
* Whether `wantedId` is that container or sits somewhere in its content. Editing a container's
|
||||
* template can place containers into it, and a cycle would expand into thousands of objects the
|
||||
* moment one of them is placed.
|
||||
* template can place containers into it, and a cycle would expand forever the moment one of them
|
||||
* is resolved.
|
||||
*/
|
||||
export function containerReaches(project: Project, asset: ContainerAsset, wantedId: string): boolean {
|
||||
if (asset.id === wantedId) return true;
|
||||
@@ -57,43 +21,3 @@ export function containerReaches(project: Project, asset: ContainerAsset, wanted
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MAX_NESTING = 8;
|
||||
|
||||
/**
|
||||
* One fresh object per template object, parented below `containerObjectId` or below the copy of its
|
||||
* template parent. A template object that is itself a container is expanded in turn, unless the
|
||||
* template already spells its content out.
|
||||
*/
|
||||
export function instantiateContainer(
|
||||
project: Project,
|
||||
asset: ContainerAsset,
|
||||
containerObjectId: string,
|
||||
depth = 0,
|
||||
): SceneObject[] {
|
||||
const ids = new Map(asset.objects.map((t) => [t.id, newId()]));
|
||||
const result: SceneObject[] = [];
|
||||
for (const template of asset.objects) {
|
||||
const id = ids.get(template.id)!;
|
||||
const parentId = template.parentId
|
||||
? (ids.get(template.parentId) ?? containerObjectId)
|
||||
: containerObjectId;
|
||||
result.push({
|
||||
id,
|
||||
assetId: template.assetId,
|
||||
name: template.name,
|
||||
position: { ...template.position },
|
||||
// The editor offset is a tweak on one placed object, not something copies should inherit.
|
||||
editorOffset: { x: 0, y: 0 },
|
||||
overrides: { ...template.overrides },
|
||||
parentId,
|
||||
templateId: template.id,
|
||||
});
|
||||
const nested = findAsset(project, template.assetId);
|
||||
const hasExplicitContent = asset.objects.some((o) => o.parentId === template.id);
|
||||
if (nested?.type === 'container' && !hasExplicitContent && depth < MAX_NESTING) {
|
||||
result.push(...instantiateContainer(project, nested, id, depth + 1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user