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
@@ -16,6 +16,7 @@ import {
|
||||
listContainers,
|
||||
objectHolders,
|
||||
} from '../../src/core/model/Project';
|
||||
import { instanceChildId } from '../../src/core/model/instance';
|
||||
import { makeBehavior, makeSpriteScene, place } from '../helpers';
|
||||
|
||||
/** A behavior with a `speed` variable, placed and overridden to 42, then wrapped in a container. */
|
||||
@@ -27,7 +28,9 @@ function containerWithOverride() {
|
||||
const container = new CreateContainerCommand(f.sceneId, [objectId]);
|
||||
f.stack.execute(container);
|
||||
const template = () => (findAsset(f.project, container.assetId) as ContainerAsset).objects[0]!;
|
||||
return { ...f, behaviorId: behavior.id, containerId: container.assetId, objectId, template };
|
||||
// The object moved into the template; what the scene shows is resolved from it.
|
||||
const placed = () => f.object(instanceChildId(container.objectId, template().id));
|
||||
return { ...f, behaviorId: behavior.id, containerId: container.assetId, objectId, template, placed };
|
||||
}
|
||||
|
||||
describe('object holders', () => {
|
||||
@@ -55,17 +58,18 @@ describe('object holders', () => {
|
||||
});
|
||||
|
||||
it('finds usages inside container templates, not just in scenes', () => {
|
||||
const { project, behaviorId, template } = containerWithOverride();
|
||||
// The placed object and the template copy the container was built from.
|
||||
expect(findUsages(project, behaviorId)).toHaveLength(2);
|
||||
const { project, behaviorId, containerId, template } = containerWithOverride();
|
||||
// Only the template: what the scene shows is resolved from it, never stored.
|
||||
const usages = findUsages(project, behaviorId);
|
||||
expect(usages).toHaveLength(1);
|
||||
expect(usages[0]!.holder.id).toBe(containerId);
|
||||
expect(template().overrides.speed).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RemoveVariableCommand', () => {
|
||||
it('clears the override on container templates too, and puts it back on undo', () => {
|
||||
const { stack, behaviorId, objectId, sceneOf, template } = containerWithOverride();
|
||||
const placed = () => sceneOf().objects.find((o) => o.id === objectId)!;
|
||||
const { stack, behaviorId, template, placed } = containerWithOverride();
|
||||
|
||||
stack.execute(new RemoveVariableCommand(behaviorId, 'speed'));
|
||||
expect(placed().overrides.speed).toBeUndefined();
|
||||
@@ -77,6 +81,40 @@ describe('RemoveVariableCommand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A behavior inside a placed container is not stored in the scene, and a value changed on it lives
|
||||
* in the instance's `instanceEdits`. Anything rewriting overrides project-wide has to reach in
|
||||
* there, or renaming a variable silently drops the value.
|
||||
*/
|
||||
describe('variables overridden on container content', () => {
|
||||
function withInstanceEdit() {
|
||||
const f = containerWithOverride();
|
||||
f.stack.execute(new SetOverrideCommand(f.sceneId, f.placed().id, 'speed', 7));
|
||||
const edits = () =>
|
||||
f.sceneOf().objects.find((o) => o.id !== f.objectId)!.instanceEdits[f.template().id]!.overrides;
|
||||
return { ...f, edits };
|
||||
}
|
||||
|
||||
it('renames the key kept on the instance', () => {
|
||||
const { stack, behaviorId, edits, placed } = withInstanceEdit();
|
||||
expect(edits().speed).toBe(7);
|
||||
stack.execute(new RenameVariableCommand(behaviorId, 'speed', 'velocity'));
|
||||
expect(edits().speed).toBeUndefined();
|
||||
expect(edits().velocity).toBe(7);
|
||||
expect(placed().overrides.velocity).toBe(7);
|
||||
stack.undo();
|
||||
expect(edits().speed).toBe(7);
|
||||
});
|
||||
|
||||
it('removes the value kept on the instance, and puts it back on undo', () => {
|
||||
const { stack, behaviorId, edits } = withInstanceEdit();
|
||||
stack.execute(new RemoveVariableCommand(behaviorId, 'speed'));
|
||||
expect(edits().speed).toBeUndefined();
|
||||
stack.undo();
|
||||
expect(edits().speed).toBe(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RenameVariableCommand', () => {
|
||||
it('renames the override on container templates too', () => {
|
||||
const { stack, behaviorId, template } = containerWithOverride();
|
||||
|
||||
Reference in new issue
Block a user