Skip to content

Commit

Permalink
Implement deterministic ids for prefab children (#1335)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #1335

This upstreams a change that allows for deterministic generation of prefab instance child ids across networked applications.

Reviewed By: Jason-M-Fugate

Differential Revision: D62050990
  • Loading branch information
SanderMertens authored and facebook-github-bot committed Sep 4, 2024
1 parent f396325 commit a75f19b
Show file tree
Hide file tree
Showing 6 changed files with 538 additions and 17 deletions.
64 changes: 56 additions & 8 deletions distr/flecs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3047,12 +3047,18 @@ int32_t flecs_relation_depth(
ecs_entity_t r,
const ecs_table_t *table);

typedef struct ecs_instantiate_ctx_t {
ecs_entity_t root_prefab;
ecs_entity_t root_instance;
} ecs_instantiate_ctx_t;

void flecs_instantiate(
ecs_world_t *world,
ecs_entity_t base,
ecs_table_t *table,
int32_t row,
int32_t count);
int32_t count,
const ecs_instantiate_ctx_t *ctx);

void* flecs_get_base_component(
const ecs_world_t *world,
Expand Down Expand Up @@ -5269,7 +5275,8 @@ void flecs_instantiate_children(
ecs_table_t *table,
int32_t row,
int32_t count,
ecs_table_t *child_table)
ecs_table_t *child_table,
const ecs_instantiate_ctx_t *ctx)
{
if (!ecs_table_count(child_table)) {
return;
Expand Down Expand Up @@ -5366,6 +5373,7 @@ void flecs_instantiate_children(
/* Instantiate the prefab child table for each new instance */
const ecs_entity_t *instances = ecs_table_entities(table);
int32_t child_count = ecs_table_count(child_table);
ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count);

for (i = row; i < count + row; i ++) {
ecs_entity_t instance = instances[i];
Expand Down Expand Up @@ -5398,9 +5406,46 @@ void flecs_instantiate_children(
ecs_check(true, ECS_INVALID_OPERATION, NULL);
#endif

/* Attempt to reserve ids for children that have the same offset from
* the instance as from the base prefab. This ensures stable ids for
* instance children, even across networked applications. */
ecs_instantiate_ctx_t ctx_cur = {base, instance};
if (ctx) {
ctx_cur = *ctx;
}

for (j = 0; j < child_count; j ++) {
if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) {
/* Child id is smaller than root prefab id, can't use offset */
child_ids[j] = ecs_new(world);
continue;
}

/* Get prefab offset, ignore lifecycle generation count */
ecs_entity_t prefab_offset =
(uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab;
ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL);

/* First check if any entity with the desired id exists */
ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset;
ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child);
if (alive_id && flecs_entities_is_alive(world, alive_id)) {
/* Alive entity with requested id exists, can't use offset id */
child_ids[j] = ecs_new(world);
continue;
}

/* Id is not in use. Make it alive & match the generation of the instance. */
instance_child = ctx_cur.root_instance + prefab_offset;
flecs_entities_make_alive(world, instance_child);
flecs_entities_ensure(world, instance_child);
ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL);
child_ids[j] = instance_child;
}

/* Create children */
int32_t child_row;
const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL,
const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids,
&diff.added, child_count, component_data, false, &child_row, &diff);

/* If children are slots, add slot relationships to parent */
Expand All @@ -5416,9 +5461,11 @@ void flecs_instantiate_children(
/* If prefab child table has children itself, recursively instantiate */
for (j = 0; j < child_count; j ++) {
ecs_entity_t child = children[j];
flecs_instantiate(world, child, i_table, child_row + j, 1);
flecs_instantiate(world, child, i_table, child_row + j, 1, &ctx_cur);
}
}
}

flecs_wfree_n(world, ecs_entity_t, child_count, child_ids);
error:
return;
}
Expand All @@ -5428,7 +5475,8 @@ void flecs_instantiate(
ecs_entity_t base,
ecs_table_t *table,
int32_t row,
int32_t count)
int32_t count,
const ecs_instantiate_ctx_t *ctx)
{
ecs_record_t *record = flecs_entities_get_any(world, base);
ecs_table_t *base_table = record->table;
Expand All @@ -5444,7 +5492,7 @@ void flecs_instantiate(
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
flecs_instantiate_children(
world, base, table, row, count, tr->hdr.table);
world, base, table, row, count, tr->hdr.table, ctx);
}
ecs_os_perf_trace_pop("flecs.instantiate");
}
Expand Down Expand Up @@ -13656,7 +13704,7 @@ void flecs_emit(
* from being called recursively, in case prefab
* children also have IsA relationships. */
world->stages[0]->base = tgt;
flecs_instantiate(world, tgt, table, offset, count);
flecs_instantiate(world, tgt, table, offset, count, NULL);
world->stages[0]->base = 0;
}

Expand Down
54 changes: 48 additions & 6 deletions src/entity.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ void flecs_instantiate_children(
ecs_table_t *table,
int32_t row,
int32_t count,
ecs_table_t *child_table)
ecs_table_t *child_table,
const ecs_instantiate_ctx_t *ctx)
{
if (!ecs_table_count(child_table)) {
return;
Expand Down Expand Up @@ -403,6 +404,7 @@ void flecs_instantiate_children(
/* Instantiate the prefab child table for each new instance */
const ecs_entity_t *instances = ecs_table_entities(table);
int32_t child_count = ecs_table_count(child_table);
ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count);

for (i = row; i < count + row; i ++) {
ecs_entity_t instance = instances[i];
Expand Down Expand Up @@ -435,9 +437,46 @@ void flecs_instantiate_children(
ecs_check(true, ECS_INVALID_OPERATION, NULL);
#endif

/* Attempt to reserve ids for children that have the same offset from
* the instance as from the base prefab. This ensures stable ids for
* instance children, even across networked applications. */
ecs_instantiate_ctx_t ctx_cur = {base, instance};
if (ctx) {
ctx_cur = *ctx;
}

for (j = 0; j < child_count; j ++) {
if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) {
/* Child id is smaller than root prefab id, can't use offset */
child_ids[j] = ecs_new(world);
continue;
}

/* Get prefab offset, ignore lifecycle generation count */
ecs_entity_t prefab_offset =
(uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab;
ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL);

/* First check if any entity with the desired id exists */
ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset;
ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child);
if (alive_id && flecs_entities_is_alive(world, alive_id)) {
/* Alive entity with requested id exists, can't use offset id */
child_ids[j] = ecs_new(world);
continue;
}

/* Id is not in use. Make it alive & match the generation of the instance. */
instance_child = ctx_cur.root_instance + prefab_offset;
flecs_entities_make_alive(world, instance_child);
flecs_entities_ensure(world, instance_child);
ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL);
child_ids[j] = instance_child;
}

/* Create children */
int32_t child_row;
const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL,
const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids,
&diff.added, child_count, component_data, false, &child_row, &diff);

/* If children are slots, add slot relationships to parent */
Expand All @@ -453,9 +492,11 @@ void flecs_instantiate_children(
/* If prefab child table has children itself, recursively instantiate */
for (j = 0; j < child_count; j ++) {
ecs_entity_t child = children[j];
flecs_instantiate(world, child, i_table, child_row + j, 1);
flecs_instantiate(world, child, i_table, child_row + j, 1, &ctx_cur);
}
}
}

flecs_wfree_n(world, ecs_entity_t, child_count, child_ids);
error:
return;
}
Expand All @@ -465,7 +506,8 @@ void flecs_instantiate(
ecs_entity_t base,
ecs_table_t *table,
int32_t row,
int32_t count)
int32_t count,
const ecs_instantiate_ctx_t *ctx)
{
ecs_record_t *record = flecs_entities_get_any(world, base);
ecs_table_t *base_table = record->table;
Expand All @@ -481,7 +523,7 @@ void flecs_instantiate(
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
flecs_instantiate_children(
world, base, table, row, count, tr->hdr.table);
world, base, table, row, count, tr->hdr.table, ctx);
}
ecs_os_perf_trace_pop("flecs.instantiate");
}
Expand Down
2 changes: 1 addition & 1 deletion src/observable.c
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,7 @@ void flecs_emit(
* from being called recursively, in case prefab
* children also have IsA relationships. */
world->stages[0]->base = tgt;
flecs_instantiate(world, tgt, table, offset, count);
flecs_instantiate(world, tgt, table, offset, count, NULL);
world->stages[0]->base = 0;
}

Expand Down
8 changes: 7 additions & 1 deletion src/private_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,18 @@ int32_t flecs_relation_depth(
ecs_entity_t r,
const ecs_table_t *table);

typedef struct ecs_instantiate_ctx_t {
ecs_entity_t root_prefab;
ecs_entity_t root_instance;
} ecs_instantiate_ctx_t;

void flecs_instantiate(
ecs_world_t *world,
ecs_entity_t base,
ecs_table_t *table,
int32_t row,
int32_t count);
int32_t count,
const ecs_instantiate_ctx_t *ctx);

void* flecs_get_base_component(
const ecs_world_t *world,
Expand Down
19 changes: 18 additions & 1 deletion test/core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -1857,7 +1857,24 @@
"prefab_w_children_w_isa_auto_override",
"prefab_child_w_override",
"prefab_child_w_override_and_higher_component",
"prefab_child_w_override_and_lower_component"
"prefab_child_w_override_and_lower_component",
"prefab_1_child_offset_id",
"prefab_2_children_offset_id",
"prefab_3_children_offset_id",
"prefab_2_children_2_types_offset_id",
"prefab_3_children_3_types_offset_id",
"prefab_2_children_2_types_reverse_offset_id",
"prefab_3_children_3_types_reverse_offset_id",
"prefab_2_lvl_nested_children_offset_id",
"prefab_3_lvl_nested_children_offset_id",
"prefab_recycled_children_offset_id",
"prefab_recycled_instance_offset_id",
"prefab_children_recycled_offset_id",
"prefab_recycled_children_recycled_offset_id",
"prefab_recycled_children_recycled_offset_id_different_generation",
"prefab_1_child_offset_id_occupied",
"prefab_1_child_offset_id_recycled_occupied",
"prefab_child_offset_w_smaller_child_id"
]
}, {
"id": "World",
Expand Down
Loading

0 comments on commit a75f19b

Please sign in to comment.