diff --git a/distr/flecs.c b/distr/flecs.c index 847efeee5d..f247bb21ec 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -50478,6 +50478,8 @@ typedef struct ecs_script_type_visitor_t { ecs_script_entity_t *reopened; bool for_static; bool for_entity; + bool parent_missing; /* v->parent is not the world entity of the current + * entity scope (it does not exist (yet)) */ } ecs_script_type_visitor_t; int flecs_script_type_scope( @@ -101230,6 +101232,43 @@ static bool flecs_script_type_entity_name_ref( return true; } +/* Set v->parent to the world entity of an entity scope before typing it, like + * evaluation does, so that names are looked up from the same scope chain as + * during evaluation. This resolves entities that exist in the world, but are + * not symbols of this script (such as entities created by another script). + * + * If the entity does not exist yet, v->parent is kept: evaluation creates it + * without children, so a recursive lookup from it is the same as a lookup from + * its parent. Its descendants do not exist either, so lookups for nested + * scopes stay at the last existing parent. */ +static void flecs_script_type_push_parent( + ecs_script_type_visitor_t *t, + ecs_script_entity_t *node) +{ + if (t->parent_missing) { + return; + } + + ecs_world_t *world = t->v->world; + ecs_entity_t e = flecs_script_type_node_entity(t, node); + if (e && !ecs_is_alive(world, e)) { + e = 0; + } + + if (!e && node->name && !node->name_expr && + ecs_os_strcmp(node->name, "_")) + { + e = ecs_lookup_path_w_sep( + world, t->v->parent, node->name, ".", NULL, false); + } + + if (e) { + t->v->parent = e; + } else { + t->parent_missing = true; + } +} + static int flecs_script_type_entity( ecs_script_type_visitor_t *t, ecs_script_entity_t *node, @@ -101350,8 +101389,13 @@ static int flecs_script_type_entity( int32_t prev_unresolved = ecs_vec_count( &t->v->base.script->unresolved_refs); + ecs_entity_t old_parent = t->v->parent; + bool old_parent_missing = t->parent_missing; + flecs_script_type_push_parent(t, node); int result = flecs_script_type_scope( t, node->scope, child_table, true, true); + t->v->parent = old_parent; + t->parent_missing = old_parent_missing; t->v->entity = old_entity; t->v->is_with_scope = old_with; t->for_static = old_for_static; diff --git a/src/addons/script/eval/visit_type.c b/src/addons/script/eval/visit_type.c index 3eee3749d3..23fe02a96c 100644 --- a/src/addons/script/eval/visit_type.c +++ b/src/addons/script/eval/visit_type.c @@ -1964,6 +1964,43 @@ static bool flecs_script_type_entity_name_ref( return true; } +/* Set v->parent to the world entity of an entity scope before typing it, like + * evaluation does, so that names are looked up from the same scope chain as + * during evaluation. This resolves entities that exist in the world, but are + * not symbols of this script (such as entities created by another script). + * + * If the entity does not exist yet, v->parent is kept: evaluation creates it + * without children, so a recursive lookup from it is the same as a lookup from + * its parent. Its descendants do not exist either, so lookups for nested + * scopes stay at the last existing parent. */ +static void flecs_script_type_push_parent( + ecs_script_type_visitor_t *t, + ecs_script_entity_t *node) +{ + if (t->parent_missing) { + return; + } + + ecs_world_t *world = t->v->world; + ecs_entity_t e = flecs_script_type_node_entity(t, node); + if (e && !ecs_is_alive(world, e)) { + e = 0; + } + + if (!e && node->name && !node->name_expr && + ecs_os_strcmp(node->name, "_")) + { + e = ecs_lookup_path_w_sep( + world, t->v->parent, node->name, ".", NULL, false); + } + + if (e) { + t->v->parent = e; + } else { + t->parent_missing = true; + } +} + static int flecs_script_type_entity( ecs_script_type_visitor_t *t, ecs_script_entity_t *node, @@ -2084,8 +2121,13 @@ static int flecs_script_type_entity( int32_t prev_unresolved = ecs_vec_count( &t->v->base.script->unresolved_refs); + ecs_entity_t old_parent = t->v->parent; + bool old_parent_missing = t->parent_missing; + flecs_script_type_push_parent(t, node); int result = flecs_script_type_scope( t, node->scope, child_table, true, true); + t->v->parent = old_parent; + t->parent_missing = old_parent_missing; t->v->entity = old_entity; t->v->is_with_scope = old_with; t->for_static = old_for_static; diff --git a/src/addons/script/eval/visit_type.h b/src/addons/script/eval/visit_type.h index b93af1176e..9f49a767f0 100644 --- a/src/addons/script/eval/visit_type.h +++ b/src/addons/script/eval/visit_type.h @@ -45,6 +45,8 @@ typedef struct ecs_script_type_visitor_t { ecs_script_entity_t *reopened; bool for_static; bool for_entity; + bool parent_missing; /* v->parent is not the world entity of the current + * entity scope (it does not exist (yet)) */ } ecs_script_type_visitor_t; int flecs_script_type_scope( diff --git a/test/script/project.json b/test/script/project.json index 0238112faa..9abbe81b15 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -834,7 +834,11 @@ "named_child_in_for_loop_warns", "interpolated_name_in_for_loop_no_warn", "child_of_interpolated_name_in_for_loop_no_warn", - "named_entity_w_tag_in_for_loop_no_warn" + "named_entity_w_tag_in_for_loop_no_warn", + "scoped_prefab_inherit_from_prev_script", + "scoped_entity_ref_from_prev_script", + "scoped_component_from_prev_script", + "nested_scope_component_from_prev_script" ] }, { "id": "Collection", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index f135bd8408..419900ab77 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -9983,8 +9983,6 @@ void Eval_const_w_component_expr_in_module(void) { } void Eval_const_w_component_in_scope_expr_in_scope(void) { - ecs_log_set_level(-4); - ecs_world_t *world = ecs_init(); ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); @@ -10009,13 +10007,15 @@ void Eval_const_w_component_in_scope_expr_in_scope(void) { LINE " }" LINE "}"; - ecs_script_eval_result_t result = {0}; - test_assert(ecs_script_run_w_desc(world, NULL, expr, &ir_desc, &result) != 0); - test_assert(result.error != NULL); - test_assert(strstr(result.error, "unresolved reference 'Position'") != NULL); - ecs_os_free(result.error); + test_assert(ecs_script_run_w_desc(world, NULL, expr, &ir_desc, NULL) == 0); - test_assert(ecs_lookup(world, "parent.foo") == 0); + ecs_entity_t foo = ecs_lookup(world, "parent.foo"); + test_assert(foo != 0); + + const Position *p = ecs_get(world, foo, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); ecs_fini(world); } @@ -10059,8 +10059,6 @@ void Eval_const_w_component_in_scope_expr_in_module(void) { } void Eval_const_w_component_and_entity_in_scope_expr_in_scope(void) { - ecs_log_set_level(-4); - ecs_world_t *world = ecs_init(); ecs_entity_t parent = ecs_entity(world, { .name = "parent" }); @@ -10085,13 +10083,15 @@ void Eval_const_w_component_and_entity_in_scope_expr_in_scope(void) { LINE " }" LINE "}"; - ecs_script_eval_result_t result = {0}; - test_assert(ecs_script_run_w_desc(world, NULL, expr, &ir_desc, &result) != 0); - test_assert(result.error != NULL); - test_assert(strstr(result.error, "unresolved reference") != NULL); - ecs_os_free(result.error); + test_assert(ecs_script_run_w_desc(world, NULL, expr, &ir_desc, NULL) == 0); - test_assert(ecs_lookup(world, "parent.foo") == 0); + ecs_entity_t foo = ecs_lookup(world, "parent.foo"); + test_assert(foo != 0); + + const Position *p = ecs_get(world, foo, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); ecs_fini(world); } @@ -26335,3 +26335,129 @@ void Eval_named_entity_w_tag_in_for_loop_no_warn(void) { ecs_os_set_api_defaults(); ecs_log_set_level(-1); } + +void Eval_scoped_prefab_inherit_from_prev_script(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + + const char *pieces = + HEAD "structure {" + LINE " prefabs {" + LINE " Prefab steel { Foo }" + LINE " }" + LINE "}"; + + const char *kinds = + HEAD "structure {" + LINE " prefabs {" + LINE " Prefab steel_a992 : steel {}" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run_w_desc(world, "pieces", pieces, &ir_desc, NULL) == 0); + test_assert(ecs_script_run_w_desc(world, "kinds", kinds, &ir_desc, NULL) == 0); + + ecs_entity_t steel = ecs_lookup(world, "structure.prefabs.steel"); + ecs_entity_t a992 = ecs_lookup(world, "structure.prefabs.steel_a992"); + test_assert(steel != 0); + test_assert(a992 != 0); + test_assert(ecs_has_pair(world, a992, EcsIsA, steel)); + test_assert(ecs_has_id(world, a992, EcsPrefab)); + test_assert(ecs_has(world, a992, Foo)); + + ecs_fini(world); +} + +void Eval_scoped_entity_ref_from_prev_script(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, MadeOf); + + const char *pieces = + HEAD "structure {" + LINE " materials {" + LINE " steel {}" + LINE " }" + LINE "}"; + + const char *kinds = + HEAD "structure {" + LINE " materials {" + LINE " beam { (MadeOf, steel) }" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run_w_desc(world, "pieces", pieces, &ir_desc, NULL) == 0); + test_assert(ecs_script_run_w_desc(world, "kinds", kinds, &ir_desc, NULL) == 0); + + ecs_entity_t steel = ecs_lookup(world, "structure.materials.steel"); + ecs_entity_t beam = ecs_lookup(world, "structure.materials.beam"); + test_assert(steel != 0); + test_assert(beam != 0); + test_assert(ecs_has_pair(world, beam, MadeOf, steel)); + + ecs_fini(world); +} + +void Eval_scoped_component_from_prev_script(void) { + ecs_world_t *world = ecs_init(); + + const char *pieces = + HEAD "structure {" + LINE " components {" + LINE " struct Profile(width: f32, depth: f32)" + LINE " }" + LINE "}"; + + const char *kinds = + HEAD "structure {" + LINE " components {" + LINE " mullion { Profile: {width: 65, depth: 200} }" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run_w_desc(world, "pieces", pieces, &ir_desc, NULL) == 0); + test_assert(ecs_script_run_w_desc(world, "kinds", kinds, &ir_desc, NULL) == 0); + + ecs_entity_t profile = ecs_lookup(world, "structure.components.Profile"); + ecs_entity_t mullion = ecs_lookup(world, "structure.components.mullion"); + test_assert(profile != 0); + test_assert(mullion != 0); + const float *p = ecs_get_id(world, mullion, profile); + test_assert(p != NULL); + test_int(p[0], 65); + test_int(p[1], 200); + + ecs_fini(world); +} + +void Eval_nested_scope_component_from_prev_script(void) { + ecs_world_t *world = ecs_init(); + + const char *pieces = + HEAD "structure {" + LINE " struct Profile(width: f32, depth: f32)" + LINE "}"; + + const char *kinds = + HEAD "structure {" + LINE " prefabs {" + LINE " Prefab mullion { Profile: {width: 65, depth: 200} }" + LINE " }" + LINE "}"; + + test_assert(ecs_script_run_w_desc(world, "pieces", pieces, &ir_desc, NULL) == 0); + test_assert(ecs_script_run_w_desc(world, "kinds", kinds, &ir_desc, NULL) == 0); + + ecs_entity_t profile = ecs_lookup(world, "structure.Profile"); + ecs_entity_t mullion = ecs_lookup(world, "structure.prefabs.mullion"); + test_assert(profile != 0); + test_assert(mullion != 0); + const float *p = ecs_get_id(world, mullion, profile); + test_assert(p != NULL); + test_int(p[0], 65); + test_int(p[1], 200); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 5461fec631..45790e3e2e 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -828,6 +828,10 @@ void Eval_named_child_in_for_loop_warns(void); void Eval_interpolated_name_in_for_loop_no_warn(void); void Eval_child_of_interpolated_name_in_for_loop_no_warn(void); void Eval_named_entity_w_tag_in_for_loop_no_warn(void); +void Eval_scoped_prefab_inherit_from_prev_script(void); +void Eval_scoped_entity_ref_from_prev_script(void); +void Eval_scoped_component_from_prev_script(void); +void Eval_nested_scope_component_from_prev_script(void); // Testsuite 'Collection' void Collection_setup(void); @@ -7476,6 +7480,22 @@ bake_test_case Eval_testcases[] = { { "named_entity_w_tag_in_for_loop_no_warn", Eval_named_entity_w_tag_in_for_loop_no_warn + }, + { + "scoped_prefab_inherit_from_prev_script", + Eval_scoped_prefab_inherit_from_prev_script + }, + { + "scoped_entity_ref_from_prev_script", + Eval_scoped_entity_ref_from_prev_script + }, + { + "scoped_component_from_prev_script", + Eval_scoped_component_from_prev_script + }, + { + "nested_scope_component_from_prev_script", + Eval_nested_scope_component_from_prev_script } }; @@ -20841,7 +20861,7 @@ static bake_test_suite suites[] = { "Eval", Eval_setup, NULL, - 818, + 822, Eval_testcases, 1, Eval_params