Describe the bug
Since ddb83fe (#2239, "Throw ALREADY_IN_USE error when changing components of an
instantiated prefab", v4.1.6), adding a component to a prefab that has at least one
instance aborts with ECS_ALREADY_IN_USE.
This makes live prefab editing impossible, which I think affects any editor built on
flecs rather than just mine. An editor almost always keeps a live instance of the prefab
the user is editing — a placement preview that follows the cursor, or a stand-in in a
preview/thumbnail viewport. So "the prefab has been instantiated" is the normal state
while editing, not an edge case, and "add a component to this prefab" is the core
editing operation. In my editor, creating a brand new archetype and adding any component
to it aborts, because the preview instance already exists.
Two things make it hard to work around:
-
The check counts components the user never named. flecs_prefab_diff_has_override
(src/addons/prefab/tree_spawner.c:378) treats a component as an auto-override when it
has neither EcsIdOnInstantiateInherit nor EcsIdOnInstantiateDontInherit, i.e. the
default. Because the whole table diff is inspected, adding a component that is
explicitly (OnInstantiate, Inherit) — and therefore exempt — still aborts if it has an
EcsWith companion left on the default. That is the case in the repro below.
-
The obvious remedy is not available. The components being dragged in are derived
runtime state (a world transform, a bounding volume) that every instance must own its
own copy of, which is exactly why they are left on the default. Marking them
DontInherit means instances no longer receive them at all
(src/addons/prefab/instantiate.c:103 and :180); marking them Inherit means instances
see the prefab's copy but do not own one, so nothing can write per-instance values.
Either choice breaks the reason the component exists.
The assert is FLECS_DEBUG-only (src/addons/prefab/prefab.h:26-37), so release builds are
unaffected. The underlying behaviour is well defined — existing instances simply do not
receive the new component — and my application already propagates the change to instances
by hand, which is the strategy the assert now forbids.
To Reproduce
#include <flecs.h>
typedef struct { float x, y; } Position;
typedef struct { float x, y; } WorldPosition;
int main(void) {
ecs_world_t *world = ecs_init();
ECS_COMPONENT(world, Position);
ECS_COMPONENT(world, WorldPosition);
/* Position is authored, so instances inherit it: exempt from the new check. */
ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit);
/* WorldPosition is derived every frame, so every instance must own its own copy.
* It is therefore left on the default OnInstantiate, and it is added automatically
* wherever Position is added. */
ecs_add_pair(world, ecs_id(Position), EcsWith, ecs_id(WorldPosition));
ecs_entity_t prefab = ecs_entity(world, {
.name = "Prefab", .add = ecs_ids(EcsPrefab) });
/* An editor stands a live instance of the prefab so the user can see it. */
ecs_entity_t preview = ecs_new_w_pair(world, EcsIsA, prefab);
(void)preview;
/* The user adds a component to the prefab in the inspector. */
ecs_add(world, prefab, Position); /* ECS_ALREADY_IN_USE */
ecs_fini(world);
}
Adding a plain default-trait component directly (ecs_add(world, prefab, WorldPosition))
aborts the same way, without the EcsWith indirection.
Expected behavior
Adding a component to an instantiated prefab succeeds, leaving it to the application to
propagate the change to existing instances if it wants to (which is what an editor is
already doing).
Failing that, any of these would unblock the use case:
- an opt-out, so an application that manages propagation itself can disable the check
(a world flag, or a tag on the prefab);
- not counting components pulled in by EcsWith, so that explicitly tagging a component
(OnInstantiate, Inherit) is actually sufficient to make an add legal;
- narrowing the assert to the cases that genuinely corrupt state, rather than every
component left on the default trait.
Describe the bug
Since ddb83fe (#2239, "Throw ALREADY_IN_USE error when changing components of an
instantiated prefab", v4.1.6), adding a component to a prefab that has at least one
instance aborts with ECS_ALREADY_IN_USE.
This makes live prefab editing impossible, which I think affects any editor built on
flecs rather than just mine. An editor almost always keeps a live instance of the prefab
the user is editing — a placement preview that follows the cursor, or a stand-in in a
preview/thumbnail viewport. So "the prefab has been instantiated" is the normal state
while editing, not an edge case, and "add a component to this prefab" is the core
editing operation. In my editor, creating a brand new archetype and adding any component
to it aborts, because the preview instance already exists.
Two things make it hard to work around:
The check counts components the user never named. flecs_prefab_diff_has_override
(src/addons/prefab/tree_spawner.c:378) treats a component as an auto-override when it
has neither EcsIdOnInstantiateInherit nor EcsIdOnInstantiateDontInherit, i.e. the
default. Because the whole table diff is inspected, adding a component that is
explicitly (OnInstantiate, Inherit) — and therefore exempt — still aborts if it has an
EcsWith companion left on the default. That is the case in the repro below.
The obvious remedy is not available. The components being dragged in are derived
runtime state (a world transform, a bounding volume) that every instance must own its
own copy of, which is exactly why they are left on the default. Marking them
DontInherit means instances no longer receive them at all
(src/addons/prefab/instantiate.c:103 and :180); marking them Inherit means instances
see the prefab's copy but do not own one, so nothing can write per-instance values.
Either choice breaks the reason the component exists.
The assert is FLECS_DEBUG-only (src/addons/prefab/prefab.h:26-37), so release builds are
unaffected. The underlying behaviour is well defined — existing instances simply do not
receive the new component — and my application already propagates the change to instances
by hand, which is the strategy the assert now forbids.
To Reproduce
Adding a plain default-trait component directly (ecs_add(world, prefab, WorldPosition))
aborts the same way, without the EcsWith indirection.
Expected behavior
Adding a component to an instantiated prefab succeeds, leaving it to the application to
propagate the change to existing instances if it wants to (which is what an editor is
already doing).
Failing that, any of these would unblock the use case:
(a world flag, or a tag on the prefab);
(OnInstantiate, Inherit) is actually sufficient to make an add legal;
component left on the default trait.