Let structs and lists hold objects across behavior runs (#238)
An object could only live in a dedicated object variable, kept in slots
beside the variable bag. Struct fields and list items could not hold
one, so a spawned object packed into a struct or list was lost. This
replaces the slots with one mechanism for all three places.
- "No object" has one resting form everywhere: {"$object": ""}. A
running game can tell an object slot from an empty text that way.
- During play the object handle lives directly in the value, also
nested in structs and lists. objectSlots.ts and the special paths for
Keep and Properties are gone.
- liveObjects.ts strips objects to (null) at every exit: overrides the
editor run writes back to the scene, saved values of persistent
Variables assets, live values in the inspector, and the world
snapshot of the test hooks (objects as names there).
- A destroyed object reads as (null) through isAlive, alone, in a
struct field or as a list item.
- "+ Field" and "+ Item" offer Object. The inspector and the list tab
show a fixed (null) row without input.
- Lists from Raycast all stay objects when written to a variable.
Before they turned into names.
- A struct that only changes by an object writes no override. A list
that grows by an object writes [(null)], its length really changed.
Found on the way: CI at 98211a8 failed only in e2e 21d. #237 added
asset to the field kinds without updating that fixed expectation. 21d
now expects ...,animation,asset,object,struct.
No project on the server has object variables, so no old data is
affected.
Tests:
- ./check.sh: 169 files, 2475 tests, CHECK EXIT 0.
- e2e 6, 21, 23, 32: ALL PASSED, 213 checks.
- object-variable.test.ts rewritten: variable, struct field and list
item over several frames, destroyed objects, nothing in scene, file
or save, editor run reload, Keep.
- New e2e 21d-ii adds an Object item by click. 21d and 23k updated.
- 7 counter-probes, each fails 1 to 9 tests. Originals back via cmp.
- Not tested on its own: stripping of inspector live values and the
names in the test snapshot, the "+ Field -> Object" menu by click.
- e2e check total 1407, only calculated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98211a8d54
commit
7979b8ff19
24 files changed
+510
-193
No files matched your search
+11
-4
@@ -1,7 +1,8 @@
|
||||
import { ENTITY_HANDLE, isHandleEnabled, type PointerState, worldMatrixOf } from '../core/graph';
|
||||
import { newId } from '../core/ids';
|
||||
import type { Asset, TilemapAsset } from '../core/model/Asset';
|
||||
import type { PropertyBag, Vec2 } from '../core/model/Properties';
|
||||
import { withoutObjects } from '../core/graph/liveObjects';
|
||||
import { type PropertyBag, sameValue, type Vec2 } from '../core/model/Properties';
|
||||
import { isPlaceableType } from '../core/model/AssetTypes';
|
||||
import { bakeInstances, materializeInstance, objectRef, resolveObjects } from '../core/model/instance';
|
||||
import { treeOrder } from '../core/model/hierarchy';
|
||||
@@ -245,7 +246,9 @@ export class World {
|
||||
entity.z = object.position.z;
|
||||
const resolved = resolveSceneObjectProps(this.project, asset, object);
|
||||
for (const key of Object.keys(entity.props)) if (!(key in resolved)) delete entity.props[key];
|
||||
Object.assign(entity.props, resolved);
|
||||
// The scene only ever got the objects' empty places, so a value that differs by them alone stays.
|
||||
for (const [key, value] of Object.entries(resolved))
|
||||
if (!sameValue(value, withoutObjects(entity.props[key]))) entity.props[key] = value;
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -305,14 +308,18 @@ export class World {
|
||||
const p = object.position;
|
||||
const moved = p.x !== entity.x || p.y !== entity.y || p.z !== entity.z;
|
||||
const base = resolveSceneObjectProps(this.project, asset, object);
|
||||
const drifted = Object.keys(entity.props).filter((k) => k in base && base[k] !== entity.props[k]);
|
||||
// What a run holds of an object stays in the run; the scene gets the empty one.
|
||||
const drifted = Object.keys(entity.props).filter(
|
||||
(k) =>
|
||||
k in base && base[k] !== entity.props[k] && !sameValue(base[k], withoutObjects(entity.props[k])),
|
||||
);
|
||||
if (!moved && drifted.length === 0) continue;
|
||||
// Only now, because a ref for resolved content costs a resolve of its own – and because a
|
||||
// direct write to such an object would land in the freshly built copy and be lost.
|
||||
const ref = objectRef(this.project, scene, object.id);
|
||||
if (!ref) continue;
|
||||
if (moved) ref.setPosition({ x: entity.x, y: entity.y, z: entity.z });
|
||||
for (const key of drifted) ref.setOverride(key, entity.props[key]);
|
||||
for (const key of drifted) ref.setOverride(key, withoutObjects(entity.props[key]));
|
||||
values = true;
|
||||
}
|
||||
return { structure: spawned || scene.objects.length !== before, values };
|
||||
|
||||
Reference in new issue
Block a user