Measure a deviation against the base, not the innermost template
An instance edit was dropped as soon as its value matched the template object. With nested containers the template is not what the value falls back to: the outer container holds an edit for the object below it, and that one wins. If the inner template happened to carry the same value the user typed, the edit was thrown away and the value jumped to whatever the container in between says. Reported against the Jump-and-Run project: a coin narrowed to 8 inside "level", then set back to the sprite's own 24 in the scene, snapped back to 8. Reproduced through the inspector before touching anything. InstanceTarget now offers `base`: the template with the edits of every container in between woven in. Everything that judges deviation uses it - setOverride/setName/setPosition, both Revert commands, and the gold marks in the inspector. Writing still goes into the real template, which is where "apply to container" belongs. `base` is built on every read. Apply writes into the template and then asks whether the edit still deviates, so a base captured beforehand would answer for the template as it was - the e2e suite caught exactly that. Also in here, because it corrects the commit before it: placing an asset by double-clicking its row is gone, as asked for in asset-doppelklick.md. Dragging is the only way to place now. That took AssetPanel.place, the PlacementTarget interface, Editor.placementTarget and the two worldCenter() methods that existed only to feed it. A double-click on a scene asset still makes it the active scene.
This commit is contained in:
1 parent
75d377af39
commit
d7c8d3e579
16 files changed
+180
-77
No files matched your search
@@ -201,6 +201,15 @@ export interface InstanceTarget {
|
||||
/** The container asset that declares `template` – the one to open to change it for good. */
|
||||
asset: ContainerAsset;
|
||||
template: SceneObject;
|
||||
/**
|
||||
* What the object would show without this instance's edit: the template with the edits of every
|
||||
* container in between already woven in. Only this is a base a value can fall back to, so it –
|
||||
* not `template` – decides whether an edit deviates. The two differ once containers nest.
|
||||
*
|
||||
* Built fresh on every read, because "apply" writes into `template` and then asks whether the
|
||||
* edit still deviates – a base captured beforehand would answer for the old template.
|
||||
*/
|
||||
readonly base: SceneObject;
|
||||
}
|
||||
|
||||
/** Where a derived id points: the instance holding the edits, and the template object behind it. */
|
||||
@@ -215,16 +224,35 @@ export function instanceTargetOf(
|
||||
const split = splitInstanceId(objectId);
|
||||
const instance = split && holder.objects.find((o) => o.id === split.instanceId);
|
||||
if (!split || !instance) return undefined;
|
||||
const segments = split.path.split(PATH_SEP);
|
||||
let asset = containerAssetOf(project, instance);
|
||||
let template: SceneObject | undefined;
|
||||
for (const segment of split.path.split(PATH_SEP)) {
|
||||
// Each container on the way holds edits for what lies below it, keyed relative to itself.
|
||||
const between: { holder: SceneObject; key: string }[] = [];
|
||||
for (const [index, segment] of segments.entries()) {
|
||||
const next = template ? containerAssetOf(project, template) : asset;
|
||||
if (!next) return undefined;
|
||||
asset = next;
|
||||
template = next.objects.find((t) => t.id === segment);
|
||||
if (!template) return undefined;
|
||||
if (index < segments.length - 1)
|
||||
between.push({ holder: template, key: segments.slice(index + 1).join(PATH_SEP) });
|
||||
}
|
||||
return asset && template ? { instance, path: split.path, asset, template } : undefined;
|
||||
if (!asset || !template) return undefined;
|
||||
const leaf = template;
|
||||
return {
|
||||
instance,
|
||||
path: split.path,
|
||||
asset,
|
||||
template: leaf,
|
||||
get base() {
|
||||
let edit: InstanceEdit | undefined;
|
||||
// Outermost wins, as in `expand`: it sits closest to the instance being edited.
|
||||
for (let i = between.length - 1; i >= 0; i--)
|
||||
edit = mergeEdits(edit, between[i]!.holder.instanceEdits[between[i]!.key]);
|
||||
return resolveChild(leaf, edit, leaf.id, leaf.parentId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// --- writing -----------------------------------------------------------------------------------
|
||||
@@ -267,13 +295,13 @@ function instanceRef(target: InstanceTarget, object: SceneObject): ObjectRef {
|
||||
target,
|
||||
setName: (name) =>
|
||||
write((edit) => {
|
||||
if (name === target.template.name) delete edit.name;
|
||||
if (name === target.base.name) delete edit.name;
|
||||
else edit.name = name;
|
||||
}),
|
||||
setPosition: (position) =>
|
||||
write((edit) => {
|
||||
for (const axis of AXES) {
|
||||
if (position[axis] === target.template.position[axis]) delete edit.position[axis];
|
||||
if (position[axis] === target.base.position[axis]) delete edit.position[axis];
|
||||
else edit.position[axis] = position[axis];
|
||||
}
|
||||
}),
|
||||
@@ -284,7 +312,7 @@ function instanceRef(target: InstanceTarget, object: SceneObject): ObjectRef {
|
||||
}),
|
||||
setOverride: (key, value) =>
|
||||
write((edit) => {
|
||||
if (value === target.template.overrides[key]) delete edit.overrides[key];
|
||||
if (value === target.base.overrides[key]) delete edit.overrides[key];
|
||||
else setBagValue(edit.overrides, key, value);
|
||||
}),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user