From dbab33765d98221bc1ed10523aaeeb6cbe103333 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 23 Dec 2024 11:39:46 -0800 Subject: [PATCH 01/36] Reduce name lookups in script evaluation by reusing already resolved values --- distr/flecs.c | 29 ++++++++++++++++++++++++----- src/addons/script/visit_eval.c | 29 ++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d799c41b0..a02f6dda5 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -61576,6 +61576,11 @@ int flecs_script_eval_id( { ecs_entity_t second_from = 0; + if (id->eval) { + /* Already resolved */ + return 0; + } + if (!id->first) { flecs_script_eval_error(v, node, "invalid component/tag identifier"); @@ -62334,8 +62339,15 @@ int flecs_script_eval_const( return -1; } - if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + ecs_entity_t type = 0; + const ecs_type_info_t *ti = NULL; + if (node->expr) { + type = node->expr->type; + ti = node->expr->type_info; + } + + if (!type && node->type) { + type = flecs_script_find_entity(v, 0, node->type); if (!type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", @@ -62343,15 +62355,21 @@ int flecs_script_eval_const( return -1; } - const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); + ti = flecs_script_get_type_info(v, node, type); if (!ti) { flecs_script_eval_error(v, node, "failed to retrieve type info for '%s' for const variable '%s'", node->type, node->name); return -1; } + } - var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); + if (type && ti) { + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; @@ -62380,7 +62398,8 @@ int flecs_script_eval_const( const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 249ae1590..2b5afa043 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -247,6 +247,11 @@ int flecs_script_eval_id( { ecs_entity_t second_from = 0; + if (id->eval) { + /* Already resolved */ + return 0; + } + if (!id->first) { flecs_script_eval_error(v, node, "invalid component/tag identifier"); @@ -1005,8 +1010,15 @@ int flecs_script_eval_const( return -1; } - if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + ecs_entity_t type = 0; + const ecs_type_info_t *ti = NULL; + if (node->expr) { + type = node->expr->type; + ti = node->expr->type_info; + } + + if (!type && node->type) { + type = flecs_script_find_entity(v, 0, node->type); if (!type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", @@ -1014,15 +1026,21 @@ int flecs_script_eval_const( return -1; } - const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); + ti = flecs_script_get_type_info(v, node, type); if (!ti) { flecs_script_eval_error(v, node, "failed to retrieve type info for '%s' for const variable '%s'", node->type, node->name); return -1; } + } + + if (type && ti) { + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; @@ -1051,7 +1069,8 @@ int flecs_script_eval_const( const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - var->value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; From fee983c4e6281ca645fcaadfd9a09a47661b3d13 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 23 Dec 2024 16:06:59 -0800 Subject: [PATCH 02/36] Speed up casts between number types in script expressions --- distr/flecs.c | 192 +++++++++++++++++++++++--- src/addons/script/expr/ast.c | 11 +- src/addons/script/expr/ast.h | 3 +- src/addons/script/expr/expr.h | 2 +- src/addons/script/expr/util.c | 14 +- src/addons/script/expr/visit_eval.c | 156 ++++++++++++++++++++- src/addons/script/expr/visit_fold.c | 1 + src/addons/script/expr/visit_free.c | 1 + src/addons/script/expr/visit_to_str.c | 1 + src/addons/script/expr/visit_type.c | 1 + src/addons/script/visit_eval.c | 2 +- 11 files changed, 352 insertions(+), 32 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index a02f6dda5..d9d7cb4a8 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4993,7 +4993,8 @@ typedef enum ecs_expr_node_kind_t { EcsExprMember, EcsExprElement, EcsExprComponent, - EcsExprCast + EcsExprCast, + EcsExprCastNumber } ecs_expr_node_kind_t; struct ecs_expr_node_t { @@ -5205,7 +5206,7 @@ ecs_script_var_t flecs_expr_find_var( int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, - const ecs_value_t *src); + const ecs_expr_value_t *src); int flecs_value_move_to( ecs_world_t *world, @@ -62395,7 +62396,7 @@ int flecs_script_eval_const( } ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); + ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc( @@ -75800,11 +75801,6 @@ bool flecs_expr_explicit_cast_allowed( return true; } - // const EcsPrimitive *from_ptype = ecs_get(world, from, EcsPrimitive); - // const EcsPrimitive *to_ptype = ecs_get(world, to, EcsPrimitive); - // ecs_assert(from_ptype != NULL, ECS_INTERNAL_ERROR, NULL); - // ecs_assert(to_ptype != NULL, ECS_INTERNAL_ERROR, NULL); - return true; } @@ -75826,6 +75822,12 @@ ecs_expr_cast_t* flecs_expr_cast( ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; + if (flecs_expr_is_type_number(expr->type) && + flecs_expr_is_type_number(type)) + { + result->node.kind = EcsExprCastNumber; + } + result->node.pos = expr->pos; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); @@ -76628,18 +76630,20 @@ void flecs_expr_stack_pop( int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, - const ecs_value_t *src) + const ecs_expr_value_t *src) { ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.ptr != 0, ECS_INTERNAL_ERROR, NULL); - if (src->type == dst->type) { - ecs_value_copy(world, src->type, dst->ptr, src->ptr); + if (src->value.type == dst->type) { + ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy_w_type_info( + world, src->type_info, dst->ptr, src->value.ptr); } else { /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); - if (ecs_meta_set_value(&cur, src)) { + if (ecs_meta_set_value(&cur, &src->value)) { goto error; } } @@ -77338,7 +77342,7 @@ int flecs_expr_cast_visit_eval( } /* Copy expression result to storage of casted-to type */ - if (flecs_value_copy_to(ctx->world, &out->value, &expr->value)) { + if (flecs_value_copy_to(ctx->world, &out->value, expr)) { flecs_expr_visit_error(ctx->script, node, "failed to cast value"); goto error; } @@ -77350,6 +77354,151 @@ int flecs_expr_cast_visit_eval( return -1; } +static +bool flecs_expr_get_signed( + const ecs_value_t *value, + int64_t *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_i8_t)) { + *out = *(int8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i16_t)) { + *out = *(int16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i32_t)) { + *out = *(int32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i64_t)) { + *out = *(int64_t*)ptr; + return true; + } + + return false; +} + +static +bool flecs_expr_get_unsigned( + const ecs_value_t *value, + uint64_t *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_u8_t)) { + *out = *(uint8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u16_t)) { + *out = *(uint16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u32_t)) { + *out = *(uint32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u64_t)) { + *out = *(uint64_t*)ptr; + return true; + } + + return false; +} + +static +bool flecs_expr_get_float( + const ecs_value_t *value, + double *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_f32_t)) { + *out = (double)*(float*)ptr; + return true; + } else if (type == ecs_id(ecs_f64_t)) { + *out = *(double*)ptr; + return true; + } + + return false; +} + +#define FLECS_EXPR_NUMBER_CAST\ + if (type == ecs_id(ecs_i8_t)) *(ecs_i8_t*)ptr = (ecs_i8_t)value;\ + else if (type == ecs_id(ecs_i16_t)) *(ecs_i16_t*)ptr = (ecs_i16_t)value;\ + else if (type == ecs_id(ecs_i32_t)) *(ecs_i32_t*)ptr = (ecs_i32_t)value;\ + else if (type == ecs_id(ecs_i64_t)) *(ecs_i64_t*)ptr = (ecs_i64_t)value;\ + else if (type == ecs_id(ecs_iptr_t)) *(ecs_iptr_t*)ptr = (ecs_iptr_t)value;\ + else if (type == ecs_id(ecs_u8_t)) *(ecs_u8_t*)ptr = (ecs_u8_t)value;\ + else if (type == ecs_id(ecs_u16_t)) *(ecs_u16_t*)ptr = (ecs_u16_t)value;\ + else if (type == ecs_id(ecs_u32_t)) *(ecs_u32_t*)ptr = (ecs_u32_t)value;\ + else if (type == ecs_id(ecs_u64_t)) *(ecs_u64_t*)ptr = (ecs_u64_t)value;\ + else if (type == ecs_id(ecs_uptr_t)) *(ecs_uptr_t*)ptr = (ecs_uptr_t)value;\ + else if (type == ecs_id(ecs_f32_t)) *(ecs_f32_t*)ptr = (ecs_f32_t)value;\ + else if (type == ecs_id(ecs_f64_t)) *(ecs_f64_t*)ptr = (ecs_f64_t)value;\ + +static +void flecs_expr_set_signed( + const ecs_value_t *out, + int64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +void flecs_expr_set_unsigned( + const ecs_value_t *out, + uint64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +void flecs_expr_set_float( + const ecs_value_t *out, + double value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +int flecs_expr_cast_number_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_cast_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + + /* Evaluate expression to cast */ + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } + + int64_t signed_; + uint64_t unsigned_; + double float_; + if (flecs_expr_get_signed(&expr->value, &signed_)) { + flecs_expr_set_signed(&out->value, signed_); + } else if (flecs_expr_get_unsigned(&expr->value, &unsigned_)) { + flecs_expr_set_unsigned(&out->value, unsigned_); + } else if (flecs_expr_get_float(&expr->value, &float_)) { + flecs_expr_set_float(&out->value, float_); + } + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_function_args_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -77662,6 +77811,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprCastNumber: + if (flecs_expr_cast_number_visit_eval( + ctx, (ecs_expr_cast_t*)node, out)) + { + goto error; + } + break; } return 0; @@ -77720,7 +77876,7 @@ int flecs_expr_visit_eval( } } else { /* Values not owned by runtime should be copied */ - if (flecs_value_copy_to(ctx.world, out, &val->value)) { + if (flecs_value_copy_to(ctx.world, out, val)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } @@ -78313,6 +78469,7 @@ int flecs_expr_visit_fold( } break; case EcsExprCast: + case EcsExprCastNumber: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { goto error; } @@ -78502,6 +78659,7 @@ void flecs_expr_visit_free( flecs_free_t(a, ecs_expr_element_t, node); break; case EcsExprCast: + case EcsExprCastNumber: flecs_expr_cast_visit_free( script, (ecs_expr_cast_t*)node); flecs_free_t(a, ecs_expr_cast_t, node); @@ -78853,6 +79011,7 @@ int flecs_expr_node_to_str( } break; case EcsExprCast: + case EcsExprCastNumber: if (flecs_expr_cast_to_str(v, (const ecs_expr_cast_t*)node)) { @@ -80268,6 +80427,7 @@ int flecs_expr_visit_type_priv( } break; case EcsExprCast: + case EcsExprCastNumber: break; case EcsExprMethod: case EcsExprComponent: diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index a75a8164d..20a0d34f3 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -283,11 +283,6 @@ bool flecs_expr_explicit_cast_allowed( return true; } - // const EcsPrimitive *from_ptype = ecs_get(world, from, EcsPrimitive); - // const EcsPrimitive *to_ptype = ecs_get(world, to, EcsPrimitive); - // ecs_assert(from_ptype != NULL, ECS_INTERNAL_ERROR, NULL); - // ecs_assert(to_ptype != NULL, ECS_INTERNAL_ERROR, NULL); - return true; } @@ -309,6 +304,12 @@ ecs_expr_cast_t* flecs_expr_cast( ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; + if (flecs_expr_is_type_number(expr->type) && + flecs_expr_is_type_number(type)) + { + result->node.kind = EcsExprCastNumber; + } + result->node.pos = expr->pos; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 90ab0ad5c..fb4fa5d5d 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -23,7 +23,8 @@ typedef enum ecs_expr_node_kind_t { EcsExprMember, EcsExprElement, EcsExprComponent, - EcsExprCast + EcsExprCast, + EcsExprCastNumber } ecs_expr_node_kind_t; struct ecs_expr_node_t { diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 9f33dd227..94f54b842 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -13,7 +13,7 @@ int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, - const ecs_value_t *src); + const ecs_expr_value_t *src); int flecs_value_move_to( ecs_world_t *world, diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 77d3d1453..8be1efc01 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -10,18 +10,20 @@ int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, - const ecs_value_t *src) + const ecs_expr_value_t *src) { ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.ptr != 0, ECS_INTERNAL_ERROR, NULL); - if (src->type == dst->type) { - ecs_value_copy(world, src->type, dst->ptr, src->ptr); + if (src->value.type == dst->type) { + ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy_w_type_info( + world, src->type_info, dst->ptr, src->value.ptr); } else { /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); - if (ecs_meta_set_value(&cur, src)) { + if (ecs_meta_set_value(&cur, &src->value)) { goto error; } } diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 820927191..07bfae70f 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -376,7 +376,7 @@ int flecs_expr_cast_visit_eval( } /* Copy expression result to storage of casted-to type */ - if (flecs_value_copy_to(ctx->world, &out->value, &expr->value)) { + if (flecs_value_copy_to(ctx->world, &out->value, expr)) { flecs_expr_visit_error(ctx->script, node, "failed to cast value"); goto error; } @@ -388,6 +388,151 @@ int flecs_expr_cast_visit_eval( return -1; } +static +bool flecs_expr_get_signed( + const ecs_value_t *value, + int64_t *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_i8_t)) { + *out = *(int8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i16_t)) { + *out = *(int16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i32_t)) { + *out = *(int32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i64_t)) { + *out = *(int64_t*)ptr; + return true; + } + + return false; +} + +static +bool flecs_expr_get_unsigned( + const ecs_value_t *value, + uint64_t *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_u8_t)) { + *out = *(uint8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u16_t)) { + *out = *(uint16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u32_t)) { + *out = *(uint32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u64_t)) { + *out = *(uint64_t*)ptr; + return true; + } + + return false; +} + +static +bool flecs_expr_get_float( + const ecs_value_t *value, + double *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_f32_t)) { + *out = (double)*(float*)ptr; + return true; + } else if (type == ecs_id(ecs_f64_t)) { + *out = *(double*)ptr; + return true; + } + + return false; +} + +#define FLECS_EXPR_NUMBER_CAST\ + if (type == ecs_id(ecs_i8_t)) *(ecs_i8_t*)ptr = (ecs_i8_t)value;\ + else if (type == ecs_id(ecs_i16_t)) *(ecs_i16_t*)ptr = (ecs_i16_t)value;\ + else if (type == ecs_id(ecs_i32_t)) *(ecs_i32_t*)ptr = (ecs_i32_t)value;\ + else if (type == ecs_id(ecs_i64_t)) *(ecs_i64_t*)ptr = (ecs_i64_t)value;\ + else if (type == ecs_id(ecs_iptr_t)) *(ecs_iptr_t*)ptr = (ecs_iptr_t)value;\ + else if (type == ecs_id(ecs_u8_t)) *(ecs_u8_t*)ptr = (ecs_u8_t)value;\ + else if (type == ecs_id(ecs_u16_t)) *(ecs_u16_t*)ptr = (ecs_u16_t)value;\ + else if (type == ecs_id(ecs_u32_t)) *(ecs_u32_t*)ptr = (ecs_u32_t)value;\ + else if (type == ecs_id(ecs_u64_t)) *(ecs_u64_t*)ptr = (ecs_u64_t)value;\ + else if (type == ecs_id(ecs_uptr_t)) *(ecs_uptr_t*)ptr = (ecs_uptr_t)value;\ + else if (type == ecs_id(ecs_f32_t)) *(ecs_f32_t*)ptr = (ecs_f32_t)value;\ + else if (type == ecs_id(ecs_f64_t)) *(ecs_f64_t*)ptr = (ecs_f64_t)value;\ + +static +void flecs_expr_set_signed( + const ecs_value_t *out, + int64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +void flecs_expr_set_unsigned( + const ecs_value_t *out, + uint64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +void flecs_expr_set_float( + const ecs_value_t *out, + double value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} + +static +int flecs_expr_cast_number_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_cast_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + + /* Evaluate expression to cast */ + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } + + int64_t signed_; + uint64_t unsigned_; + double float_; + if (flecs_expr_get_signed(&expr->value, &signed_)) { + flecs_expr_set_signed(&out->value, signed_); + } else if (flecs_expr_get_unsigned(&expr->value, &unsigned_)) { + flecs_expr_set_unsigned(&out->value, unsigned_); + } else if (flecs_expr_get_float(&expr->value, &float_)) { + flecs_expr_set_float(&out->value, float_); + } + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_function_args_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -700,6 +845,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprCastNumber: + if (flecs_expr_cast_number_visit_eval( + ctx, (ecs_expr_cast_t*)node, out)) + { + goto error; + } + break; } return 0; @@ -758,7 +910,7 @@ int flecs_expr_visit_eval( } } else { /* Values not owned by runtime should be copied */ - if (flecs_value_copy_to(ctx.world, out, &val->value)) { + if (flecs_value_copy_to(ctx.world, out, val)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 69aa5cf3e..a12273c66 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -572,6 +572,7 @@ int flecs_expr_visit_fold( } break; case EcsExprCast: + case EcsExprCastNumber: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { goto error; } diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 0d88ae938..59daf83b3 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -176,6 +176,7 @@ void flecs_expr_visit_free( flecs_free_t(a, ecs_expr_element_t, node); break; case EcsExprCast: + case EcsExprCastNumber: flecs_expr_cast_visit_free( script, (ecs_expr_cast_t*)node); flecs_free_t(a, ecs_expr_cast_t, node); diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index b2ffb0e30..97756c5a5 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -342,6 +342,7 @@ int flecs_expr_node_to_str( } break; case EcsExprCast: + case EcsExprCastNumber: if (flecs_expr_cast_to_str(v, (const ecs_expr_cast_t*)node)) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index dfed36307..c005787ce 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -1386,6 +1386,7 @@ int flecs_expr_visit_type_priv( } break; case EcsExprCast: + case EcsExprCastNumber: break; case EcsExprMethod: case EcsExprComponent: diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 2b5afa043..b1d2403c1 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1066,7 +1066,7 @@ int flecs_script_eval_const( } ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); + ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc( From 22283a29bb5b34c295e5ade8abfa2bdd910efbca Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 23 Dec 2024 19:50:27 -0800 Subject: [PATCH 03/36] Change script variable lookups to use frame offsets instead of name --- distr/flecs.c | 83 ++++++++++++++++++++++++++--- distr/flecs.h | 24 +++++++++ include/flecs/addons/script.h | 24 +++++++++ src/addons/script/expr/ast.c | 2 + src/addons/script/expr/ast.h | 1 + src/addons/script/expr/visit_eval.c | 15 +++++- src/addons/script/expr/visit_type.c | 1 + src/addons/script/template.c | 16 +++--- src/addons/script/vars.c | 46 ++++++++++++++++ src/addons/script/visit_eval.c | 2 + 10 files changed, 198 insertions(+), 16 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d9d7cb4a8..97639d78f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5043,6 +5043,7 @@ typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_value_t global_value; /* Only set for global variables */ + int32_t frame_offset; /* For fast variable lookups */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { @@ -59412,6 +59413,13 @@ void flecs_script_template_instantiate( ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ + vars->frame_offset = ecs_vec_count(&template->vars->vars); + + /* Populate $this variable with instance entity */ + ecs_entity_t instance = entities[i]; + ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + var->value.ptr = &instance; /* Populate properties from template members */ if (st) { @@ -59427,16 +59435,11 @@ void flecs_script_template_instantiate( } } - /* Populate $this variable with instance entity */ - ecs_entity_t instance = entities[i]; - ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); - var->value.type = ecs_id(ecs_entity_t); - var->value.ptr = &instance; - ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; + ecs_script_visit_scope(&v, scope); /* Pop variable scope */ @@ -59624,6 +59627,8 @@ int flecs_script_template_preprocess( v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + ecs_script_vars_declare(v->vars, "this"); + int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; @@ -60441,6 +60446,10 @@ ecs_script_vars_t* flecs_script_vars_push( result->parent = parent; if (parent) { result->world = parent->world; + result->frame_offset = + parent->frame_offset + ecs_vec_count(&parent->vars); + } else { + result->frame_offset = 0; } result->stack = stack; result->allocator = allocator; @@ -60530,6 +60539,7 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; + var->frame_offset = ecs_vec_count(&vars->vars) + vars->frame_offset - 1; var->is_const = false; flecs_name_index_ensure(&vars->var_index, @@ -60594,6 +60604,47 @@ ecs_script_var_t* ecs_script_vars_lookup( flecs_uto(int32_t, var_id - 1)); } +ecs_script_var_t* ecs_script_vars_from_frame_offset( + const ecs_script_vars_t *vars, + int32_t frame_offset) +{ + ecs_check(frame_offset >= 0, ECS_INVALID_PARAMETER, NULL); + + if (frame_offset < vars->frame_offset) { + ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_script_vars_from_frame_offset(vars->parent, frame_offset); + } + + frame_offset -= vars->frame_offset; + ecs_check(frame_offset < ecs_vec_count(&vars->vars), + ECS_INVALID_PARAMETER, NULL); + + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, frame_offset); +error: + return NULL; +} + +void ecs_script_vars_print( + const ecs_script_vars_t *vars) +{ + if (vars->parent) { + ecs_script_vars_print(vars->parent); + } + + int32_t i, count = ecs_vec_count(&vars->vars); + ecs_script_var_t *array = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *var = &array[i]; + if (!i) { + printf("FRAME "); + } else { + printf(" "); + } + + printf("%2d: %s\n", var->frame_offset, var->name); + } +} + /* Static names for iterator fields */ static const char* flecs_script_iter_field_names[] = { "0", "1", "2", "3", "4", "5", "6", "7", @@ -61673,6 +61724,7 @@ int flecs_script_eval_expr( ecs_value_t *value) { ecs_expr_node_t *expr = *expr_ptr; + ecs_assert(expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; @@ -62651,6 +62703,7 @@ void flecs_script_eval_visit_init( ecs_allocator_t *a = &v->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; + v->vars->frame_offset = ecs_vec_count(&desc->vars->vars); } /* Always include flecs.meta */ @@ -75567,6 +75620,7 @@ ecs_expr_variable_t* flecs_expr_variable_from( ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; + result->frame_offset = -1; result->node.kind = EcsExprVariable; result->node.pos = node ? node->pos : NULL; return result; @@ -75694,6 +75748,7 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; + result->frame_offset = -1; return result; } @@ -77290,8 +77345,19 @@ int flecs_expr_variable_visit_eval( ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); - const ecs_script_var_t *var = ecs_script_vars_lookup( - ctx->desc->vars, node->name); + const ecs_script_var_t *var; + if (node->frame_offset != -1) { + var = ecs_script_vars_from_frame_offset( + ctx->desc->vars, node->frame_offset); + ecs_assert(!ecs_os_strcmp(var->name, node->name), + ECS_INVALID_PARAMETER, + "variable '%s' is not at expected frame offset (got '%s')", + node->name, var->name); + } else { + var = ecs_script_vars_lookup( + ctx->desc->vars, node->name); + } + if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); @@ -79990,6 +80056,7 @@ int flecs_expr_variable_visit_type( desc->vars, node->name); if (var) { node->node.type = var->value.type; + node->frame_offset = var->frame_offset; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/distr/flecs.h b/distr/flecs.h index 9ae079906..a08f768aa 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14370,12 +14370,15 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; + int32_t frame_offset; bool is_const; } ecs_script_var_t; /** Script variable scope. */ typedef struct ecs_script_vars_t { struct ecs_script_vars_t *parent; + int32_t frame_offset; + ecs_hashmap_t var_index; ecs_vec_t vars; @@ -14766,6 +14769,27 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name); +/** Lookup a variable by frame offset. + * This operation provides a faster way to lookup variables that are always + * declared in the same order in a ecs_script_vars_t scope. + * + * The frame offset of a variable can be obtained from the ecs_script_var_t + * type. The provided frame offset must be valid for the provided variable + * stack. If the frame offset is not valid, this operation will panic. + * + * @param vars The variable scope. + * @param name The variable frame offset.asm + * @return The variable. + */ +FLECS_API +ecs_script_var_t* ecs_script_vars_from_frame_offset( + const ecs_script_vars_t *vars, + int32_t frame_offset); + +FLECS_API +void ecs_script_vars_print( + const ecs_script_vars_t *vars); + /** Convert iterator to vars * This operation converts an iterator to a variable array. This allows for * using iterator results in expressions. The operation only converts a diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 71ae11853..e80df06bb 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -56,12 +56,15 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; + int32_t frame_offset; bool is_const; } ecs_script_var_t; /** Script variable scope. */ typedef struct ecs_script_vars_t { struct ecs_script_vars_t *parent; + int32_t frame_offset; + ecs_hashmap_t var_index; ecs_vec_t vars; @@ -452,6 +455,27 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name); +/** Lookup a variable by frame offset. + * This operation provides a faster way to lookup variables that are always + * declared in the same order in a ecs_script_vars_t scope. + * + * The frame offset of a variable can be obtained from the ecs_script_var_t + * type. The provided frame offset must be valid for the provided variable + * stack. If the frame offset is not valid, this operation will panic. + * + * @param vars The variable scope. + * @param name The variable frame offset.asm + * @return The variable. + */ +FLECS_API +ecs_script_var_t* ecs_script_vars_from_frame_offset( + const ecs_script_vars_t *vars, + int32_t frame_offset); + +FLECS_API +void ecs_script_vars_print( + const ecs_script_vars_t *vars); + /** Convert iterator to vars * This operation converts an iterator to a variable array. This allows for * using iterator results in expressions. The operation only converts a diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 20a0d34f3..c765c8ee1 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -49,6 +49,7 @@ ecs_expr_variable_t* flecs_expr_variable_from( ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; + result->frame_offset = -1; result->node.kind = EcsExprVariable; result->node.pos = node ? node->pos : NULL; return result; @@ -176,6 +177,7 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; + result->frame_offset = -1; return result; } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index fb4fa5d5d..0f6ea32f2 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -73,6 +73,7 @@ typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_value_t global_value; /* Only set for global variables */ + int32_t frame_offset; /* For fast variable lookups */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 07bfae70f..1ce824112 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -324,8 +324,19 @@ int flecs_expr_variable_visit_eval( ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); - const ecs_script_var_t *var = ecs_script_vars_lookup( - ctx->desc->vars, node->name); + const ecs_script_var_t *var; + if (node->frame_offset != -1) { + var = ecs_script_vars_from_frame_offset( + ctx->desc->vars, node->frame_offset); + ecs_assert(!ecs_os_strcmp(var->name, node->name), + ECS_INVALID_PARAMETER, + "variable '%s' is not at expected frame offset (got '%s')", + node->name, var->name); + } else { + var = ecs_script_vars_lookup( + ctx->desc->vars, node->name); + } + if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index c005787ce..5b451b691 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -949,6 +949,7 @@ int flecs_expr_variable_visit_type( desc->vars, node->name); if (var) { node->node.type = var->value.type; + node->frame_offset = var->frame_offset; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 2b4964b08..55205974b 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -189,6 +189,13 @@ void flecs_script_template_instantiate( ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ + vars->frame_offset = ecs_vec_count(&template->vars->vars); + + /* Populate $this variable with instance entity */ + ecs_entity_t instance = entities[i]; + ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + var->value.ptr = &instance; /* Populate properties from template members */ if (st) { @@ -204,16 +211,11 @@ void flecs_script_template_instantiate( } } - /* Populate $this variable with instance entity */ - ecs_entity_t instance = entities[i]; - ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); - var->value.type = ecs_id(ecs_entity_t); - var->value.ptr = &instance; - ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; + ecs_script_visit_scope(&v, scope); /* Pop variable scope */ @@ -401,6 +403,8 @@ int flecs_script_template_preprocess( v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + ecs_script_vars_declare(v->vars, "this"); + int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; diff --git a/src/addons/script/vars.c b/src/addons/script/vars.c index 49ff6aebb..3d78840fe 100644 --- a/src/addons/script/vars.c +++ b/src/addons/script/vars.c @@ -37,6 +37,10 @@ ecs_script_vars_t* flecs_script_vars_push( result->parent = parent; if (parent) { result->world = parent->world; + result->frame_offset = + parent->frame_offset + ecs_vec_count(&parent->vars); + } else { + result->frame_offset = 0; } result->stack = stack; result->allocator = allocator; @@ -126,6 +130,7 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; + var->frame_offset = ecs_vec_count(&vars->vars) + vars->frame_offset - 1; var->is_const = false; flecs_name_index_ensure(&vars->var_index, @@ -190,6 +195,47 @@ ecs_script_var_t* ecs_script_vars_lookup( flecs_uto(int32_t, var_id - 1)); } +ecs_script_var_t* ecs_script_vars_from_frame_offset( + const ecs_script_vars_t *vars, + int32_t frame_offset) +{ + ecs_check(frame_offset >= 0, ECS_INVALID_PARAMETER, NULL); + + if (frame_offset < vars->frame_offset) { + ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_script_vars_from_frame_offset(vars->parent, frame_offset); + } + + frame_offset -= vars->frame_offset; + ecs_check(frame_offset < ecs_vec_count(&vars->vars), + ECS_INVALID_PARAMETER, NULL); + + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, frame_offset); +error: + return NULL; +} + +void ecs_script_vars_print( + const ecs_script_vars_t *vars) +{ + if (vars->parent) { + ecs_script_vars_print(vars->parent); + } + + int32_t i, count = ecs_vec_count(&vars->vars); + ecs_script_var_t *array = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *var = &array[i]; + if (!i) { + printf("FRAME "); + } else { + printf(" "); + } + + printf("%2d: %s\n", var->frame_offset, var->name); + } +} + /* Static names for iterator fields */ static const char* flecs_script_iter_field_names[] = { "0", "1", "2", "3", "4", "5", "6", "7", diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index b1d2403c1..c72eac11e 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -343,6 +343,7 @@ int flecs_script_eval_expr( ecs_value_t *value) { ecs_expr_node_t *expr = *expr_ptr; + ecs_assert(expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; @@ -1321,6 +1322,7 @@ void flecs_script_eval_visit_init( ecs_allocator_t *a = &v->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; + v->vars->frame_offset = ecs_vec_count(&desc->vars->vars); } /* Always include flecs.meta */ From 13fe47cae644d9237804e6b84c6472a06ecfa7bc Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 23 Dec 2024 23:30:18 -0800 Subject: [PATCH 04/36] Don't push template variables with name to improve performance --- distr/flecs.c | 300 +++++++++++++++++++-------- distr/flecs.h | 34 ++- include/flecs/addons/script.h | 34 ++- src/addons/script/ast.c | 3 + src/addons/script/ast.h | 6 + src/addons/script/expr/ast.c | 4 +- src/addons/script/expr/ast.h | 2 +- src/addons/script/expr/visit_eval.c | 8 +- src/addons/script/expr/visit_type.c | 2 +- src/addons/script/template.c | 25 ++- src/addons/script/vars.c | 57 +++-- src/addons/script/visit_check.c | 18 +- src/addons/script/visit_eval.c | 164 +++++++++++---- src/addons/script/visit_eval.h | 11 +- test/script/project.json | 11 + test/script/src/Template.c | 309 +++++++++++++++++++++++++++- test/script/src/main.c | 57 ++++- 17 files changed, 865 insertions(+), 180 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 97639d78f..d03765350 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4706,6 +4706,11 @@ typedef struct ecs_script_id_t { const char *second; ecs_id_t flag; ecs_id_t eval; + + /* If first or second refer to a variable, these are the cached variable + * stack pointers so we don't have to lookup variables by name. */ + int32_t first_sp; + int32_t second_sp; } ecs_script_id_t; typedef struct ecs_script_tag_t { @@ -4730,6 +4735,7 @@ typedef struct ecs_script_default_component_t { typedef struct ecs_script_var_component_t { ecs_script_node_t node; const char *name; + int32_t sp; } ecs_script_var_component_t; struct ecs_script_entity_t { @@ -5043,7 +5049,7 @@ typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_value_t global_value; /* Only set for global variables */ - int32_t frame_offset; /* For fast variable lookups */ + int32_t sp; /* For fast variable lookups */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { @@ -5466,10 +5472,17 @@ void flecs_script_eval_error_( #define flecs_script_eval_error(v, node, ...)\ flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) -ecs_entity_t flecs_script_find_entity( +int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, - const char *path); + const char *path, + int32_t *frame_offset, + ecs_entity_t *out); + +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *frame_offset); ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, @@ -55372,6 +55385,8 @@ void flecs_script_set_id( ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); id->first = first; id->second = second; + id->first_sp = -1; + id->second_sp = -1; } ecs_script_pair_scope_t* flecs_script_insert_pair_scope( @@ -55463,6 +55478,7 @@ ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_var_component_t *result = flecs_ast_new( parser, ecs_script_var_component_t, EcsAstVarComponent); result->name = var_name; + result->sp = -1; flecs_ast_append(parser, scope->stmts, ecs_script_var_component_t, result); @@ -59413,13 +59429,17 @@ void flecs_script_template_instantiate( ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ - vars->frame_offset = ecs_vec_count(&template->vars->vars); + vars->sp = ecs_vec_count(&template->vars->vars); + + /* Allocate enough space for variables */ + ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); /* Populate $this variable with instance entity */ ecs_entity_t instance = entities[i]; - ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); - var->value.type = ecs_id(ecs_entity_t); - var->value.ptr = &instance; + ecs_script_var_t *this_var = ecs_script_vars_declare( + vars, NULL /* $this */); + this_var->value.type = ecs_id(ecs_entity_t); + this_var->value.ptr = &instance; /* Populate properties from template members */ if (st) { @@ -59427,9 +59447,10 @@ void flecs_script_template_instantiate( for (m = 0; m < st->members.count; m ++) { const ecs_member_t *member = &members[m]; - /* Assign template property from template instance */ + /* Assign template property from template instance. Don't + * set name as variables will be resolved by frame offset. */ ecs_script_var_t *var = ecs_script_vars_declare( - vars, member->name); + vars, NULL /* member->name */); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } @@ -59531,8 +59552,8 @@ int flecs_script_template_eval_prop( } if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); - if (!type) { + ecs_entity_t type; + if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); @@ -59627,8 +59648,8 @@ int flecs_script_template_preprocess( v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); - ecs_script_vars_declare(v->vars, "this"); - + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); + var->value.type = ecs_id(ecs_entity_t); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; @@ -60446,10 +60467,10 @@ ecs_script_vars_t* flecs_script_vars_push( result->parent = parent; if (parent) { result->world = parent->world; - result->frame_offset = - parent->frame_offset + ecs_vec_count(&parent->vars); + result->sp = + parent->sp + ecs_vec_count(&parent->vars); } else { - result->frame_offset = 0; + result->sp = 0; } result->stack = stack; result->allocator = allocator; @@ -60525,11 +60546,13 @@ ecs_script_var_t* ecs_script_vars_declare( ecs_script_vars_t *vars, const char *name) { - if (!ecs_vec_count(&vars->vars)) { - flecs_name_index_init(&vars->var_index, vars->allocator); - } else { - if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { - goto error; + if (name) { + if (flecs_name_index_is_init(&vars->var_index)) { + if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { + goto error; + } + } else { + flecs_name_index_init(&vars->var_index, vars->allocator); } } @@ -60539,17 +60562,28 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; - var->frame_offset = ecs_vec_count(&vars->vars) + vars->frame_offset - 1; + var->sp = ecs_vec_count(&vars->vars) + vars->sp - 1; var->is_const = false; - flecs_name_index_ensure(&vars->var_index, - flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + if (name) { + flecs_name_index_ensure(&vars->var_index, + flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + } return var; error: return NULL; } +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count) +{ + ecs_assert(!ecs_vec_count(&vars->vars), ECS_INVALID_OPERATION, + "variable scope must be empty for resize operation"); + ecs_vec_set_size_t(vars->allocator, &vars->vars, ecs_script_var_t, count); +} + ecs_script_var_t* ecs_script_vars_define_id( ecs_script_vars_t *vars, const char *name, @@ -60590,7 +60624,9 @@ ecs_script_var_t* ecs_script_vars_lookup( uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { - var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); + if (flecs_name_index_is_init(&vars->var_index)) { + var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); + } } if (!var_id) { @@ -60604,22 +60640,22 @@ ecs_script_var_t* ecs_script_vars_lookup( flecs_uto(int32_t, var_id - 1)); } -ecs_script_var_t* ecs_script_vars_from_frame_offset( +ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, - int32_t frame_offset) + int32_t sp) { - ecs_check(frame_offset >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(sp >= 0, ECS_INVALID_PARAMETER, NULL); - if (frame_offset < vars->frame_offset) { + if (sp < vars->sp) { ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_script_vars_from_frame_offset(vars->parent, frame_offset); + return ecs_script_vars_from_sp(vars->parent, sp); } - frame_offset -= vars->frame_offset; - ecs_check(frame_offset < ecs_vec_count(&vars->vars), + sp -= vars->sp; + ecs_check(sp < ecs_vec_count(&vars->vars), ECS_INVALID_PARAMETER, NULL); - return ecs_vec_get_t(&vars->vars, ecs_script_var_t, frame_offset); + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, sp); error: return NULL; } @@ -60641,7 +60677,7 @@ void ecs_script_vars_print( printf(" "); } - printf("%2d: %s\n", var->frame_offset, var->name); + printf("%2d: %s\n", var->sp, var->name); } } @@ -61093,6 +61129,8 @@ int flecs_script_check_var_component( return -1; } + node->sp = var->sp; + return 0; } @@ -61113,7 +61151,7 @@ int flecs_script_check_default_component( static int flecs_script_check_with_var( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_var_component_t *node) { ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); if (!var) { @@ -61122,6 +61160,8 @@ int flecs_script_check_with_var( return -1; } + node->sp = var->sp; + return 0; } @@ -61199,11 +61239,17 @@ int flecs_script_check_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { - if (!flecs_script_find_entity(v, 0, node->id.first)) { + ecs_entity_t dummy; + + if (flecs_script_find_entity( + v, 0, node->id.first, &node->id.first_sp, &dummy)) + { return -1; } - if (!flecs_script_find_entity(v, 0, node->id.second)) { + if (flecs_script_find_entity( + v, 0, node->id.second, &node->id.second_sp, &dummy)) + { return -1; } @@ -61312,7 +61358,7 @@ int flecs_script_check_node( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_check_with_var( - v, (ecs_script_var_node_t*)node); + v, (ecs_script_var_component_t*)node); case EcsAstWithTag: return flecs_script_check_with_tag( v, (ecs_script_tag_t*)node); @@ -61510,19 +61556,46 @@ const ecs_type_info_t* flecs_script_get_type_info( return NULL; } -ecs_entity_t flecs_script_find_entity( +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *sp) +{ + ecs_assert(sp != NULL, ECS_INTERNAL_ERROR, NULL); + + if (sp[0] != -1) { + return ecs_script_vars_from_sp(vars, sp[0]); + } else { + ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); + if (var) { + sp[0] = var->sp; + } + return var; + } +} + +int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, - const char *path) + const char *path, + int32_t *sp, + ecs_entity_t *out) { if (!path) { - return 0; + goto error; } if (path[0] == '$') { - const ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, &path[1]); + if (!sp) { + flecs_script_eval_error(v, NULL, + "variable identifier '%s' not allowed here", path); + goto error; + } + + const ecs_script_var_t *var = flecs_script_find_var( + v->vars, &path[1], sp); if (!var) { - return 0; + goto error; } if (var->value.type != ecs_id(ecs_entity_t)) { @@ -61531,27 +61604,33 @@ ecs_entity_t flecs_script_find_entity( "variable '%s' must be of type entity, got '%s'", path, type_str); ecs_os_free(type_str); - return 0; + goto error; } if (var->value.ptr == NULL) { - flecs_script_eval_error(v, NULL, - "variable '%s' is not initialized", path); - return 0; + if (!v->template) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + goto error; + } else { + return 0; + } } ecs_entity_t result = *(ecs_entity_t*)var->value.ptr; if (!result) { flecs_script_eval_error(v, NULL, "variable '%s' contains invalid entity id (0)", path); - return 0; + goto error; } - return result; + *out = result; + + return 0; } if (from) { - return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); + *out = ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { int32_t i, using_count = ecs_vec_count(&v->r->using); if (using_count) { @@ -61560,14 +61639,19 @@ ecs_entity_t flecs_script_find_entity( ecs_entity_t e = ecs_lookup_path_w_sep( v->world, using[i], path, NULL, NULL, false); if (e) { - return e; + *out = e; + return 0; } } } - return ecs_lookup_path_w_sep( + *out = ecs_lookup_path_w_sep( v->world, v->parent, path, NULL, NULL, true); } + + return 0; +error: + return -1; } ecs_entity_t flecs_script_create_entity( @@ -61593,7 +61677,11 @@ ecs_entity_t flecs_script_find_entity_action( { (void)world; ecs_script_eval_visitor_t *v = ctx; - return flecs_script_find_entity(v, 0, path); + ecs_entity_t result; + if (!flecs_script_find_entity(v, 0, path, NULL, &result)) { + return result; + } + return 0; } static @@ -61642,15 +61730,31 @@ int flecs_script_eval_id( if (v->template) { /* Can't resolve variables while preprocessing template scope */ if (id->first[0] == '$') { - return 0; + if (flecs_script_find_var(v->vars, &id->first[1], &id->first_sp)) { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->first[1]); + return -1; + } } if (id->second && id->second[0] == '$') { - return 0; + if (flecs_script_find_var( + v->vars, &id->second[1], &id->second_sp)) + { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->second[1]); + return -1; + } } } - ecs_entity_t first = flecs_script_find_entity(v, 0, id->first); - if (!first) { + ecs_entity_t first = 0; + if (flecs_script_find_entity( + v, 0, id->first, &id->first_sp, &first) || !first) + { if (id->first[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->first); @@ -61666,9 +61770,11 @@ int flecs_script_eval_id( } if (id->second) { - ecs_entity_t second = flecs_script_find_entity( - v, second_from, id->second); - if (!second) { + ecs_entity_t second = 0; + if (flecs_script_find_entity( + v, second_from, id->second, &id->second_sp, &second) || + !second) + { if (id->second[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->second); @@ -62110,11 +62216,17 @@ int flecs_script_eval_var_component( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; + ecs_script_var_t *var; + + if (node->sp != -1) { + var = ecs_script_vars_from_sp(v->vars, node->sp); + } else { + var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } } if (v->is_with_scope) { @@ -62203,9 +62315,21 @@ int flecs_script_eval_default_component( static int flecs_script_eval_with_var( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_var_component_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + ecs_script_var_t *var; + + if (node->sp != -1) { + var = ecs_script_vars_from_sp(v->vars, node->sp); + } else { + var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + } + if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); @@ -62400,8 +62524,7 @@ int flecs_script_eval_const( } if (!type && node->type) { - type = flecs_script_find_entity(v, 0, node->type); - if (!type) { + if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); @@ -62479,15 +62602,30 @@ int flecs_script_eval_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { - ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); - if (!first) { + ecs_entity_t first; + if (flecs_script_find_entity( + v, 0, node->id.first, &node->id.first_sp, &first) || !first) + { first = flecs_script_create_entity(v, node->id.first); if (!first) { return -1; } } - ecs_entity_t second = flecs_script_create_entity(v, node->id.second); + ecs_entity_t second = 0; + if (node->id.second) { + if (node->id.second[0] == '$') { + if (flecs_script_find_entity( + v, 0, node->id.second, &node->id.second_sp, &second)) + { + return -1; + } + } else { + second = flecs_script_create_entity(v, node->id.second); + } + + } + if (!second) { return -1; } @@ -62637,7 +62775,7 @@ int flecs_script_eval_node( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_eval_with_var( - v, (ecs_script_var_node_t*)node); + v, (ecs_script_var_component_t*)node); case EcsAstWithTag: return flecs_script_eval_with_tag( v, (ecs_script_tag_t*)node); @@ -62703,7 +62841,7 @@ void flecs_script_eval_visit_init( ecs_allocator_t *a = &v->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; - v->vars->frame_offset = ecs_vec_count(&desc->vars->vars); + v->vars->sp = ecs_vec_count(&desc->vars->vars); } /* Always include flecs.meta */ @@ -75620,7 +75758,7 @@ ecs_expr_variable_t* flecs_expr_variable_from( ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; - result->frame_offset = -1; + result->sp = -1; result->node.kind = EcsExprVariable; result->node.pos = node ? node->pos : NULL; return result; @@ -75748,7 +75886,7 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; - result->frame_offset = -1; + result->sp = -1; return result; } @@ -77346,10 +77484,10 @@ int flecs_expr_variable_visit_eval( "variables available at parse time are not provided"); const ecs_script_var_t *var; - if (node->frame_offset != -1) { - var = ecs_script_vars_from_frame_offset( - ctx->desc->vars, node->frame_offset); - ecs_assert(!ecs_os_strcmp(var->name, node->name), + if (node->sp != -1) { + var = ecs_script_vars_from_sp( + ctx->desc->vars, node->sp); + ecs_assert(!var->name || !ecs_os_strcmp(var->name, node->name), ECS_INVALID_PARAMETER, "variable '%s' is not at expected frame offset (got '%s')", node->name, var->name); @@ -80056,7 +80194,7 @@ int flecs_expr_variable_visit_type( desc->vars, node->name); if (var) { node->node.type = var->value.type; - node->frame_offset = var->frame_offset; + node->sp = var->sp; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/distr/flecs.h b/distr/flecs.h index a08f768aa..fd4738773 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14370,14 +14370,14 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; - int32_t frame_offset; + int32_t sp; bool is_const; } ecs_script_var_t; /** Script variable scope. */ typedef struct ecs_script_vars_t { struct ecs_script_vars_t *parent; - int32_t frame_offset; + int32_t sp; ecs_hashmap_t var_index; ecs_vec_t vars; @@ -14769,27 +14769,45 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name); -/** Lookup a variable by frame offset. +/** Lookup a variable by stack pointer. * This operation provides a faster way to lookup variables that are always * declared in the same order in a ecs_script_vars_t scope. * - * The frame offset of a variable can be obtained from the ecs_script_var_t - * type. The provided frame offset must be valid for the provided variable + * The stack pointer of a variable can be obtained from the ecs_script_var_t + * type. The provided frame offset must be valid for the provided variable * stack. If the frame offset is not valid, this operation will panic. * * @param vars The variable scope. - * @param name The variable frame offset.asm + * @param sp The stack pointer to the variable. * @return The variable. */ FLECS_API -ecs_script_var_t* ecs_script_vars_from_frame_offset( +ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, - int32_t frame_offset); + int32_t sp); +/** Print variables. + * This operation prints all variables in the vars scope and parent scopes.asm + * + * @param vars The variable scope. + */ FLECS_API void ecs_script_vars_print( const ecs_script_vars_t *vars); +/** Preallocate space for variables. + * This operation preallocates space for the specified number of variables. This + * is a performance optimization only, and is not necessary before declaring + * variables in a scope. + * + * @param vars The variable scope. + * @param count The number of variables to preallocate space for. + */ +FLECS_API +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count); + /** Convert iterator to vars * This operation converts an iterator to a variable array. This allows for * using iterator results in expressions. The operation only converts a diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index e80df06bb..0c1c2e541 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -56,14 +56,14 @@ typedef struct ecs_script_var_t { const char *name; ecs_value_t value; const ecs_type_info_t *type_info; - int32_t frame_offset; + int32_t sp; bool is_const; } ecs_script_var_t; /** Script variable scope. */ typedef struct ecs_script_vars_t { struct ecs_script_vars_t *parent; - int32_t frame_offset; + int32_t sp; ecs_hashmap_t var_index; ecs_vec_t vars; @@ -455,27 +455,45 @@ ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name); -/** Lookup a variable by frame offset. +/** Lookup a variable by stack pointer. * This operation provides a faster way to lookup variables that are always * declared in the same order in a ecs_script_vars_t scope. * - * The frame offset of a variable can be obtained from the ecs_script_var_t - * type. The provided frame offset must be valid for the provided variable + * The stack pointer of a variable can be obtained from the ecs_script_var_t + * type. The provided frame offset must be valid for the provided variable * stack. If the frame offset is not valid, this operation will panic. * * @param vars The variable scope. - * @param name The variable frame offset.asm + * @param sp The stack pointer to the variable. * @return The variable. */ FLECS_API -ecs_script_var_t* ecs_script_vars_from_frame_offset( +ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, - int32_t frame_offset); + int32_t sp); +/** Print variables. + * This operation prints all variables in the vars scope and parent scopes.asm + * + * @param vars The variable scope. + */ FLECS_API void ecs_script_vars_print( const ecs_script_vars_t *vars); +/** Preallocate space for variables. + * This operation preallocates space for the specified number of variables. This + * is a performance optimization only, and is not necessary before declaring + * variables in a scope. + * + * @param vars The variable scope. + * @param count The number of variables to preallocate space for. + */ +FLECS_API +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count); + /** Convert iterator to vars * This operation converts an iterator to a variable array. This allows for * using iterator results in expressions. The operation only converts a diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index c0d76cbd6..5b51bb050 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -101,6 +101,8 @@ void flecs_script_set_id( ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); id->first = first; id->second = second; + id->first_sp = -1; + id->second_sp = -1; } ecs_script_pair_scope_t* flecs_script_insert_pair_scope( @@ -192,6 +194,7 @@ ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_var_component_t *result = flecs_ast_new( parser, ecs_script_var_component_t, EcsAstVarComponent); result->name = var_name; + result->sp = -1; flecs_ast_append(parser, scope->stmts, ecs_script_var_component_t, result); diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index c23f5c143..fe6527dc4 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -45,6 +45,11 @@ typedef struct ecs_script_id_t { const char *second; ecs_id_t flag; ecs_id_t eval; + + /* If first or second refer to a variable, these are the cached variable + * stack pointers so we don't have to lookup variables by name. */ + int32_t first_sp; + int32_t second_sp; } ecs_script_id_t; typedef struct ecs_script_tag_t { @@ -69,6 +74,7 @@ typedef struct ecs_script_default_component_t { typedef struct ecs_script_var_component_t { ecs_script_node_t node; const char *name; + int32_t sp; } ecs_script_var_component_t; struct ecs_script_entity_t { diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index c765c8ee1..10efdaf19 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -49,7 +49,7 @@ ecs_expr_variable_t* flecs_expr_variable_from( ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; - result->frame_offset = -1; + result->sp = -1; result->node.kind = EcsExprVariable; result->node.pos = node ? node->pos : NULL; return result; @@ -177,7 +177,7 @@ ecs_expr_variable_t* flecs_expr_variable( ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; - result->frame_offset = -1; + result->sp = -1; return result; } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 0f6ea32f2..1d599d43d 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -73,7 +73,7 @@ typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_value_t global_value; /* Only set for global variables */ - int32_t frame_offset; /* For fast variable lookups */ + int32_t sp; /* For fast variable lookups */ } ecs_expr_variable_t; typedef struct ecs_expr_unary_t { diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 1ce824112..3a12b3c37 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -325,10 +325,10 @@ int flecs_expr_variable_visit_eval( "variables available at parse time are not provided"); const ecs_script_var_t *var; - if (node->frame_offset != -1) { - var = ecs_script_vars_from_frame_offset( - ctx->desc->vars, node->frame_offset); - ecs_assert(!ecs_os_strcmp(var->name, node->name), + if (node->sp != -1) { + var = ecs_script_vars_from_sp( + ctx->desc->vars, node->sp); + ecs_assert(!var->name || !ecs_os_strcmp(var->name, node->name), ECS_INVALID_PARAMETER, "variable '%s' is not at expected frame offset (got '%s')", node->name, var->name); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 5b451b691..9547532fa 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -949,7 +949,7 @@ int flecs_expr_variable_visit_type( desc->vars, node->name); if (var) { node->node.type = var->value.type; - node->frame_offset = var->frame_offset; + node->sp = var->sp; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 55205974b..d81cd5322 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -189,13 +189,17 @@ void flecs_script_template_instantiate( ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ - vars->frame_offset = ecs_vec_count(&template->vars->vars); + vars->sp = ecs_vec_count(&template->vars->vars); + + /* Allocate enough space for variables */ + ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); /* Populate $this variable with instance entity */ ecs_entity_t instance = entities[i]; - ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); - var->value.type = ecs_id(ecs_entity_t); - var->value.ptr = &instance; + ecs_script_var_t *this_var = ecs_script_vars_declare( + vars, NULL /* $this */); + this_var->value.type = ecs_id(ecs_entity_t); + this_var->value.ptr = &instance; /* Populate properties from template members */ if (st) { @@ -203,9 +207,10 @@ void flecs_script_template_instantiate( for (m = 0; m < st->members.count; m ++) { const ecs_member_t *member = &members[m]; - /* Assign template property from template instance */ + /* Assign template property from template instance. Don't + * set name as variables will be resolved by frame offset. */ ecs_script_var_t *var = ecs_script_vars_declare( - vars, member->name); + vars, NULL /* member->name */); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } @@ -307,8 +312,8 @@ int flecs_script_template_eval_prop( } if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); - if (!type) { + ecs_entity_t type; + if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); @@ -403,8 +408,8 @@ int flecs_script_template_preprocess( v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); - ecs_script_vars_declare(v->vars, "this"); - + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); + var->value.type = ecs_id(ecs_entity_t); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; diff --git a/src/addons/script/vars.c b/src/addons/script/vars.c index 3d78840fe..c0a348bb9 100644 --- a/src/addons/script/vars.c +++ b/src/addons/script/vars.c @@ -37,10 +37,10 @@ ecs_script_vars_t* flecs_script_vars_push( result->parent = parent; if (parent) { result->world = parent->world; - result->frame_offset = - parent->frame_offset + ecs_vec_count(&parent->vars); + result->sp = + parent->sp + ecs_vec_count(&parent->vars); } else { - result->frame_offset = 0; + result->sp = 0; } result->stack = stack; result->allocator = allocator; @@ -116,11 +116,13 @@ ecs_script_var_t* ecs_script_vars_declare( ecs_script_vars_t *vars, const char *name) { - if (!ecs_vec_count(&vars->vars)) { - flecs_name_index_init(&vars->var_index, vars->allocator); - } else { - if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { - goto error; + if (name) { + if (flecs_name_index_is_init(&vars->var_index)) { + if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { + goto error; + } + } else { + flecs_name_index_init(&vars->var_index, vars->allocator); } } @@ -130,17 +132,28 @@ ecs_script_var_t* ecs_script_vars_declare( var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; - var->frame_offset = ecs_vec_count(&vars->vars) + vars->frame_offset - 1; + var->sp = ecs_vec_count(&vars->vars) + vars->sp - 1; var->is_const = false; - flecs_name_index_ensure(&vars->var_index, - flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + if (name) { + flecs_name_index_ensure(&vars->var_index, + flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + } return var; error: return NULL; } +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count) +{ + ecs_assert(!ecs_vec_count(&vars->vars), ECS_INVALID_OPERATION, + "variable scope must be empty for resize operation"); + ecs_vec_set_size_t(vars->allocator, &vars->vars, ecs_script_var_t, count); +} + ecs_script_var_t* ecs_script_vars_define_id( ecs_script_vars_t *vars, const char *name, @@ -181,7 +194,9 @@ ecs_script_var_t* ecs_script_vars_lookup( uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { - var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); + if (flecs_name_index_is_init(&vars->var_index)) { + var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); + } } if (!var_id) { @@ -195,22 +210,22 @@ ecs_script_var_t* ecs_script_vars_lookup( flecs_uto(int32_t, var_id - 1)); } -ecs_script_var_t* ecs_script_vars_from_frame_offset( +ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, - int32_t frame_offset) + int32_t sp) { - ecs_check(frame_offset >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(sp >= 0, ECS_INVALID_PARAMETER, NULL); - if (frame_offset < vars->frame_offset) { + if (sp < vars->sp) { ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_script_vars_from_frame_offset(vars->parent, frame_offset); + return ecs_script_vars_from_sp(vars->parent, sp); } - frame_offset -= vars->frame_offset; - ecs_check(frame_offset < ecs_vec_count(&vars->vars), + sp -= vars->sp; + ecs_check(sp < ecs_vec_count(&vars->vars), ECS_INVALID_PARAMETER, NULL); - return ecs_vec_get_t(&vars->vars, ecs_script_var_t, frame_offset); + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, sp); error: return NULL; } @@ -232,7 +247,7 @@ void ecs_script_vars_print( printf(" "); } - printf("%2d: %s\n", var->frame_offset, var->name); + printf("%2d: %s\n", var->sp, var->name); } } diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c index b7ec8bc6b..0a3f9740f 100644 --- a/src/addons/script/visit_check.c +++ b/src/addons/script/visit_check.c @@ -211,6 +211,8 @@ int flecs_script_check_var_component( return -1; } + node->sp = var->sp; + return 0; } @@ -231,7 +233,7 @@ int flecs_script_check_default_component( static int flecs_script_check_with_var( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_var_component_t *node) { ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); if (!var) { @@ -240,6 +242,8 @@ int flecs_script_check_with_var( return -1; } + node->sp = var->sp; + return 0; } @@ -317,11 +321,17 @@ int flecs_script_check_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { - if (!flecs_script_find_entity(v, 0, node->id.first)) { + ecs_entity_t dummy; + + if (flecs_script_find_entity( + v, 0, node->id.first, &node->id.first_sp, &dummy)) + { return -1; } - if (!flecs_script_find_entity(v, 0, node->id.second)) { + if (flecs_script_find_entity( + v, 0, node->id.second, &node->id.second_sp, &dummy)) + { return -1; } @@ -430,7 +440,7 @@ int flecs_script_check_node( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_check_with_var( - v, (ecs_script_var_node_t*)node); + v, (ecs_script_var_component_t*)node); case EcsAstWithTag: return flecs_script_check_with_tag( v, (ecs_script_tag_t*)node); diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index c72eac11e..dd2773ce7 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -129,19 +129,46 @@ const ecs_type_info_t* flecs_script_get_type_info( return NULL; } -ecs_entity_t flecs_script_find_entity( +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *sp) +{ + ecs_assert(sp != NULL, ECS_INTERNAL_ERROR, NULL); + + if (sp[0] != -1) { + return ecs_script_vars_from_sp(vars, sp[0]); + } else { + ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); + if (var) { + sp[0] = var->sp; + } + return var; + } +} + +int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, - const char *path) + const char *path, + int32_t *sp, + ecs_entity_t *out) { if (!path) { - return 0; + goto error; } if (path[0] == '$') { - const ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, &path[1]); + if (!sp) { + flecs_script_eval_error(v, NULL, + "variable identifier '%s' not allowed here", path); + goto error; + } + + const ecs_script_var_t *var = flecs_script_find_var( + v->vars, &path[1], sp); if (!var) { - return 0; + goto error; } if (var->value.type != ecs_id(ecs_entity_t)) { @@ -150,27 +177,33 @@ ecs_entity_t flecs_script_find_entity( "variable '%s' must be of type entity, got '%s'", path, type_str); ecs_os_free(type_str); - return 0; + goto error; } if (var->value.ptr == NULL) { - flecs_script_eval_error(v, NULL, - "variable '%s' is not initialized", path); - return 0; + if (!v->template) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + goto error; + } else { + return 0; + } } ecs_entity_t result = *(ecs_entity_t*)var->value.ptr; if (!result) { flecs_script_eval_error(v, NULL, "variable '%s' contains invalid entity id (0)", path); - return 0; + goto error; } - return result; + *out = result; + + return 0; } if (from) { - return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); + *out = ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { int32_t i, using_count = ecs_vec_count(&v->r->using); if (using_count) { @@ -179,14 +212,19 @@ ecs_entity_t flecs_script_find_entity( ecs_entity_t e = ecs_lookup_path_w_sep( v->world, using[i], path, NULL, NULL, false); if (e) { - return e; + *out = e; + return 0; } } } - return ecs_lookup_path_w_sep( + *out = ecs_lookup_path_w_sep( v->world, v->parent, path, NULL, NULL, true); } + + return 0; +error: + return -1; } ecs_entity_t flecs_script_create_entity( @@ -212,7 +250,11 @@ ecs_entity_t flecs_script_find_entity_action( { (void)world; ecs_script_eval_visitor_t *v = ctx; - return flecs_script_find_entity(v, 0, path); + ecs_entity_t result; + if (!flecs_script_find_entity(v, 0, path, NULL, &result)) { + return result; + } + return 0; } static @@ -261,15 +303,31 @@ int flecs_script_eval_id( if (v->template) { /* Can't resolve variables while preprocessing template scope */ if (id->first[0] == '$') { - return 0; + if (flecs_script_find_var(v->vars, &id->first[1], &id->first_sp)) { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->first[1]); + return -1; + } } if (id->second && id->second[0] == '$') { - return 0; + if (flecs_script_find_var( + v->vars, &id->second[1], &id->second_sp)) + { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->second[1]); + return -1; + } } } - ecs_entity_t first = flecs_script_find_entity(v, 0, id->first); - if (!first) { + ecs_entity_t first = 0; + if (flecs_script_find_entity( + v, 0, id->first, &id->first_sp, &first) || !first) + { if (id->first[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->first); @@ -285,9 +343,11 @@ int flecs_script_eval_id( } if (id->second) { - ecs_entity_t second = flecs_script_find_entity( - v, second_from, id->second); - if (!second) { + ecs_entity_t second = 0; + if (flecs_script_find_entity( + v, second_from, id->second, &id->second_sp, &second) || + !second) + { if (id->second[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->second); @@ -729,11 +789,17 @@ int flecs_script_eval_var_component( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; + ecs_script_var_t *var; + + if (node->sp != -1) { + var = ecs_script_vars_from_sp(v->vars, node->sp); + } else { + var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } } if (v->is_with_scope) { @@ -822,9 +888,21 @@ int flecs_script_eval_default_component( static int flecs_script_eval_with_var( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_var_component_t *node) { - ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + ecs_script_var_t *var; + + if (node->sp != -1) { + var = ecs_script_vars_from_sp(v->vars, node->sp); + } else { + var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + } + if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); @@ -1019,8 +1097,7 @@ int flecs_script_eval_const( } if (!type && node->type) { - type = flecs_script_find_entity(v, 0, node->type); - if (!type) { + if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); @@ -1098,15 +1175,30 @@ int flecs_script_eval_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { - ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); - if (!first) { + ecs_entity_t first; + if (flecs_script_find_entity( + v, 0, node->id.first, &node->id.first_sp, &first) || !first) + { first = flecs_script_create_entity(v, node->id.first); if (!first) { return -1; } } - ecs_entity_t second = flecs_script_create_entity(v, node->id.second); + ecs_entity_t second = 0; + if (node->id.second) { + if (node->id.second[0] == '$') { + if (flecs_script_find_entity( + v, 0, node->id.second, &node->id.second_sp, &second)) + { + return -1; + } + } else { + second = flecs_script_create_entity(v, node->id.second); + } + + } + if (!second) { return -1; } @@ -1256,7 +1348,7 @@ int flecs_script_eval_node( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_eval_with_var( - v, (ecs_script_var_node_t*)node); + v, (ecs_script_var_component_t*)node); case EcsAstWithTag: return flecs_script_eval_with_tag( v, (ecs_script_tag_t*)node); @@ -1322,7 +1414,7 @@ void flecs_script_eval_visit_init( ecs_allocator_t *a = &v->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; - v->vars->frame_offset = ecs_vec_count(&desc->vars->vars); + v->vars->sp = ecs_vec_count(&desc->vars->vars); } /* Always include flecs.meta */ diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index 6ec536f52..f578eb377 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -30,10 +30,17 @@ void flecs_script_eval_error_( #define flecs_script_eval_error(v, node, ...)\ flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) -ecs_entity_t flecs_script_find_entity( +int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, - const char *path); + const char *path, + int32_t *frame_offset, + ecs_entity_t *out); + +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *frame_offset); ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, diff --git a/test/script/project.json b/test/script/project.json index 42c20736d..51a8687dc 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -324,6 +324,15 @@ "module_w_template", "module_w_nested_template", "template_w_pair_w_this_var", + "template_w_pair_w_prop_var", + "template_w_pair_w_const_var", + "template_w_pair_scope_w_this_var", + "template_w_pair_scope_w_prop_var", + "template_w_pair_scope_w_const_var", + "template_w_pair_w_unresolved_var_first", + "template_w_pair_w_unresolved_var_second", + "template_w_pair_scope_w_unresolved_var_first", + "template_w_pair_scope_w_unresolved_var_second", "prop_without_using_meta", "hoist_var", "hoist_vars_nested", @@ -341,6 +350,8 @@ "template_w_prefab_and_instance", "template_w_with_var", "template_w_with_prop", + "template_w_child_w_var", + "template_w_child_w_prop", "fold_const", "bulk_create_template", "template_w_expr_w_self_ref", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 0196ff9f4..77f1aff8f 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -1649,7 +1649,6 @@ void Template_template_w_pair_w_this_var(void) { const char *expr = LINE "template Foo {\n" - LINE " prop x = flecs.meta.f32: 10\n" // dummy prop LINE " (Rel, $this)\n" LINE "}\n" LINE "ent { Foo: {} }\n" @@ -1666,6 +1665,226 @@ void Template_template_w_pair_w_this_var(void) { ecs_fini(world); } +void Template_template_w_pair_w_prop_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " prop x = flecs.meta.entity: flecs\n" + LINE " (Rel, $x)\n" + LINE "}\n" + LINE "ent { Foo: {flecs.core} }\n" + LINE "\n"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t ent = ecs_lookup(world, "ent"); + + test_assert(ecs_has_id(world, ent, foo)); + test_assert(ecs_has_pair(world, ent, Rel, EcsFlecsCore)); + + ecs_fini(world); +} + +void Template_template_w_pair_w_const_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " const x = flecs.meta.entity: flecs\n" + LINE " (Rel, $x)\n" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t ent = ecs_lookup(world, "ent"); + + test_assert(ecs_has_id(world, ent, foo)); + test_assert(ecs_has_pair(world, ent, Rel, EcsFlecs)); + + ecs_fini(world); +} + +void Template_template_w_pair_scope_w_this_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Rel, PairIsTag); + + const char *expr = + LINE "template Foo {\n" + LINE " (Rel, $this) {\n" + LINE " child {}" + LINE " }" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t ent = ecs_lookup(world, "ent"); + ecs_entity_t child = ecs_lookup(world, "ent.child"); + + test_assert(foo != 0); + test_assert(ent != 0); + test_assert(child != 0); + + test_assert(ecs_has_id(world, ent, foo)); + test_assert(ecs_has_pair(world, child, Rel, ent)); + + ecs_fini(world); +} + +void Template_template_w_pair_scope_w_prop_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " prop x = flecs.meta.entity: flecs\n" + LINE " (Rel, $x) {\n" + LINE " child {}" + LINE " }" + LINE "}\n" + LINE "ent { Foo: {flecs.core} }\n" + LINE "\n"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t ent = ecs_lookup(world, "ent"); + ecs_entity_t child = ecs_lookup(world, "ent.child"); + + test_assert(foo != 0); + test_assert(ent != 0); + test_assert(child != 0); + + test_assert(ecs_has_id(world, ent, foo)); + test_assert(ecs_has_pair(world, child, Rel, EcsFlecsCore)); + + ecs_fini(world); +} + +void Template_template_w_pair_scope_w_const_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " prop x = flecs.meta.entity: flecs\n" + LINE " (Rel, $x) {\n" + LINE " child {}" + LINE " }" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t ent = ecs_lookup(world, "ent"); + ecs_entity_t child = ecs_lookup(world, "ent.child"); + + test_assert(foo != 0); + test_assert(ent != 0); + test_assert(child != 0); + + test_assert(ecs_has_id(world, ent, foo)); + test_assert(ecs_has_pair(world, child, Rel, EcsFlecs)); + + ecs_fini(world); +} + +void Template_template_w_pair_w_unresolved_var_first(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tgt); + + const char *expr = + LINE "template Foo {\n" + LINE " ($x, Tgt)\n" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Template_template_w_pair_w_unresolved_var_second(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " (Rel, $x)\n" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Template_template_w_pair_scope_w_unresolved_var_first(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + + const char *expr = + LINE "template Foo {\n" + LINE " (Rel, $x) {\n" + LINE " child {}" + LINE " }" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Template_template_w_pair_scope_w_unresolved_var_second(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tgt); + + const char *expr = + LINE "template Foo {\n" + LINE " ($x, Tgt) {\n" + LINE " child {}" + LINE " }" + LINE "}\n" + LINE "ent { Foo: {} }\n" + LINE "\n"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + void Template_prop_without_using_meta(void) { ecs_world_t *world = ecs_init(); @@ -2312,6 +2531,94 @@ void Template_template_w_with_prop(void) { ecs_fini(world); } +void Template_template_w_child_w_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " const pos = Position: {10, 20}" + LINE " child {" + LINE " $pos" + LINE " }" + LINE "}" + LINE "" + LINE "e = Foo: {}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, foo)); + + ecs_entity_t child = ecs_lookup(world, "e.child"); + test_assert(child != 0); + + const Position *p = ecs_get(world, child, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Template_template_w_child_w_prop(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " prop pos = Position: {0, 0}" + LINE " child {" + LINE " $pos" + LINE " }" + LINE "}" + LINE "" + LINE "e = Foo: {{10, 20}}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, foo)); + + ecs_entity_t child = ecs_lookup(world, "e.child"); + test_assert(child != 0); + + const Position *p = ecs_get(world, child, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + void Template_fold_const(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 23a320941..7a0400714 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -318,6 +318,15 @@ void Template_template_with_with(void); void Template_module_w_template(void); void Template_module_w_nested_template(void); void Template_template_w_pair_w_this_var(void); +void Template_template_w_pair_w_prop_var(void); +void Template_template_w_pair_w_const_var(void); +void Template_template_w_pair_scope_w_this_var(void); +void Template_template_w_pair_scope_w_prop_var(void); +void Template_template_w_pair_scope_w_const_var(void); +void Template_template_w_pair_w_unresolved_var_first(void); +void Template_template_w_pair_w_unresolved_var_second(void); +void Template_template_w_pair_scope_w_unresolved_var_first(void); +void Template_template_w_pair_scope_w_unresolved_var_second(void); void Template_prop_without_using_meta(void); void Template_hoist_var(void); void Template_hoist_vars_nested(void); @@ -335,6 +344,8 @@ void Template_entity_w_2_template_instances(void); void Template_template_w_prefab_and_instance(void); void Template_template_w_with_var(void); void Template_template_w_with_prop(void); +void Template_template_w_child_w_var(void); +void Template_template_w_child_w_prop(void); void Template_fold_const(void); void Template_bulk_create_template(void); void Template_template_w_expr_w_self_ref(void); @@ -2084,6 +2095,42 @@ bake_test_case Template_testcases[] = { "template_w_pair_w_this_var", Template_template_w_pair_w_this_var }, + { + "template_w_pair_w_prop_var", + Template_template_w_pair_w_prop_var + }, + { + "template_w_pair_w_const_var", + Template_template_w_pair_w_const_var + }, + { + "template_w_pair_scope_w_this_var", + Template_template_w_pair_scope_w_this_var + }, + { + "template_w_pair_scope_w_prop_var", + Template_template_w_pair_scope_w_prop_var + }, + { + "template_w_pair_scope_w_const_var", + Template_template_w_pair_scope_w_const_var + }, + { + "template_w_pair_w_unresolved_var_first", + Template_template_w_pair_w_unresolved_var_first + }, + { + "template_w_pair_w_unresolved_var_second", + Template_template_w_pair_w_unresolved_var_second + }, + { + "template_w_pair_scope_w_unresolved_var_first", + Template_template_w_pair_scope_w_unresolved_var_first + }, + { + "template_w_pair_scope_w_unresolved_var_second", + Template_template_w_pair_scope_w_unresolved_var_second + }, { "prop_without_using_meta", Template_prop_without_using_meta @@ -2152,6 +2199,14 @@ bake_test_case Template_testcases[] = { "template_w_with_prop", Template_template_w_with_prop }, + { + "template_w_child_w_var", + Template_template_w_child_w_var + }, + { + "template_w_child_w_prop", + Template_template_w_child_w_prop + }, { "fold_const", Template_fold_const @@ -4196,7 +4251,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 50, + 61, Template_testcases }, { From 102abd54f65cd9a4365f2a74e17047501916699e Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 11:53:42 -0800 Subject: [PATCH 05/36] Don't declare const variables in template scope with a name --- distr/flecs.c | 30 +++++++++++------------------ src/addons/script/expr/visit_eval.c | 15 ++------------- src/addons/script/expr/visit_fold.c | 4 ++-- src/addons/script/expr/visit_type.c | 5 ++--- src/addons/script/visit_eval.c | 6 +++++- 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d03765350..db3fb3fdd 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -62509,7 +62509,11 @@ int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); + /* Declare variable. If this variable is declared while instantiating a + * template, the variable sp has already been resolved in all expressions + * that used it, so we don't need to create the variable with a name. */ + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, + v->template_entity ? NULL : node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); @@ -77483,19 +77487,8 @@ int flecs_expr_variable_visit_eval( ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); - const ecs_script_var_t *var; - if (node->sp != -1) { - var = ecs_script_vars_from_sp( - ctx->desc->vars, node->sp); - ecs_assert(!var->name || !ecs_os_strcmp(var->name, node->name), - ECS_INVALID_PARAMETER, - "variable '%s' is not at expected frame offset (got '%s')", - node->name, var->name); - } else { - var = ecs_script_vars_lookup( - ctx->desc->vars, node->name); - } - + const ecs_script_var_t *var = flecs_script_find_var( + ctx->desc->vars, node->name, &node->sp); if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); @@ -78484,8 +78477,8 @@ int flecs_expr_variable_visit_fold( ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; - ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); /* Should've been caught by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); @@ -80190,11 +80183,10 @@ int flecs_expr_variable_visit_type( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { - ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); if (var) { node->node.type = var->value.type; - node->sp = var->sp; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 3a12b3c37..dfa8c0da8 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -324,19 +324,8 @@ int flecs_expr_variable_visit_eval( ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); - const ecs_script_var_t *var; - if (node->sp != -1) { - var = ecs_script_vars_from_sp( - ctx->desc->vars, node->sp); - ecs_assert(!var->name || !ecs_os_strcmp(var->name, node->name), - ECS_INVALID_PARAMETER, - "variable '%s' is not at expected frame offset (got '%s')", - node->name, var->name); - } else { - var = ecs_script_vars_lookup( - ctx->desc->vars, node->name); - } - + const ecs_script_var_t *var = flecs_script_find_var( + ctx->desc->vars, node->name, &node->sp); if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index a12273c66..4569f4725 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -383,8 +383,8 @@ int flecs_expr_variable_visit_fold( ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; - ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); /* Should've been caught by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 9547532fa..5a9b88e50 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -945,11 +945,10 @@ int flecs_expr_variable_visit_type( ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { - ecs_script_var_t *var = ecs_script_vars_lookup( - desc->vars, node->name); + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); if (var) { node->node.type = var->value.type; - node->sp = var->sp; } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index dd2773ce7..20108da25 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1082,7 +1082,11 @@ int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); + /* Declare variable. If this variable is declared while instantiating a + * template, the variable sp has already been resolved in all expressions + * that used it, so we don't need to create the variable with a name. */ + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, + v->template_entity ? NULL : node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); From 33d417ee02f6914106edec385327cd681edfc602 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 13:17:52 -0800 Subject: [PATCH 06/36] Bulk add components in entity script scope --- distr/flecs.c | 83 +++++++++++++++++++++++++++++++-- src/addons/script/ast.c | 2 + src/addons/script/ast.h | 6 ++- src/addons/script/template.c | 4 +- src/addons/script/visit_check.c | 29 +++++++++++- src/addons/script/visit_eval.c | 9 ++++ src/addons/script/visit_eval.h | 4 ++ src/entity.c | 23 +++++++++ src/private_api.h | 6 +++ 9 files changed, 156 insertions(+), 10 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index db3fb3fdd..640165fa4 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -3134,6 +3134,12 @@ void flecs_invoke_hook( ecs_entity_t event, ecs_iter_action_t hook); +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count); + //////////////////////////////////////////////////////////////////////////////// //// Query API //////////////////////////////////////////////////////////////////////////////// @@ -4699,6 +4705,10 @@ struct ecs_script_scope_t { ecs_vec_t stmts; ecs_script_scope_t *parent; ecs_id_t default_component_eval; + + /* Array with component ids that are added in scope. Used to limit + * archetype moves. */ + ecs_vec_t components; /* vec */ }; typedef struct ecs_script_id_t { @@ -4747,7 +4757,7 @@ struct ecs_script_entity_t { ecs_script_scope_t *scope; ecs_expr_node_t *name_expr; - // Populated during eval + /* Populated during eval */ ecs_script_entity_t *parent; ecs_entity_t eval; ecs_entity_t eval_kind; @@ -5515,6 +5525,10 @@ int flecs_script_check_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + /* Functions shared between check and eval visitor */ int flecs_script_eval_scope( @@ -6855,6 +6869,29 @@ void flecs_remove_id( flecs_defer_end(world, stage); } +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count) +{ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + ecs_table_t *table = ecs_get_table(world, entity); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + table = flecs_find_table_add(world, table, id, &diff); + } + + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); +} + static flecs_component_ptr_t flecs_ensure( ecs_world_t *world, @@ -55320,6 +55357,7 @@ ecs_script_scope_t* flecs_script_scope_new( ecs_script_scope_t *result = flecs_ast_new( parser, ecs_script_scope_t, EcsAstScope); flecs_ast_vec(parser, result->stmts, ecs_script_node_t); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); return result; } @@ -55336,6 +55374,7 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_scope_t *result = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); return result; } @@ -59461,7 +59500,7 @@ void flecs_script_template_instantiate( /* Run template code */ v.vars = vars; - ecs_script_visit_scope(&v, scope); + flecs_script_eval_scope(&v, scope); /* Pop variable scope */ ecs_script_vars_pop(vars); @@ -59650,7 +59689,7 @@ int flecs_script_template_preprocess( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); var->value.type = ecs_id(ecs_entity_t); - int result = ecs_script_visit_scope(v, template->node->scope); + int result = flecs_script_check_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; v->template = NULL; @@ -60965,12 +61004,37 @@ int flecs_script_check_expr( return -1; } -static int flecs_script_check_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) { - return flecs_script_eval_scope(v, node); + int ret = flecs_script_eval_scope(v, node); + if (ret) { + return -1; + } + + /* Gather all resolved components in scope so we can add them in one bulk + * operation to entities. */ + ecs_allocator_t *a = &v->base.script->allocator; + int32_t i, count = ecs_vec_count(&node->stmts); + ecs_script_node_t **stmts = ecs_vec_first(&node->stmts); + for (i = 0; i < count; i ++) { + ecs_script_node_t *stmt = stmts[i]; + ecs_id_t id = 0; + if (stmt->kind == EcsAstComponent) { + ecs_script_component_t *cmp = (ecs_script_component_t*)stmt; + id = cmp->id.eval; + } else if (stmt->kind == EcsAstTag) { + ecs_script_tag_t *cmp = (ecs_script_tag_t*)stmt; + id = cmp->id.eval; + } + + if (id) { + ecs_vec_append_t(a, &node->components, ecs_id_t)[0] = id; + } + } + + return 0; } static @@ -61886,6 +61950,15 @@ int flecs_script_eval_scope( } } + if (v->entity) { + ecs_entity_t src = v->entity->eval; + int32_t count = ecs_vec_count(&node->components); + if (src != EcsSingleton && count) { + flecs_add_ids( + v->world, src, ecs_vec_first(&node->components), count); + } + } + int result = ecs_script_visit_scope(v, node); ecs_vec_set_count_t(a, &v->r->using, ecs_entity_t, prev_using_count); diff --git a/src/addons/script/ast.c b/src/addons/script/ast.c index 5b51bb050..03100a60c 100644 --- a/src/addons/script/ast.c +++ b/src/addons/script/ast.c @@ -36,6 +36,7 @@ ecs_script_scope_t* flecs_script_scope_new( ecs_script_scope_t *result = flecs_ast_new( parser, ecs_script_scope_t, EcsAstScope); flecs_ast_vec(parser, result->stmts, ecs_script_node_t); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); return result; } @@ -52,6 +53,7 @@ ecs_script_scope_t* flecs_script_insert_scope( ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_scope_t *result = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); return result; } diff --git a/src/addons/script/ast.h b/src/addons/script/ast.h index fe6527dc4..a9fb5832f 100644 --- a/src/addons/script/ast.h +++ b/src/addons/script/ast.h @@ -38,6 +38,10 @@ struct ecs_script_scope_t { ecs_vec_t stmts; ecs_script_scope_t *parent; ecs_id_t default_component_eval; + + /* Array with component ids that are added in scope. Used to limit + * archetype moves. */ + ecs_vec_t components; /* vec */ }; typedef struct ecs_script_id_t { @@ -86,7 +90,7 @@ struct ecs_script_entity_t { ecs_script_scope_t *scope; ecs_expr_node_t *name_expr; - // Populated during eval + /* Populated during eval */ ecs_script_entity_t *parent; ecs_entity_t eval; ecs_entity_t eval_kind; diff --git a/src/addons/script/template.c b/src/addons/script/template.c index d81cd5322..e6805af45 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -221,7 +221,7 @@ void flecs_script_template_instantiate( /* Run template code */ v.vars = vars; - ecs_script_visit_scope(&v, scope); + flecs_script_eval_scope(&v, scope); /* Pop variable scope */ ecs_script_vars_pop(vars); @@ -410,7 +410,7 @@ int flecs_script_template_preprocess( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); var->value.type = ecs_id(ecs_entity_t); - int result = ecs_script_visit_scope(v, template->node->scope); + int result = flecs_script_check_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; v->template = NULL; diff --git a/src/addons/script/visit_check.c b/src/addons/script/visit_check.c index 0a3f9740f..76184f42f 100644 --- a/src/addons/script/visit_check.c +++ b/src/addons/script/visit_check.c @@ -47,12 +47,37 @@ int flecs_script_check_expr( return -1; } -static int flecs_script_check_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) { - return flecs_script_eval_scope(v, node); + int ret = flecs_script_eval_scope(v, node); + if (ret) { + return -1; + } + + /* Gather all resolved components in scope so we can add them in one bulk + * operation to entities. */ + ecs_allocator_t *a = &v->base.script->allocator; + int32_t i, count = ecs_vec_count(&node->stmts); + ecs_script_node_t **stmts = ecs_vec_first(&node->stmts); + for (i = 0; i < count; i ++) { + ecs_script_node_t *stmt = stmts[i]; + ecs_id_t id = 0; + if (stmt->kind == EcsAstComponent) { + ecs_script_component_t *cmp = (ecs_script_component_t*)stmt; + id = cmp->id.eval; + } else if (stmt->kind == EcsAstTag) { + ecs_script_tag_t *cmp = (ecs_script_tag_t*)stmt; + id = cmp->id.eval; + } + + if (id) { + ecs_vec_append_t(a, &node->components, ecs_id_t)[0] = id; + } + } + + return 0; } static diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 20108da25..697fe3595 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -459,6 +459,15 @@ int flecs_script_eval_scope( } } + if (v->entity) { + ecs_entity_t src = v->entity->eval; + int32_t count = ecs_vec_count(&node->components); + if (src != EcsSingleton && count) { + flecs_add_ids( + v->world, src, ecs_vec_first(&node->components), count); + } + } + int result = ecs_script_visit_scope(v, node); ecs_vec_set_count_t(a, &v->r->using, ecs_entity_t, prev_using_count); diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index f578eb377..a37cd1676 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -73,6 +73,10 @@ int flecs_script_check_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + /* Functions shared between check and eval visitor */ int flecs_script_eval_scope( diff --git a/src/entity.c b/src/entity.c index 9db494a17..11113d7d1 100644 --- a/src/entity.c +++ b/src/entity.c @@ -1210,6 +1210,29 @@ void flecs_remove_id( flecs_defer_end(world, stage); } +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count) +{ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + ecs_table_t *table = ecs_get_table(world, entity); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + table = flecs_find_table_add(world, table, id, &diff); + } + + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); +} + static flecs_component_ptr_t flecs_ensure( ecs_world_t *world, diff --git a/src/private_api.h b/src/private_api.h index e64f87702..79c077c8e 100644 --- a/src/private_api.h +++ b/src/private_api.h @@ -121,6 +121,12 @@ void flecs_invoke_hook( ecs_entity_t event, ecs_iter_action_t hook); +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count); + //////////////////////////////////////////////////////////////////////////////// //// Query API //////////////////////////////////////////////////////////////////////////////// From fe1307968e49611438202845775510aa09d5e290 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 15:05:54 -0800 Subject: [PATCH 07/36] Fix misaligned memory access to template instance data --- distr/flecs.c | 18 ++++++++++-------- src/addons/script/template.h | 1 + src/addons/script/visit_eval.c | 16 ++++++++-------- src/addons/script/visit_free.c | 1 + 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 640165fa4..a7cd965fa 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5595,6 +5595,7 @@ typedef struct EcsScriptTemplateSetEvent { int32_t count; /* Storage for small template types */ + int64_t _align; /* Align data storage to 8 bytes */ char data_storage[ECS_TEMPLATE_SMALL_SIZE]; ecs_entity_t entity_storage; } EcsScriptTemplateSetEvent; @@ -61671,14 +61672,14 @@ int flecs_script_find_entity( goto error; } - if (var->value.ptr == NULL) { - if (!v->template) { - flecs_script_eval_error(v, NULL, - "variable '%s' is not initialized", path); - goto error; - } else { - return 0; - } + if (v->template) { + return 0; + } + + if (var->value.ptr == NULL) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + goto error; } ecs_entity_t result = *(ecs_entity_t*)var->value.ptr; @@ -62980,6 +62981,7 @@ void flecs_script_scope_free( { ecs_script_visit_scope(v, node); ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); + ecs_vec_fini_t(&v->script->allocator, &node->components, ecs_id_t); flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); } diff --git a/src/addons/script/template.h b/src/addons/script/template.h index a3ab59b03..3b6fab585 100644 --- a/src/addons/script/template.h +++ b/src/addons/script/template.h @@ -38,6 +38,7 @@ typedef struct EcsScriptTemplateSetEvent { int32_t count; /* Storage for small template types */ + int64_t _align; /* Align data storage to 8 bytes */ char data_storage[ECS_TEMPLATE_SMALL_SIZE]; ecs_entity_t entity_storage; } EcsScriptTemplateSetEvent; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 697fe3595..6fac84ef0 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -180,14 +180,14 @@ int flecs_script_find_entity( goto error; } - if (var->value.ptr == NULL) { - if (!v->template) { - flecs_script_eval_error(v, NULL, - "variable '%s' is not initialized", path); - goto error; - } else { - return 0; - } + if (v->template) { + return 0; + } + + if (var->value.ptr == NULL) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + goto error; } ecs_entity_t result = *(ecs_entity_t*)var->value.ptr; diff --git a/src/addons/script/visit_free.c b/src/addons/script/visit_free.c index 275cecffc..c3c3e0dd6 100644 --- a/src/addons/script/visit_free.c +++ b/src/addons/script/visit_free.c @@ -15,6 +15,7 @@ void flecs_script_scope_free( { ecs_script_visit_scope(v, node); ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); + ecs_vec_fini_t(&v->script->allocator, &node->components, ecs_id_t); flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); } From 8626d5ceab68672a3eee7117b5b5a36174eb8b6f Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 17:01:05 -0800 Subject: [PATCH 08/36] Allow script variables to be provided in arbitrary order --- distr/flecs.c | 68 ++--- distr/flecs.h | 17 +- include/flecs/addons/script.h | 17 +- src/addons/script/expr/visit_eval.c | 3 +- src/addons/script/template.c | 7 + src/addons/script/visit_eval.c | 57 ++-- src/addons/script/visit_eval.h | 1 + test/script/project.json | 6 + test/script/src/Eval.c | 413 ++++++++++++++++++++++++++++ test/script/src/main.c | 32 ++- 10 files changed, 540 insertions(+), 81 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index a7cd965fa..d5520b298 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5470,6 +5470,7 @@ typedef struct ecs_script_eval_visitor_t { ecs_entity_t with_relationship; int32_t with_relationship_sp; bool is_with_scope; + bool dynamic_variable_binding; ecs_script_vars_t *vars; } ecs_script_eval_visitor_t; @@ -59796,6 +59797,11 @@ int flecs_script_eval_template( template->entity = template_entity; template->node = node; + /* Variables are always presented to a template in a well defined order, so + * we don't need dynamic variable binding. */ + bool old_dynamic_variable_binding = v->dynamic_variable_binding; + v->dynamic_variable_binding = false; + if (flecs_script_template_preprocess(v, template)) { goto error; } @@ -59808,6 +59814,8 @@ int flecs_script_eval_template( goto error; } + v->dynamic_variable_binding = old_dynamic_variable_binding; + /* If template has no props, give template dummy size so we can register * hooks for it. */ if (!ecs_has(v->world, template_entity, EcsComponent)) { @@ -61626,13 +61634,11 @@ ecs_script_var_t* flecs_script_find_var( const char *name, int32_t *sp) { - ecs_assert(sp != NULL, ECS_INTERNAL_ERROR, NULL); - - if (sp[0] != -1) { + if (sp && sp[0] != -1) { return ecs_script_vars_from_sp(vars, sp[0]); } else { ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); - if (var) { + if (var && sp) { sp[0] = var->sp; } return var; @@ -61658,7 +61664,7 @@ int flecs_script_find_entity( } const ecs_script_var_t *var = flecs_script_find_var( - v->vars, &path[1], sp); + v->vars, &path[1], v->dynamic_variable_binding ? NULL : sp); if (!var) { goto error; } @@ -61795,7 +61801,9 @@ int flecs_script_eval_id( if (v->template) { /* Can't resolve variables while preprocessing template scope */ if (id->first[0] == '$') { - if (flecs_script_find_var(v->vars, &id->first[1], &id->first_sp)) { + if (flecs_script_find_var(v->vars, &id->first[1], + v->dynamic_variable_binding ? NULL : &id->first_sp)) + { return 0; } else { flecs_script_eval_error(v, node, @@ -61804,8 +61812,8 @@ int flecs_script_eval_id( } } if (id->second && id->second[0] == '$') { - if (flecs_script_find_var( - v->vars, &id->second[1], &id->second_sp)) + if (flecs_script_find_var(v->vars, &id->second[1], + v->dynamic_variable_binding ? NULL : &id->second_sp)) { return 0; } else { @@ -61905,7 +61913,8 @@ int flecs_script_eval_expr( .lookup_ctx = v, .vars = v->vars, .type = value->type, - .runtime = v->r + .runtime = v->r, + .disable_dynamic_variable_binding = !v->dynamic_variable_binding }; if (expr->type_info == NULL) { @@ -62290,17 +62299,13 @@ int flecs_script_eval_var_component( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var; - - if (node->sp != -1) { - var = ecs_script_vars_from_sp(v->vars, node->sp); - } else { - var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; - } + + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; } if (v->is_with_scope) { @@ -62391,19 +62396,8 @@ int flecs_script_eval_with_var( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var; - - if (node->sp != -1) { - var = ecs_script_vars_from_sp(v->vars, node->sp); - } else { - var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; - } - } - + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); @@ -62920,6 +62914,11 @@ void flecs_script_eval_visit_init( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; v->vars->sp = ecs_vec_count(&desc->vars->vars); + + /* When variables are provided to script, don't use cached variable + * stack pointers, as the order in which the application provides + * variables may not be the same across evaluations. */ + v->dynamic_variable_binding = true; } /* Always include flecs.meta */ @@ -77563,7 +77562,8 @@ int flecs_expr_variable_visit_eval( "variables available at parse time are not provided"); const ecs_script_var_t *var = flecs_script_find_var( - ctx->desc->vars, node->name, &node->sp); + ctx->desc->vars, node->name, + ctx->desc->disable_dynamic_variable_binding ? &node->sp : NULL); if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); diff --git a/distr/flecs.h b/distr/flecs.h index fd4738773..a15987eaf 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -14853,18 +14853,23 @@ void ecs_script_vars_from_iter( typedef struct ecs_expr_eval_desc_t { const char *name; /**< Script name */ const char *expr; /**< Full expression string */ + const ecs_script_vars_t *vars; /**< Variables accessible in expression */ + ecs_entity_t type; /**< Type of parsed value (optional) */ ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ const ecs_world_t*, const char *value, void *ctx); void *lookup_ctx; /**< Context passed to lookup function */ - const ecs_script_vars_t *vars; /**< Variables accessible in expression */ - ecs_entity_t type; /**< Type of parsed value (optional) */ - - /* Disable constant folding (slower evaluation, faster parsing) */ + + /** Disable constant folding (slower evaluation, faster parsing) */ bool disable_folding; - - /* Allow for unresolved identifiers when parsing. Useful when entities can + + /** This option instructs the expression runtime to lookup variables by + * stack pointer instead of by name, which improves performance. Only enable + * when provided variables are always declared in the same order. */ + bool disable_dynamic_variable_binding; + + /** Allow for unresolved identifiers when parsing. Useful when entities can * be created in between parsing & evaluating. */ bool allow_unresolved_identifiers; diff --git a/include/flecs/addons/script.h b/include/flecs/addons/script.h index 0c1c2e541..4f6ee4f7c 100644 --- a/include/flecs/addons/script.h +++ b/include/flecs/addons/script.h @@ -539,18 +539,23 @@ void ecs_script_vars_from_iter( typedef struct ecs_expr_eval_desc_t { const char *name; /**< Script name */ const char *expr; /**< Full expression string */ + const ecs_script_vars_t *vars; /**< Variables accessible in expression */ + ecs_entity_t type; /**< Type of parsed value (optional) */ ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ const ecs_world_t*, const char *value, void *ctx); void *lookup_ctx; /**< Context passed to lookup function */ - const ecs_script_vars_t *vars; /**< Variables accessible in expression */ - ecs_entity_t type; /**< Type of parsed value (optional) */ - - /* Disable constant folding (slower evaluation, faster parsing) */ + + /** Disable constant folding (slower evaluation, faster parsing) */ bool disable_folding; - - /* Allow for unresolved identifiers when parsing. Useful when entities can + + /** This option instructs the expression runtime to lookup variables by + * stack pointer instead of by name, which improves performance. Only enable + * when provided variables are always declared in the same order. */ + bool disable_dynamic_variable_binding; + + /** Allow for unresolved identifiers when parsing. Useful when entities can * be created in between parsing & evaluating. */ bool allow_unresolved_identifiers; diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index dfa8c0da8..53c81ec85 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -325,7 +325,8 @@ int flecs_expr_variable_visit_eval( "variables available at parse time are not provided"); const ecs_script_var_t *var = flecs_script_find_var( - ctx->desc->vars, node->name, &node->sp); + ctx->desc->vars, node->name, + ctx->desc->disable_dynamic_variable_binding ? &node->sp : NULL); if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index e6805af45..aeba9960c 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -516,6 +516,11 @@ int flecs_script_eval_template( template->entity = template_entity; template->node = node; + /* Variables are always presented to a template in a well defined order, so + * we don't need dynamic variable binding. */ + bool old_dynamic_variable_binding = v->dynamic_variable_binding; + v->dynamic_variable_binding = false; + if (flecs_script_template_preprocess(v, template)) { goto error; } @@ -528,6 +533,8 @@ int flecs_script_eval_template( goto error; } + v->dynamic_variable_binding = old_dynamic_variable_binding; + /* If template has no props, give template dummy size so we can register * hooks for it. */ if (!ecs_has(v->world, template_entity, EcsComponent)) { diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 6fac84ef0..ca644e362 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -134,13 +134,11 @@ ecs_script_var_t* flecs_script_find_var( const char *name, int32_t *sp) { - ecs_assert(sp != NULL, ECS_INTERNAL_ERROR, NULL); - - if (sp[0] != -1) { + if (sp && sp[0] != -1) { return ecs_script_vars_from_sp(vars, sp[0]); } else { ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); - if (var) { + if (var && sp) { sp[0] = var->sp; } return var; @@ -166,7 +164,7 @@ int flecs_script_find_entity( } const ecs_script_var_t *var = flecs_script_find_var( - v->vars, &path[1], sp); + v->vars, &path[1], v->dynamic_variable_binding ? NULL : sp); if (!var) { goto error; } @@ -303,7 +301,9 @@ int flecs_script_eval_id( if (v->template) { /* Can't resolve variables while preprocessing template scope */ if (id->first[0] == '$') { - if (flecs_script_find_var(v->vars, &id->first[1], &id->first_sp)) { + if (flecs_script_find_var(v->vars, &id->first[1], + v->dynamic_variable_binding ? NULL : &id->first_sp)) + { return 0; } else { flecs_script_eval_error(v, node, @@ -312,8 +312,8 @@ int flecs_script_eval_id( } } if (id->second && id->second[0] == '$') { - if (flecs_script_find_var( - v->vars, &id->second[1], &id->second_sp)) + if (flecs_script_find_var(v->vars, &id->second[1], + v->dynamic_variable_binding ? NULL : &id->second_sp)) { return 0; } else { @@ -413,7 +413,8 @@ int flecs_script_eval_expr( .lookup_ctx = v, .vars = v->vars, .type = value->type, - .runtime = v->r + .runtime = v->r, + .disable_dynamic_variable_binding = !v->dynamic_variable_binding }; if (expr->type_info == NULL) { @@ -798,17 +799,13 @@ int flecs_script_eval_var_component( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var; - - if (node->sp != -1) { - var = ecs_script_vars_from_sp(v->vars, node->sp); - } else { - var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; - } + + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; } if (v->is_with_scope) { @@ -899,19 +896,8 @@ int flecs_script_eval_with_var( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { - ecs_script_var_t *var; - - if (node->sp != -1) { - var = ecs_script_vars_from_sp(v->vars, node->sp); - } else { - var = ecs_script_vars_lookup(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; - } - } - + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); @@ -1428,6 +1414,11 @@ void flecs_script_eval_visit_init( v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; v->vars->sp = ecs_vec_count(&desc->vars->vars); + + /* When variables are provided to script, don't use cached variable + * stack pointers, as the order in which the application provides + * variables may not be the same across evaluations. */ + v->dynamic_variable_binding = true; } /* Always include flecs.meta */ diff --git a/src/addons/script/visit_eval.h b/src/addons/script/visit_eval.h index a37cd1676..918b572ae 100644 --- a/src/addons/script/visit_eval.h +++ b/src/addons/script/visit_eval.h @@ -18,6 +18,7 @@ typedef struct ecs_script_eval_visitor_t { ecs_entity_t with_relationship; int32_t with_relationship_sp; bool is_with_scope; + bool dynamic_variable_binding; ecs_script_vars_t *vars; } ecs_script_eval_visitor_t; diff --git a/test/script/project.json b/test/script/project.json index 51a8687dc..b3e4a1684 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -277,6 +277,12 @@ "assign_call_scoped_func_w_using", "eval_w_vars", "eval_w_runtime", + "eval_w_other_vars", + "eval_w_vars_different_order", + "eval_w_vars_different_order_var_component", + "eval_w_vars_different_order_with_var", + "eval_w_vars_different_order_pair_w_var", + "eval_w_vars_different_order_pair_scope_w_var", "component_in_entity_in_with_scope", "entity_w_string_name", "entity_w_interpolated_name", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index faf106952..26efc1eb3 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -8752,6 +8752,419 @@ void Eval_eval_w_vars(void) { ecs_fini(world); } +void Eval_eval_w_other_vars(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_t *s; + + const char *expr = + LINE "e = Position: {$foo, $bar * 2}"; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(int32_t*)foo->value.ptr = 10; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_i32_t); + *(int32_t*)bar->value.ptr = 20; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 40); + + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(int32_t*)foo->value.ptr = 20; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_i32_t); + *(int32_t*)bar->value.ptr = 30; + ecs_script_eval_desc_t desc = { .vars = vars }; + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 60); + + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_vars_different_order(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_t *s; + + const char *expr = + LINE "e = Position: {$foo, $bar * 2}"; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(int32_t*)foo->value.ptr = 10; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_i32_t); + *(int32_t*)bar->value.ptr = 20; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 40); + + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_i32_t); + *(int32_t*)bar->value.ptr = 30; + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_i32_t); + *(int32_t*)foo->value.ptr = 20; + ecs_script_eval_desc_t desc = { .vars = vars }; + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 60); + + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_vars_different_order_var_component(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Velocity"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_t *s; + + const char *expr = + LINE "e {" + LINE " $foo" + LINE " $bar" + LINE "}" + ; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Position); + ((Position*)foo->value.ptr)->x = 10; + ((Position*)foo->value.ptr)->y = 20; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", Velocity); + ((Velocity*)bar->value.ptr)->x = 1; + ((Velocity*)bar->value.ptr)->y = 2; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", Velocity); + ((Velocity*)bar->value.ptr)->x = 1; + ((Velocity*)bar->value.ptr)->y = 2; + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Position); + ((Position*)foo->value.ptr)->x = 10; + ((Position*)foo->value.ptr)->y = 20; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_vars_different_order_with_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + + ecs_entity_t ecs_id(Velocity) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Velocity"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_script_t *s; + + const char *expr = + LINE "with $foo, $bar {" + LINE " e {}" + LINE "}" + ; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Position); + ((Position*)foo->value.ptr)->x = 10; + ((Position*)foo->value.ptr)->y = 20; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", Velocity); + ((Velocity*)bar->value.ptr)->x = 1; + ((Velocity*)bar->value.ptr)->y = 2; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", Velocity); + ((Velocity*)bar->value.ptr)->x = 1; + ((Velocity*)bar->value.ptr)->y = 2; + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", Position); + ((Position*)foo->value.ptr)->x = 10; + ((Position*)foo->value.ptr)->y = 20; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_vars_different_order_pair_w_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Tgt); + + ecs_script_t *s; + + const char *expr = + LINE "e {" + LINE " ($foo, $bar)" + LINE "}" + ; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_entity_t); + *((ecs_entity_t*)foo->value.ptr) = Rel; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_entity_t); + *((ecs_entity_t*)bar->value.ptr) = Tgt; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, Rel, Tgt)); + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_entity_t); + *((ecs_entity_t*)bar->value.ptr) = Tgt; + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_entity_t); + *((ecs_entity_t*)foo->value.ptr) = Rel; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, Rel, Tgt)); + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_eval_w_vars_different_order_pair_scope_w_var(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Tgt); + + ecs_script_t *s; + + const char *expr = + LINE "($foo, $bar) {" + LINE " e {}" + LINE "}" + ; + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_entity_t); + *((ecs_entity_t*)foo->value.ptr) = Rel; + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_entity_t); + *((ecs_entity_t*)bar->value.ptr) = Tgt; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, Rel, Tgt)); + ecs_script_vars_fini(vars); + } + + { + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *bar = ecs_script_vars_define(vars, "bar", ecs_entity_t); + *((ecs_entity_t*)bar->value.ptr) = Tgt; + ecs_script_var_t *foo = ecs_script_vars_define(vars, "foo", ecs_entity_t); + *((ecs_entity_t*)foo->value.ptr) = Rel; + ecs_script_eval_desc_t desc = { .vars = vars }; + + s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + test_int(0, ecs_script_eval(s, &desc)); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, Rel, Tgt)); + ecs_script_vars_fini(vars); + } + + ecs_script_free(s); + + ecs_fini(world); +} + void Eval_eval_w_runtime(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 7a0400714..65f24faf4 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -273,6 +273,12 @@ void Eval_assign_call_scoped_func(void); void Eval_assign_call_scoped_func_w_using(void); void Eval_eval_w_vars(void); void Eval_eval_w_runtime(void); +void Eval_eval_w_other_vars(void); +void Eval_eval_w_vars_different_order(void); +void Eval_eval_w_vars_different_order_var_component(void); +void Eval_eval_w_vars_different_order_with_var(void); +void Eval_eval_w_vars_different_order_pair_w_var(void); +void Eval_eval_w_vars_different_order_pair_scope_w_var(void); void Eval_component_in_entity_in_with_scope(void); void Eval_entity_w_string_name(void); void Eval_entity_w_interpolated_name(void); @@ -1920,6 +1926,30 @@ bake_test_case Eval_testcases[] = { "eval_w_runtime", Eval_eval_w_runtime }, + { + "eval_w_other_vars", + Eval_eval_w_other_vars + }, + { + "eval_w_vars_different_order", + Eval_eval_w_vars_different_order + }, + { + "eval_w_vars_different_order_var_component", + Eval_eval_w_vars_different_order_var_component + }, + { + "eval_w_vars_different_order_with_var", + Eval_eval_w_vars_different_order_with_var + }, + { + "eval_w_vars_different_order_pair_w_var", + Eval_eval_w_vars_different_order_pair_w_var + }, + { + "eval_w_vars_different_order_pair_scope_w_var", + Eval_eval_w_vars_different_order_pair_scope_w_var + }, { "component_in_entity_in_with_scope", Eval_component_in_entity_in_with_scope @@ -4244,7 +4274,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 279, + 285, Eval_testcases }, { From 2ee680f7c12b38957ef74faa3bfebbad4fc2c133 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 21:04:47 -0800 Subject: [PATCH 09/36] Fix issue with initialization of random number generator --- distr/flecs.c | 22 +++++++++++++++++----- src/addons/script/functions_math.c | 22 +++++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d5520b298..ffba7e915 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56161,15 +56161,17 @@ typedef struct ecs_script_rng_t { uint64_t w; /* Weyl sequence increment */ uint64_t s; /* Constant for Weyl sequence */ int32_t refcount; /* Necessary as flecs script doesn't have ref types */ + bool initialized; } ecs_script_rng_t; static -ecs_script_rng_t* flecs_script_rng_new(uint64_t seed) { +ecs_script_rng_t* flecs_script_rng_new() { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); - result->x = seed; + result->x = 0; result->w = 0; result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ result->refcount = 1; + result->initialized = false; return result; } @@ -56205,7 +56207,7 @@ ECS_COMPONENT_DECLARE(EcsScriptRng); static ECS_CTOR(EcsScriptRng, ptr, { ptr->seed = 0; - ptr->impl = NULL; + ptr->impl = flecs_script_rng_new(); }) static @@ -56240,7 +56242,12 @@ void flecs_script_rng_get_float( (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; - if (!rng->impl) rng->impl = flecs_script_rng_new(rng->seed); + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; @@ -56256,7 +56263,12 @@ void flecs_script_rng_get_uint( (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; - if (!rng->impl) rng->impl = flecs_script_rng_new(rng->seed); + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index 3f0987450..aa582df34 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -14,15 +14,17 @@ typedef struct ecs_script_rng_t { uint64_t w; /* Weyl sequence increment */ uint64_t s; /* Constant for Weyl sequence */ int32_t refcount; /* Necessary as flecs script doesn't have ref types */ + bool initialized; } ecs_script_rng_t; static -ecs_script_rng_t* flecs_script_rng_new(uint64_t seed) { +ecs_script_rng_t* flecs_script_rng_new() { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); - result->x = seed; + result->x = 0; result->w = 0; result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ result->refcount = 1; + result->initialized = false; return result; } @@ -58,7 +60,7 @@ ECS_COMPONENT_DECLARE(EcsScriptRng); static ECS_CTOR(EcsScriptRng, ptr, { ptr->seed = 0; - ptr->impl = NULL; + ptr->impl = flecs_script_rng_new(); }) static @@ -93,7 +95,12 @@ void flecs_script_rng_get_float( (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; - if (!rng->impl) rng->impl = flecs_script_rng_new(rng->seed); + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; @@ -109,7 +116,12 @@ void flecs_script_rng_get_uint( (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; - if (!rng->impl) rng->impl = flecs_script_rng_new(rng->seed); + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; From 23703e97c7bb47f978bd661127fd5986e34534d8 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 24 Dec 2024 21:44:04 -0800 Subject: [PATCH 10/36] Fix issue with looking up entity in template scope with anonymous child --- distr/flecs.c | 14 +++++- src/addons/script/visit_eval.c | 14 +++++- test/script/project.json | 5 ++- test/script/src/Template.c | 82 ++++++++++++++++++++++++++++++++++ test/script/src/main.c | 17 ++++++- 5 files changed, 128 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index ffba7e915..b5496f765 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -61770,10 +61770,16 @@ ecs_entity_t flecs_script_find_entity_action( static int flecs_script_find_template_entity( ecs_script_eval_visitor_t *v, + void *node, const char *name) { + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + /* Loop template scope to see if it declares an entity with requested name */ ecs_script_template_t *t = v->template; + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t->node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_scope_t *scope = t->node->scope; ecs_script_node_t **nodes = ecs_vec_first_t( &scope->stmts, ecs_script_node_t*); @@ -61783,12 +61789,18 @@ int flecs_script_find_template_entity( ecs_script_node_t *node = nodes[i]; if (node->kind == EcsAstEntity) { ecs_script_entity_t *entity_node = (ecs_script_entity_t*)node; + if (!entity_node->name) { + continue; + } + if (!ecs_os_strcmp(entity_node->name, name)) { return 0; } } } + flecs_script_eval_error(v, node, "unresolved reference to '%s'", name); + return -1; } @@ -61868,7 +61880,7 @@ int flecs_script_eval_id( /* Targets may be defined by the template */ if (v->template) { - if (!flecs_script_find_template_entity(v, id->second)) { + if (!flecs_script_find_template_entity(v, node, id->second)) { return 0; } else { return -1; diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index ca644e362..b8e720d5f 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -258,10 +258,16 @@ ecs_entity_t flecs_script_find_entity_action( static int flecs_script_find_template_entity( ecs_script_eval_visitor_t *v, + void *node, const char *name) { + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + /* Loop template scope to see if it declares an entity with requested name */ ecs_script_template_t *t = v->template; + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t->node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_scope_t *scope = t->node->scope; ecs_script_node_t **nodes = ecs_vec_first_t( &scope->stmts, ecs_script_node_t*); @@ -271,12 +277,18 @@ int flecs_script_find_template_entity( ecs_script_node_t *node = nodes[i]; if (node->kind == EcsAstEntity) { ecs_script_entity_t *entity_node = (ecs_script_entity_t*)node; + if (!entity_node->name) { + continue; + } + if (!ecs_os_strcmp(entity_node->name, name)) { return 0; } } } + flecs_script_eval_error(v, node, "unresolved reference to '%s'", name); + return -1; } @@ -356,7 +368,7 @@ int flecs_script_eval_id( /* Targets may be defined by the template */ if (v->template) { - if (!flecs_script_find_template_entity(v, id->second)) { + if (!flecs_script_find_template_entity(v, node, id->second)) { return 0; } else { return -1; diff --git a/test/script/project.json b/test/script/project.json index b3e4a1684..4aa3ccfd7 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -362,7 +362,10 @@ "bulk_create_template", "template_w_expr_w_self_ref", "entity_w_assign_with_nested_template", - "template_w_for" + "template_w_for", + "template_w_component_w_undefined_identifier", + "template_w_child_component_w_undefined_identifier", + "template_w_anonymous_child_component_w_undefined_identifier" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 77f1aff8f..47d5d15ec 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2906,3 +2906,85 @@ void Template_template_w_for(void) { ecs_fini(world); } + +void Template_template_w_component_w_undefined_identifier(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " Position: P" + LINE "}"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Template_template_w_child_component_w_undefined_identifier(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " foo {" + LINE " Position: P" + LINE " }" + LINE "}"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Template_template_w_anonymous_child_component_w_undefined_identifier(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Foo {" + LINE " _ {" + LINE " Position: P" + LINE " }" + LINE "}"; + + ecs_log_set_level(-4); + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 65f24faf4..b0f8bfb81 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -357,6 +357,9 @@ void Template_bulk_create_template(void); void Template_template_w_expr_w_self_ref(void); void Template_entity_w_assign_with_nested_template(void); void Template_template_w_for(void); +void Template_template_w_component_w_undefined_identifier(void); +void Template_template_w_child_component_w_undefined_identifier(void); +void Template_template_w_anonymous_child_component_w_undefined_identifier(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2256,6 +2259,18 @@ bake_test_case Template_testcases[] = { { "template_w_for", Template_template_w_for + }, + { + "template_w_component_w_undefined_identifier", + Template_template_w_component_w_undefined_identifier + }, + { + "template_w_child_component_w_undefined_identifier", + Template_template_w_child_component_w_undefined_identifier + }, + { + "template_w_anonymous_child_component_w_undefined_identifier", + Template_template_w_anonymous_child_component_w_undefined_identifier } }; @@ -4281,7 +4296,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 61, + 64, Template_testcases }, { From 573207502ef47bd3b52d2b7486fc881a78a0e0c6 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 25 Dec 2024 18:58:09 -0800 Subject: [PATCH 11/36] Remove empty table propagation from table cache --- distr/flecs.c | 580 ++++++---------------------- distr/flecs.h | 12 +- include/flecs.h | 8 - include/flecs/private/api_flags.h | 1 - include/flecs/private/api_types.h | 3 +- src/addons/doc.c | 2 - src/addons/journal.c | 3 - src/addons/stats/stats.c | 1 - src/bootstrap.c | 2 - src/each.c | 2 - src/entity.c | 8 +- src/observer.c | 12 +- src/private_types.h | 5 - src/query/api.c | 33 +- src/query/engine/cache.c | 91 +---- src/query/engine/cache.h | 4 - src/query/engine/cache_iter.c | 45 ++- src/query/engine/cache_order_by.c | 11 +- src/query/engine/change_detection.c | 6 +- src/query/engine/eval_iter.c | 12 +- src/query/engine/eval_up.c | 4 +- src/stage.c | 2 - src/storage/id_index.c | 4 - src/storage/table.c | 40 +- src/storage/table_cache.c | 133 +++---- src/storage/table_cache.h | 11 - src/storage/table_graph.c | 1 - src/world.c | 143 +------ src/world.h | 7 - test/core/project.json | 2 - test/core/src/Entity.c | 1 + test/core/src/Observer.c | 31 -- test/core/src/World.c | 17 +- test/core/src/WorldInfo.c | 26 -- test/core/src/main.c | 14 +- test/query/project.json | 1 + test/query/src/Basic.c | 140 +++++-- test/query/src/Operators.c | 82 ++-- test/query/src/OrderBy.c | 2 + test/query/src/Sparse.c | 32 +- test/query/src/main.c | 7 +- test/script/project.json | 7 +- test/script/src/Eval.c | 15 + test/script/src/Template.c | 114 ++++++ test/script/src/main.c | 19 +- 45 files changed, 570 insertions(+), 1126 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index b5496f765..1c6a3fd2e 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -735,7 +735,6 @@ typedef struct ecs_table_cache_list_t { typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; - ecs_table_cache_list_t empty_tables; } ecs_table_cache_t; /* World level allocators are for operations that are not multithreaded */ @@ -973,10 +972,6 @@ struct ecs_world_t { /* -- Data storage -- */ ecs_store_t store; - /* -- Pending table event buffers -- */ - ecs_sparse_t *pending_buffer; /* sparse */ - ecs_sparse_t *pending_tables; /* sparse */ - /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ @@ -1064,12 +1059,6 @@ void ecs_table_cache_insert( const ecs_table_t *table, ecs_table_cache_hdr_t *result); -void ecs_table_cache_insert_w_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result, - bool is_empty); - void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, @@ -1089,12 +1078,7 @@ bool ecs_table_cache_set_empty( const ecs_table_t *table, bool empty); -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache); - #define flecs_table_cache_count(cache) (cache)->tables.count -#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count -#define flecs_table_cache_all_count(cache) ((cache)->tables.count + (cache)->empty_tables.count) bool flecs_table_cache_iter( ecs_table_cache_t *cache, @@ -1882,10 +1866,6 @@ void flecs_query_cache_build_sorted_tables( int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache); -/* Return number of empty tables in cache */ -int32_t flecs_query_cache_empty_table_count( - ecs_query_cache_t *cache); - /* Return number of entities in cache (requires iterating tables) */ int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache); @@ -2889,17 +2869,10 @@ void flecs_unregister_table( ecs_world_t *world, ecs_table_t *table); -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table); - void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); -void flecs_process_pending_tables( - const ecs_world_t *world); - /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from @@ -4170,8 +4143,6 @@ void flecs_bootstrap( flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); - flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); - flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); @@ -4423,8 +4394,6 @@ ecs_iter_t ecs_each_id( const ecs_world_t *world = ecs_get_world(stage); - flecs_process_pending_tables(world); - ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), @@ -8479,9 +8448,6 @@ void flecs_on_delete( * frame will handle the actual cleanup. */ int32_t i, count = ecs_vec_count(&world->store.marked_ids); - /* Make sure we're evaluating a consistent list of non-empty tables */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); @@ -8493,9 +8459,6 @@ void flecs_on_delete( /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); - /* All marked tables are empty, ensure they're in the right list */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); @@ -9159,7 +9122,7 @@ void flecs_modified_id_if( ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; - if (!flecs_table_record_get(world, table, id)) { + if (!table || !flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } @@ -14945,12 +14908,6 @@ ecs_flags32_t flecs_id_flag_for_event( if (e == EcsOnSet) { return EcsIdHasOnSet; } - if (e == EcsOnTableFill) { - return EcsIdHasOnTableFill; - } - if (e == EcsOnTableEmpty) { - return EcsIdHasOnTableEmpty; - } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } @@ -15542,8 +15499,6 @@ void flecs_observer_yield_existing( run = flecs_multi_observer_invoke_no_query; } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - ecs_defer_begin(world); /* If yield existing is enabled, invoke for each thing that matches @@ -15712,9 +15667,7 @@ int flecs_multi_observer_init( bool only_table_events = true; for (i = 0; i < o->event_count; i ++) { ecs_entity_t e = o->events[i]; - if (e != EcsOnTableCreate && e != EcsOnTableDelete && - e != EcsOnTableEmpty && e != EcsOnTableFill) - { + if (e != EcsOnTableCreate && e != EcsOnTableDelete) { only_table_events = false; break; } @@ -18142,8 +18095,6 @@ bool ecs_readonly_begin( { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); - ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); @@ -18543,8 +18494,6 @@ const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; -const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 47; -const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 48; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; @@ -19177,8 +19126,6 @@ void flecs_fini_roots( { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, @@ -19436,13 +19383,6 @@ ecs_world_t *ecs_mini(void) { ecs_id_record_t*, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); - world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); - flecs_sparse_init_t(world->pending_tables, a, - &world->allocators.sparse_chunk, ecs_table_t*); - world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); - flecs_sparse_init_t(world->pending_buffer, a, - &world->allocators.sparse_chunk, ecs_table_t*); - flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); @@ -20112,10 +20052,6 @@ int ecs_fini( ecs_dbg_1("#[bold]cleanup world data structures"); ecs_log_push_1(); flecs_entities_fini(world); - flecs_sparse_fini(world->pending_tables); - flecs_sparse_fini(world->pending_buffer); - ecs_os_free(world->pending_tables); - ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); @@ -20157,7 +20093,6 @@ void flecs_eval_component_monitors( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } @@ -20204,7 +20139,6 @@ void ecs_set_default_query_flags( ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); world->default_query_flags = flags; } @@ -20632,8 +20566,6 @@ void flecs_process_empty_queries( return; } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, world->stages[0]); @@ -20659,119 +20591,6 @@ void flecs_process_empty_queries( flecs_defer_end(world, world->stages[0]); } -/** Walk over tables that had a state change which requires bookkeeping */ -void flecs_process_pending_tables( - const ecs_world_t *world_r) -{ - flecs_poly_assert(world_r, ecs_world_t); - - /* We can't update the administration while in readonly mode, but we can - * ensure that when this function is called there are no pending events. */ - if (world_r->flags & EcsWorldReadonly) { - ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - return; - } - - /* Safe to cast, world is not readonly */ - ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); - - /* If pending buffer is NULL there already is a stackframe that's iterating - * the table list. This can happen when an observer for a table event results - * in a mutation that causes another table to change state. A typical - * example of this is a system that becomes active/inactive as the result of - * a query (and as a result, its matched tables) becoming empty/non empty */ - if (!world->pending_buffer) { - return; - } - - /* Swap buffer. The logic could in theory have been implemented with a - * single sparse set, but that would've complicated (and slowed down) the - * iteration. Additionally, by using a double buffer approach we can still - * keep most of the original ordering of events intact, which is desirable - * as it means that the ordering of tables in the internal data structures is - * more predictable. */ - int32_t i, count = flecs_sparse_count(world->pending_tables); - if (!count) { - return; - } - - ecs_os_perf_trace_push("flecs.process_pending_tables"); - - flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); - - do { - ecs_sparse_t *pending_tables = world->pending_tables; - world->pending_tables = world->pending_buffer; - world->pending_buffer = NULL; - - /* Make sure that any ECS operations that occur while delivering the - * events does not cause inconsistencies, like sending an Empty - * notification for a table that just became non-empty. */ - flecs_defer_begin(world, world->stages[0]); - - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense_t( - pending_tables, ecs_table_t*, i)[0]; - if (!table->id) { - /* Table is being deleted, ignore empty events */ - continue; - } - - /* For each id in the table, add it to the empty/non empty list - * based on its current state */ - if (flecs_table_records_update_empty(table)) { - int32_t table_count = ecs_table_count(table); - if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { - /* Only emit an event when there was a change in the - * administration. It is possible that a table ended up in the - * pending_tables list by going from empty->non-empty, but then - * became empty again. By the time we run this code, no changes - * in the administration would actually be made. */ - ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; - if (ecs_should_log_3()) { - ecs_dbg_3("table %u state change (%s)", - (uint32_t)table->id, - table_count ? "non-empty" : "empty"); - } - - ecs_log_push_3(); - - flecs_table_emit(world, table, evt); - - ecs_log_pop_3(); - } - world->info.empty_table_count += (table_count == 0) * 2 - 1; - } - } - - flecs_sparse_clear(pending_tables); - ecs_defer_end(world); - - world->pending_buffer = pending_tables; - } while ((count = flecs_sparse_count(world->pending_tables))); - - flecs_journal_end(); - - ecs_os_perf_trace_pop("flecs.process_pending_tables"); -} - -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_table_count(table)) { - table->_->generation = 0; - } - - flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, - (uint32_t)table->id)[0] = table; -} - bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) @@ -20780,8 +20599,8 @@ bool ecs_id_in_use( if (!idr) { return false; } - return (flecs_table_cache_count(&idr->cache) != 0) || - (flecs_table_cache_empty_count(&idr->cache) != 0); + + return (flecs_table_cache_count(&idr->cache) != 0); } void ecs_run_aperiodic( @@ -20789,10 +20608,6 @@ void ecs_run_aperiodic( ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); - - if (!flags || (flags & EcsAperiodicEmptyTables)) { - flecs_process_pending_tables(world); - } if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); @@ -20815,9 +20630,6 @@ int32_t ecs_delete_empty_tables( ecs_os_perf_trace_push("flecs.delete_empty_tables"); - /* Make sure empty tables are in the empty table lists */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0; bool time_budget = false; @@ -22120,8 +21932,6 @@ void flecs_doc_import_core_definitions( ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); - ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); - ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); @@ -24532,9 +24342,6 @@ void flecs_journal_begin( } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); - } else if (kind == EcsJournalTableEvents) { - ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " - "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); @@ -33695,23 +33502,13 @@ ecs_query_count_t ecs_query_count( return result; } - ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); - - ecs_query_impl_t *impl = flecs_query_impl(q); - if (impl->cache && q->flags & EcsQueryIsCacheable) { - result.results = flecs_query_cache_table_count(impl->cache); - result.entities = flecs_query_cache_entity_count(impl->cache); - result.tables = flecs_query_cache_table_count(impl->cache); - result.empty_tables = flecs_query_cache_empty_table_count(impl->cache); - } else { - ecs_iter_t it = flecs_query_iter(q->world, q); - it.flags |= EcsIterNoData; + ecs_iter_t it = flecs_query_iter(q->world, q); + it.flags |= EcsIterNoData; - while (ecs_query_next(&it)) { - result.results ++; - result.entities += it.count; - ecs_iter_skip(&it); - } + while (ecs_query_next(&it)) { + result.results ++; + result.entities += it.count; + ecs_iter_skip(&it); } return result; @@ -33722,15 +33519,8 @@ bool ecs_query_is_true( { flecs_poly_assert(q, ecs_query_t); - ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); - - ecs_query_impl_t *impl = flecs_query_impl(q); - if (impl->cache && q->flags & EcsQueryIsCacheable) { - return flecs_query_cache_table_count(impl->cache) != 0; - } else { - ecs_iter_t it = flecs_query_iter(q->world, q); - return ecs_iter_is_true(&it); - } + ecs_iter_t it = flecs_query_iter(q->world, q); + return ecs_iter_is_true(&it); } int32_t ecs_query_match_count( @@ -36893,8 +36683,6 @@ ecs_flags32_t flecs_id_record_event_flags( result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; - result |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; - result |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; return result; @@ -37114,8 +36902,6 @@ void flecs_id_record_assert_empty( (void)idr; ecs_assert(flecs_table_cache_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); } static @@ -38091,13 +37877,12 @@ void flecs_table_add_trigger_flags( flags = EcsTableHasOnRemove; } else if (event == EcsOnSet) { flags = EcsTableHasOnSet; - } else if (event == EcsOnTableFill) { - flags = EcsTableHasOnTableFill; - } else if (event == EcsOnTableEmpty) { - flags = EcsTableHasOnTableEmpty; + } else if (event == EcsOnTableCreate) { + flags = EcsTableHasOnTableCreate; + } else if (event == EcsOnTableDelete) { + flags = EcsTableHasOnTableDelete; } else if (event == EcsWildcard) { flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| - EcsTableHasOnTableFill|EcsTableHasOnTableEmpty| EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; } @@ -38395,11 +38180,6 @@ void flecs_table_fini_data( } table->data.count = 0; - - if (deactivate && count) { - flecs_table_set_empty(world, table); - } - table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } @@ -38483,8 +38263,6 @@ void flecs_table_fini( ecs_log_push_2(); } - world->info.empty_table_count -= (ecs_table_count(table) == 0); - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ flecs_table_fini_data(world, table, false, true, false, true); flecs_table_clear_edges(world, table); @@ -38840,10 +38618,6 @@ int32_t flecs_table_grow_data( /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); - if (!(world->flags & EcsWorldReadonly) && !count) { - flecs_table_set_empty(world, table); - } - /* Return index of first added entity */ return count; } @@ -38903,9 +38677,6 @@ int32_t flecs_table_append( flecs_table_fast_append(world, table); table->data.count = v_entities.count; table->data.size = v_entities.size; - if (!count) { - flecs_table_set_empty(world, table); /* See below */ - } return count; } @@ -38917,12 +38688,6 @@ int32_t flecs_table_append( table->data.count = v_entities.count; table->data.size = v_entities.size; - /* If this is the first entity in this table, signal queries so that the - * table moves from an inactive table to an active table. */ - if (!count) { - flecs_table_set_empty(world, table); - } - /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ @@ -39032,9 +38797,6 @@ void flecs_table_delete( } table->data.count --; - if (!count) { - flecs_table_set_empty(world, table); - } flecs_table_check_sanity(world, table); return; @@ -39098,9 +38860,6 @@ void flecs_table_delete( } table->data.count --; - if (!count) { - flecs_table_set_empty(world, table); - } flecs_table_check_sanity(world, table); } @@ -39624,11 +39383,6 @@ void flecs_table_merge( flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); if (src_count) { - if (!dst_count) { - flecs_table_set_empty(world, dst_table); - } - flecs_table_set_empty(world, src_table); - flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); @@ -40017,20 +39771,19 @@ void flecs_table_cache_list_remove( prev->next = next; } - cache->empty_tables.count -= !!elem->empty; - cache->tables.count -= !elem->empty; + cache->tables.count --; - if (cache->empty_tables.first == elem) { - cache->empty_tables.first = next; - } else if (cache->tables.first == elem) { + if (cache->tables.first == elem) { cache->tables.first = next; } - if (cache->empty_tables.last == elem) { - cache->empty_tables.last = prev; - } if (cache->tables.last == elem) { cache->tables.last = prev; } + + ecs_assert(cache->tables.first == NULL || cache->tables.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, + ECS_INTERNAL_ERROR, NULL); } static @@ -40038,19 +39791,10 @@ void flecs_table_cache_list_insert( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { - ecs_table_cache_hdr_t *last; - if (elem->empty) { - last = cache->empty_tables.last; - cache->empty_tables.last = elem; - if ((++ cache->empty_tables.count) == 1) { - cache->empty_tables.first = elem; - } - } else { - last = cache->tables.last; - cache->tables.last = elem; - if ((++ cache->tables.count) == 1) { - cache->tables.first = elem; - } + ecs_table_cache_hdr_t *last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; } elem->next = NULL; @@ -40059,6 +39803,10 @@ void flecs_table_cache_list_insert( if (last) { last->next = elem; } + + ecs_assert( + cache->tables.count != 1 || cache->tables.first == cache->tables.last, + ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_init( @@ -40076,17 +39824,10 @@ void ecs_table_cache_fini( ecs_map_fini(&cache->index); } -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) -{ - return ecs_map_count(&cache->index) == 0; -} - -void ecs_table_cache_insert_w_empty( +void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, - ecs_table_cache_hdr_t *result, - bool empty) + ecs_table_cache_hdr_t *result) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_cache_get(cache, table) == NULL, @@ -40095,7 +39836,6 @@ void ecs_table_cache_insert_w_empty( result->cache = cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); - result->empty = empty; flecs_table_cache_list_insert(cache, result); @@ -40103,25 +39843,7 @@ void ecs_table_cache_insert_w_empty( ecs_map_insert_ptr(&cache->index, table->id, result); } - ecs_assert(empty || cache->tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!empty || cache->empty_tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); -} - -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result) -{ - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; - } - - ecs_table_cache_insert_w_empty(cache, table, result, empty); + ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_replace( @@ -40146,12 +39868,6 @@ void ecs_table_cache_replace( next->prev = elem; } - if (cache->empty_tables.first == old) { - cache->empty_tables.first = elem; - } - if (cache->empty_tables.last == old) { - cache->empty_tables.last = elem; - } if (cache->tables.first == old) { cache->tables.first = elem; } @@ -40203,24 +39919,7 @@ bool ecs_table_cache_set_empty( const ecs_table_t *table, bool empty) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, - ecs_table_cache_hdr_t, table->id); - if (!elem) { - return false; - } - - if (elem->empty == empty) { - return false; - } - - flecs_table_cache_list_remove(cache, elem); - elem->empty = empty; - flecs_table_cache_list_insert(cache, elem); - - return true; + return false; } bool flecs_table_cache_iter( @@ -40230,8 +39929,9 @@ bool flecs_table_cache_iter( ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->tables.first; - out->next_list = NULL; out->cur = NULL; + out->iter_fill = true; + out->iter_empty = false; return out->next != NULL; } @@ -40241,9 +39941,10 @@ bool flecs_table_cache_empty_iter( { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = NULL; + out->next = cache->tables.first; out->cur = NULL; + out->iter_fill = false; + out->iter_empty = true; return out->next != NULL; } @@ -40253,26 +39954,36 @@ bool flecs_table_cache_all_iter( { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = cache->tables.first; + out->next = cache->tables.first; out->cur = NULL; - return out->next != NULL || out->next_list != NULL; + out->iter_fill = true; + out->iter_empty = true; + return out->next != NULL; } ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { - ecs_table_cache_hdr_t *next = it->next; - if (!next) { - next = it->next_list; - it->next_list = NULL; - if (!next) { - return NULL; + ecs_table_cache_hdr_t *next; + +repeat: + next = it->next; + it->cur = next; + + if (next) { + it->next = next->next; + + if (ecs_table_count(next->table)) { + if (!it->iter_fill) { + goto repeat; + } + } else { + if (!it->iter_empty) { + goto repeat; + } } } - it->cur = next; - it->next = next->next; return next; } @@ -40871,7 +40582,6 @@ ecs_table_t *flecs_table_new( /* Update counters */ world->info.table_count ++; - world->info.empty_table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); @@ -64530,7 +64240,6 @@ void ecs_world_stats_get( ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); - ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); @@ -68373,22 +68082,12 @@ int flecs_query_compile_term( int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache) { - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); return cache->cache.tables.count; } -int32_t flecs_query_cache_empty_table_count( - ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - return cache->cache.empty_tables.count; -} - int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - +{ int32_t result = 0; ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; if (!last) { @@ -68588,7 +68287,9 @@ void flecs_query_cache_create_group( /* This group should appear after another group */ ecs_query_cache_table_match_t *insert_before = insert_after->next; - match->prev = insert_after; + if (match != insert_after) { + match->prev = insert_after; + } insert_after->next = match; match->next = insert_before; if (insert_before) { @@ -68739,6 +68440,7 @@ void flecs_query_cache_insert_table_node( ecs_query_cache_table_list_t *list = flecs_query_cache_ensure_node_list(cache, match); + if (list->last) { ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); @@ -68847,11 +68549,7 @@ ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( qm->trs = flecs_balloc(&cache->allocators.trs); /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0 || - (cache->query->flags & EcsQueryCacheYieldEmptyTables)) - { - flecs_query_cache_insert_table_node(cache, qm); - } + flecs_query_cache_insert_table_node(cache, qm); return qm; } @@ -68927,11 +68625,7 @@ ecs_query_cache_table_t* flecs_query_cache_table_insert( qt->table_id = 0; } - if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { - ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); - } else { - ecs_table_cache_insert(&cache->cache, table, &qt->hdr); - } + ecs_table_cache_insert(&cache->cache, table, &qt->hdr); return qt; } @@ -68954,6 +68648,8 @@ void flecs_query_cache_match_tables( /* New table matched, add record to cache */ table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); + ecs_dbg_3("query cache matched existing table [%s]", + ecs_table_str(world, table)); } ecs_query_cache_table_match_t *qm = @@ -69107,43 +68803,6 @@ int flecs_query_cache_process_signature( return -1; } -/** When a table becomes empty remove it from the query list, or vice versa. */ -static -void flecs_query_cache_update_table( - ecs_query_cache_t *cache, - ecs_table_t *table, - bool empty) -{ - int32_t prev_count = flecs_query_cache_table_count(cache); - ecs_table_cache_set_empty(&cache->cache, table, empty); - int32_t cur_count = flecs_query_cache_table_count(cache); - - if (prev_count != cur_count) { - ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_cache_table_match_t *cur, *next; - - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; - - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_query_cache_remove_table_node(cache, cur); - } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_query_cache_insert_table_node(cache, cur); - } - } - } - - ecs_assert(cur_count || cache->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); -} - /* Remove table */ static void flecs_query_cache_table_match_free( @@ -69169,9 +68828,7 @@ void flecs_query_cache_table_match_free( flecs_bfree(&cache->allocators.monitors, cur->monitor); } - if (!elem->hdr.empty) { - flecs_query_cache_remove_table_node(cache, cur); - } + flecs_query_cache_remove_table_node(cache, cur); next = cur->next_match; @@ -69265,7 +68922,7 @@ void flecs_query_cache_rematch_tables( flecs_query_cache_set_table_match(cache, qm, &it); - if (table && ecs_table_count(table) && cache->group_by_callback) { + if (table && cache->group_by_callback) { if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { /* Update table group */ flecs_query_cache_remove_table_node(cache, qm); @@ -69456,12 +69113,7 @@ void flecs_query_cache_on_event( ecs_os_free(table_str); } - if (event == EcsOnTableEmpty) { - flecs_query_cache_update_table(cache, table, true); - } else - if (event == EcsOnTableFill) { - flecs_query_cache_update_table(cache, table, false); - } else if (event == EcsOnTableDelete) { + if (event == EcsOnTableDelete) { /* Deletion of table */ flecs_query_cache_unmatch_table(cache, table, NULL); return; @@ -69641,11 +69293,6 @@ ecs_query_cache_t* flecs_query_cache_init( observer_desc.ctx = impl; int32_t event_index = 0; - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - observer_desc.events[event_index ++] = EcsOnTableEmpty; - observer_desc.events[event_index ++] = EcsOnTableFill; - } - observer_desc.events[event_index ++] = EcsOnTableCreate; observer_desc.events[event_index ++] = EcsOnTableDelete; observer_desc.flags_ = EcsObserverBypassQuery; @@ -69696,11 +69343,6 @@ ecs_query_cache_t* flecs_query_cache_init( result->group_by_ctx_free = const_desc->group_by_ctx_free; } - /* Ensure that while initially populating the query with tables, they are - * in the right empty/non-empty list. This ensures the query won't miss - * empty/non-empty events for tables that are currently out of sync, but - * change back to being in sync before processing pending events. */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_init(world, &result->cache); flecs_query_cache_match_tables(world, result); @@ -69841,20 +69483,35 @@ void flecs_query_update_node_up_trs( static ecs_query_cache_table_match_t* flecs_query_cache_next( - const ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + bool match_empty) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_cache_table_match_t *node = qit->node; - ecs_query_cache_table_match_t *prev = qit->prev; - if (prev != qit->last) { - ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - ctx->vars[0].range.table = node->table; - it->group_id = node->group_id; - qit->node = node->next; - qit->prev = node; - return node; + repeat: { + ecs_query_cache_table_match_t *node = qit->node; + ecs_query_cache_table_match_t *prev = qit->prev; + + if (prev != qit->last) { + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ctx->vars[0].range.table = node->table; + it->group_id = node->group_id; + qit->node = node->next; + qit->prev = node; + if (node) { + if (!ecs_table_count(node->table)) { + if (!match_empty) { + if (ctx->query->pub.flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor( + flecs_query_impl(qit->query), node); + } + goto repeat; + } + } + } + return node; + } } return NULL; @@ -69884,7 +69541,7 @@ ecs_query_cache_table_match_t* flecs_query_test( qit->last = qt->last; } - return flecs_query_cache_next(ctx); + return flecs_query_cache_next(ctx, true); } static @@ -69917,7 +69574,8 @@ void flecs_query_cache_init_mapped_fields( bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, + ctx->query->pub.flags & EcsQueryMatchEmptyTables); if (!node) { return false; } @@ -69935,7 +69593,8 @@ bool flecs_query_cache_search( bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, + ctx->query->pub.flags & EcsQueryMatchEmptyTables); if (!node) { return false; } @@ -70144,7 +69803,9 @@ void flecs_query_cache_build_sorted_table_range( to_sort ++; } - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); + if (!to_sort) { + goto done; + } bool proceed; do { @@ -70202,9 +69863,12 @@ void flecs_query_cache_build_sorted_table_range( nodes[i].next = &nodes[i + 1]; } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; + if (nodes) { + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + } +done: flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } @@ -70531,7 +70195,7 @@ bool flecs_query_check_cache_monitor( } ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&cache->cache, &it)) { + if (flecs_table_cache_all_iter(&cache->cache, &it)) { ecs_query_cache_table_t *qt; while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { if (flecs_query_check_table_monitor(impl, qt, -1)) { @@ -70818,10 +70482,6 @@ bool ecs_query_changed( * cached/cacheable and don't have a fixed source, since that requires * storing state per result, which doesn't happen for uncached queries. */ if (impl->cache) { - /* If we're checking the cache, make sure that tables are in the correct - * empty/non-empty lists. */ - flecs_process_pending_tables(q->world); - if (!(impl->pub.flags & EcsQueryHasMonitor)) { flecs_query_init_query_monitors(impl); } @@ -72661,9 +72321,11 @@ ecs_iter_t flecs_query_iter( if (cache->order_by_callback && cache->list.info.table_count) { flecs_query_cache_sort_tables(it.real_world, impl); - qit->node = ecs_vec_first(&cache->table_slices); - qit->last = ecs_vec_last_t( - &cache->table_slices, ecs_query_cache_table_match_t); + if (ecs_vec_count(&cache->table_slices)) { + qit->node = ecs_vec_first(&cache->table_slices); + qit->last = ecs_vec_last_t( + &cache->table_slices, ecs_query_cache_table_match_t); + } } cache->prev_match_count = cache->match_count; @@ -72703,10 +72365,6 @@ ecs_iter_t ecs_query_iter( { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); - } /* Ok, only for stats */ ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); @@ -74312,7 +73970,7 @@ bool flecs_query_up_select( /* If id record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + !flecs_table_cache_count(&op_ctx->idr_trav->cache)) { if (!self) { /* If operation does not match owned components, return false */ @@ -74443,7 +74101,7 @@ bool flecs_query_up_with( } if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + !flecs_table_cache_count(&op_ctx->idr_trav->cache)) { /* If there are no tables with traversable relationship, there are no * matches. */ diff --git a/distr/flecs.h b/distr/flecs.h index a15987eaf..6d15de6a8 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -561,7 +561,6 @@ extern "C" { //// Aperiodic action flags (used by ecs_run_aperiodic) //////////////////////////////////////////////////////////////////////////////// -#define EcsAperiodicEmptyTables (1u << 1u) /* Process pending empty table events */ #define EcsAperiodicComponentMonitors (1u << 2u) /* Process component monitors */ #define EcsAperiodicEmptyQueries (1u << 4u) /* Process empty queries */ @@ -3208,7 +3207,6 @@ typedef struct ecs_table_cache_hdr_t { struct ecs_table_cache_t *cache; /**< Table cache of element. Of type ecs_id_record_t* for component index elements. */ ecs_table_t *table; /**< Table associated with element. */ struct ecs_table_cache_hdr_t *prev, *next; /**< Next/previous elements for id in table cache. */ - bool empty; /**< Whether element is in empty list. */ } ecs_table_cache_hdr_t; /** Metadata describing where a component id is stored in a table. @@ -3758,7 +3756,8 @@ typedef struct ecs_worker_iter_t { /* Convenience struct to iterate table array for id */ typedef struct ecs_table_cache_iter_t { struct ecs_table_cache_hdr_t *cur, *next; - struct ecs_table_cache_hdr_t *next_list; + bool iter_fill; + bool iter_empty; } ecs_table_cache_iter_t; /** Each iterator */ @@ -4674,7 +4673,6 @@ typedef struct ecs_world_info_t { int32_t pair_id_count; /**< Number of pair ids in the world */ int32_t table_count; /**< Number of tables */ - int32_t empty_table_count; /**< Number of tables without entities */ /* -- Command counts -- */ struct { @@ -5051,12 +5049,6 @@ FLECS_API extern const ecs_entity_t EcsOnTableCreate; /** Event that triggers when a table is deleted. */ FLECS_API extern const ecs_entity_t EcsOnTableDelete; -/** Event that triggers when a table becomes empty (doesn't emit on creation). */ -FLECS_API extern const ecs_entity_t EcsOnTableEmpty; - -/** Event that triggers when a table becomes non-empty. */ -FLECS_API extern const ecs_entity_t EcsOnTableFill; - /** Relationship used for specifying cleanup behavior. */ FLECS_API extern const ecs_entity_t EcsOnDelete; diff --git a/include/flecs.h b/include/flecs.h index a7c31f83c..18da32b8a 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -502,7 +502,6 @@ typedef struct ecs_table_cache_hdr_t { struct ecs_table_cache_t *cache; /**< Table cache of element. Of type ecs_id_record_t* for component index elements. */ ecs_table_t *table; /**< Table associated with element. */ struct ecs_table_cache_hdr_t *prev, *next; /**< Next/previous elements for id in table cache. */ - bool empty; /**< Whether element is in empty list. */ } ecs_table_cache_hdr_t; /** Metadata describing where a component id is stored in a table. @@ -1435,7 +1434,6 @@ typedef struct ecs_world_info_t { int32_t pair_id_count; /**< Number of pair ids in the world */ int32_t table_count; /**< Number of tables */ - int32_t empty_table_count; /**< Number of tables without entities */ /* -- Command counts -- */ struct { @@ -1791,12 +1789,6 @@ FLECS_API extern const ecs_entity_t EcsOnTableCreate; /** Event that triggers when a table is deleted. */ FLECS_API extern const ecs_entity_t EcsOnTableDelete; -/** Event that triggers when a table becomes empty (doesn't emit on creation). */ -FLECS_API extern const ecs_entity_t EcsOnTableEmpty; - -/** Event that triggers when a table becomes non-empty. */ -FLECS_API extern const ecs_entity_t EcsOnTableFill; - /** Relationship used for specifying cleanup behavior. */ FLECS_API extern const ecs_entity_t EcsOnDelete; diff --git a/include/flecs/private/api_flags.h b/include/flecs/private/api_flags.h index be05493f8..f95942db3 100644 --- a/include/flecs/private/api_flags.h +++ b/include/flecs/private/api_flags.h @@ -240,7 +240,6 @@ extern "C" { //// Aperiodic action flags (used by ecs_run_aperiodic) //////////////////////////////////////////////////////////////////////////////// -#define EcsAperiodicEmptyTables (1u << 1u) /* Process pending empty table events */ #define EcsAperiodicComponentMonitors (1u << 2u) /* Process component monitors */ #define EcsAperiodicEmptyQueries (1u << 4u) /* Process empty queries */ diff --git a/include/flecs/private/api_types.h b/include/flecs/private/api_types.h index 70473620a..c7bdf5d88 100644 --- a/include/flecs/private/api_types.h +++ b/include/flecs/private/api_types.h @@ -92,7 +92,8 @@ typedef struct ecs_worker_iter_t { /* Convenience struct to iterate table array for id */ typedef struct ecs_table_cache_iter_t { struct ecs_table_cache_hdr_t *cur, *next; - struct ecs_table_cache_hdr_t *next_list; + bool iter_fill; + bool iter_empty; } ecs_table_cache_iter_t; /** Each iterator */ diff --git a/src/addons/doc.c b/src/addons/doc.c index 2f7bff277..9113f944e 100644 --- a/src/addons/doc.c +++ b/src/addons/doc.c @@ -218,8 +218,6 @@ void flecs_doc_import_core_definitions( ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); - ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); - ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); diff --git a/src/addons/journal.c b/src/addons/journal.c index 6733b918c..369ea16c4 100644 --- a/src/addons/journal.c +++ b/src/addons/journal.c @@ -118,9 +118,6 @@ void flecs_journal_begin( } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); - } else if (kind == EcsJournalTableEvents) { - ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " - "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); diff --git a/src/addons/stats/stats.c b/src/addons/stats/stats.c index 0f01c3e72..990ce995c 100644 --- a/src/addons/stats/stats.c +++ b/src/addons/stats/stats.c @@ -290,7 +290,6 @@ void ecs_world_stats_get( ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); - ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); diff --git a/src/bootstrap.c b/src/bootstrap.c index ee45ac19a..267cd043c 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -877,8 +877,6 @@ void flecs_bootstrap( flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); - flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); - flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); diff --git a/src/each.c b/src/each.c index 478764af9..22a61e08a 100644 --- a/src/each.c +++ b/src/each.c @@ -14,8 +14,6 @@ ecs_iter_t ecs_each_id( const ecs_world_t *world = ecs_get_world(stage); - flecs_process_pending_tables(world); - ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), diff --git a/src/entity.c b/src/entity.c index 11113d7d1..a7f9794cb 100644 --- a/src/entity.c +++ b/src/entity.c @@ -2818,9 +2818,6 @@ void flecs_on_delete( * frame will handle the actual cleanup. */ int32_t i, count = ecs_vec_count(&world->store.marked_ids); - /* Make sure we're evaluating a consistent list of non-empty tables */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); @@ -2832,9 +2829,6 @@ void flecs_on_delete( /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); - /* All marked tables are empty, ensure they're in the right list */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); @@ -3498,7 +3492,7 @@ void flecs_modified_id_if( ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; - if (!flecs_table_record_get(world, table, id)) { + if (!table || !flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } diff --git a/src/observer.c b/src/observer.c index f1ec2893c..d8dff0fbd 100644 --- a/src/observer.c +++ b/src/observer.c @@ -41,12 +41,6 @@ ecs_flags32_t flecs_id_flag_for_event( if (e == EcsOnSet) { return EcsIdHasOnSet; } - if (e == EcsOnTableFill) { - return EcsIdHasOnTableFill; - } - if (e == EcsOnTableEmpty) { - return EcsIdHasOnTableEmpty; - } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } @@ -638,8 +632,6 @@ void flecs_observer_yield_existing( run = flecs_multi_observer_invoke_no_query; } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - ecs_defer_begin(world); /* If yield existing is enabled, invoke for each thing that matches @@ -808,9 +800,7 @@ int flecs_multi_observer_init( bool only_table_events = true; for (i = 0; i < o->event_count; i ++) { ecs_entity_t e = o->events[i]; - if (e != EcsOnTableCreate && e != EcsOnTableDelete && - e != EcsOnTableEmpty && e != EcsOnTableFill) - { + if (e != EcsOnTableCreate && e != EcsOnTableDelete) { only_table_events = false; break; } diff --git a/src/private_types.h b/src/private_types.h index 2dad2903a..b1c246fba 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -84,7 +84,6 @@ typedef struct ecs_table_cache_list_t { typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; - ecs_table_cache_list_t empty_tables; } ecs_table_cache_t; /* World level allocators are for operations that are not multithreaded */ @@ -322,10 +321,6 @@ struct ecs_world_t { /* -- Data storage -- */ ecs_store_t store; - /* -- Pending table event buffers -- */ - ecs_sparse_t *pending_buffer; /* sparse */ - ecs_sparse_t *pending_tables; /* sparse */ - /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ diff --git a/src/query/api.c b/src/query/api.c index 6cfa24aac..48e6ab647 100644 --- a/src/query/api.c +++ b/src/query/api.c @@ -480,23 +480,13 @@ ecs_query_count_t ecs_query_count( return result; } - ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); + ecs_iter_t it = flecs_query_iter(q->world, q); + it.flags |= EcsIterNoData; - ecs_query_impl_t *impl = flecs_query_impl(q); - if (impl->cache && q->flags & EcsQueryIsCacheable) { - result.results = flecs_query_cache_table_count(impl->cache); - result.entities = flecs_query_cache_entity_count(impl->cache); - result.tables = flecs_query_cache_table_count(impl->cache); - result.empty_tables = flecs_query_cache_empty_table_count(impl->cache); - } else { - ecs_iter_t it = flecs_query_iter(q->world, q); - it.flags |= EcsIterNoData; - - while (ecs_query_next(&it)) { - result.results ++; - result.entities += it.count; - ecs_iter_skip(&it); - } + while (ecs_query_next(&it)) { + result.results ++; + result.entities += it.count; + ecs_iter_skip(&it); } return result; @@ -507,15 +497,8 @@ bool ecs_query_is_true( { flecs_poly_assert(q, ecs_query_t); - ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); - - ecs_query_impl_t *impl = flecs_query_impl(q); - if (impl->cache && q->flags & EcsQueryIsCacheable) { - return flecs_query_cache_table_count(impl->cache) != 0; - } else { - ecs_iter_t it = flecs_query_iter(q->world, q); - return ecs_iter_is_true(&it); - } + ecs_iter_t it = flecs_query_iter(q->world, q); + return ecs_iter_is_true(&it); } int32_t ecs_query_match_count( diff --git a/src/query/engine/cache.c b/src/query/engine/cache.c index 4d1b34edc..b53a57282 100644 --- a/src/query/engine/cache.c +++ b/src/query/engine/cache.c @@ -8,22 +8,12 @@ int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache) { - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); return cache->cache.tables.count; } -int32_t flecs_query_cache_empty_table_count( - ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - return cache->cache.empty_tables.count; -} - int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - +{ int32_t result = 0; ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; if (!last) { @@ -223,7 +213,9 @@ void flecs_query_cache_create_group( /* This group should appear after another group */ ecs_query_cache_table_match_t *insert_before = insert_after->next; - match->prev = insert_after; + if (match != insert_after) { + match->prev = insert_after; + } insert_after->next = match; match->next = insert_before; if (insert_before) { @@ -374,6 +366,7 @@ void flecs_query_cache_insert_table_node( ecs_query_cache_table_list_t *list = flecs_query_cache_ensure_node_list(cache, match); + if (list->last) { ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); @@ -482,11 +475,7 @@ ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( qm->trs = flecs_balloc(&cache->allocators.trs); /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0 || - (cache->query->flags & EcsQueryCacheYieldEmptyTables)) - { - flecs_query_cache_insert_table_node(cache, qm); - } + flecs_query_cache_insert_table_node(cache, qm); return qm; } @@ -562,11 +551,7 @@ ecs_query_cache_table_t* flecs_query_cache_table_insert( qt->table_id = 0; } - if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { - ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); - } else { - ecs_table_cache_insert(&cache->cache, table, &qt->hdr); - } + ecs_table_cache_insert(&cache->cache, table, &qt->hdr); return qt; } @@ -589,6 +574,8 @@ void flecs_query_cache_match_tables( /* New table matched, add record to cache */ table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); + ecs_dbg_3("query cache matched existing table [%s]", + ecs_table_str(world, table)); } ecs_query_cache_table_match_t *qm = @@ -742,43 +729,6 @@ int flecs_query_cache_process_signature( return -1; } -/** When a table becomes empty remove it from the query list, or vice versa. */ -static -void flecs_query_cache_update_table( - ecs_query_cache_t *cache, - ecs_table_t *table, - bool empty) -{ - int32_t prev_count = flecs_query_cache_table_count(cache); - ecs_table_cache_set_empty(&cache->cache, table, empty); - int32_t cur_count = flecs_query_cache_table_count(cache); - - if (prev_count != cur_count) { - ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_cache_table_match_t *cur, *next; - - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; - - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_query_cache_remove_table_node(cache, cur); - } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_query_cache_insert_table_node(cache, cur); - } - } - } - - ecs_assert(cur_count || cache->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); -} - /* Remove table */ static void flecs_query_cache_table_match_free( @@ -804,9 +754,7 @@ void flecs_query_cache_table_match_free( flecs_bfree(&cache->allocators.monitors, cur->monitor); } - if (!elem->hdr.empty) { - flecs_query_cache_remove_table_node(cache, cur); - } + flecs_query_cache_remove_table_node(cache, cur); next = cur->next_match; @@ -900,7 +848,7 @@ void flecs_query_cache_rematch_tables( flecs_query_cache_set_table_match(cache, qm, &it); - if (table && ecs_table_count(table) && cache->group_by_callback) { + if (table && cache->group_by_callback) { if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { /* Update table group */ flecs_query_cache_remove_table_node(cache, qm); @@ -1091,12 +1039,7 @@ void flecs_query_cache_on_event( ecs_os_free(table_str); } - if (event == EcsOnTableEmpty) { - flecs_query_cache_update_table(cache, table, true); - } else - if (event == EcsOnTableFill) { - flecs_query_cache_update_table(cache, table, false); - } else if (event == EcsOnTableDelete) { + if (event == EcsOnTableDelete) { /* Deletion of table */ flecs_query_cache_unmatch_table(cache, table, NULL); return; @@ -1276,11 +1219,6 @@ ecs_query_cache_t* flecs_query_cache_init( observer_desc.ctx = impl; int32_t event_index = 0; - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - observer_desc.events[event_index ++] = EcsOnTableEmpty; - observer_desc.events[event_index ++] = EcsOnTableFill; - } - observer_desc.events[event_index ++] = EcsOnTableCreate; observer_desc.events[event_index ++] = EcsOnTableDelete; observer_desc.flags_ = EcsObserverBypassQuery; @@ -1331,11 +1269,6 @@ ecs_query_cache_t* flecs_query_cache_init( result->group_by_ctx_free = const_desc->group_by_ctx_free; } - /* Ensure that while initially populating the query with tables, they are - * in the right empty/non-empty list. This ensures the query won't miss - * empty/non-empty events for tables that are currently out of sync, but - * change back to being in sync before processing pending events. */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_init(world, &result->cache); flecs_query_cache_match_tables(world, result); diff --git a/src/query/engine/cache.h b/src/query/engine/cache.h index 4da4ec932..49f359821 100644 --- a/src/query/engine/cache.h +++ b/src/query/engine/cache.h @@ -37,10 +37,6 @@ void flecs_query_cache_build_sorted_tables( int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache); -/* Return number of empty tables in cache */ -int32_t flecs_query_cache_empty_table_count( - ecs_query_cache_t *cache); - /* Return number of entities in cache (requires iterating tables) */ int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache); diff --git a/src/query/engine/cache_iter.c b/src/query/engine/cache_iter.c index 18b479bc3..d712738d7 100644 --- a/src/query/engine/cache_iter.c +++ b/src/query/engine/cache_iter.c @@ -42,20 +42,35 @@ void flecs_query_update_node_up_trs( static ecs_query_cache_table_match_t* flecs_query_cache_next( - const ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + bool match_empty) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_cache_table_match_t *node = qit->node; - ecs_query_cache_table_match_t *prev = qit->prev; - - if (prev != qit->last) { - ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - ctx->vars[0].range.table = node->table; - it->group_id = node->group_id; - qit->node = node->next; - qit->prev = node; - return node; + + repeat: { + ecs_query_cache_table_match_t *node = qit->node; + ecs_query_cache_table_match_t *prev = qit->prev; + + if (prev != qit->last) { + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ctx->vars[0].range.table = node->table; + it->group_id = node->group_id; + qit->node = node->next; + qit->prev = node; + if (node) { + if (!ecs_table_count(node->table)) { + if (!match_empty) { + if (ctx->query->pub.flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor( + flecs_query_impl(qit->query), node); + } + goto repeat; + } + } + } + return node; + } } return NULL; @@ -85,7 +100,7 @@ ecs_query_cache_table_match_t* flecs_query_test( qit->last = qt->last; } - return flecs_query_cache_next(ctx); + return flecs_query_cache_next(ctx, true); } static @@ -118,7 +133,8 @@ void flecs_query_cache_init_mapped_fields( bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, + ctx->query->pub.flags & EcsQueryMatchEmptyTables); if (!node) { return false; } @@ -136,7 +152,8 @@ bool flecs_query_cache_search( bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, + ctx->query->pub.flags & EcsQueryMatchEmptyTables); if (!node) { return false; } diff --git a/src/query/engine/cache_order_by.c b/src/query/engine/cache_order_by.c index 67e929edb..275ddfb2f 100644 --- a/src/query/engine/cache_order_by.c +++ b/src/query/engine/cache_order_by.c @@ -155,7 +155,9 @@ void flecs_query_cache_build_sorted_table_range( to_sort ++; } - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); + if (!to_sort) { + goto done; + } bool proceed; do { @@ -213,9 +215,12 @@ void flecs_query_cache_build_sorted_table_range( nodes[i].next = &nodes[i + 1]; } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; + if (nodes) { + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + } +done: flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } diff --git a/src/query/engine/change_detection.c b/src/query/engine/change_detection.c index 6a4233e38..31a84aaa7 100644 --- a/src/query/engine/change_detection.c +++ b/src/query/engine/change_detection.c @@ -222,7 +222,7 @@ bool flecs_query_check_cache_monitor( } ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&cache->cache, &it)) { + if (flecs_table_cache_all_iter(&cache->cache, &it)) { ecs_query_cache_table_t *qt; while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { if (flecs_query_check_table_monitor(impl, qt, -1)) { @@ -509,10 +509,6 @@ bool ecs_query_changed( * cached/cacheable and don't have a fixed source, since that requires * storing state per result, which doesn't happen for uncached queries. */ if (impl->cache) { - /* If we're checking the cache, make sure that tables are in the correct - * empty/non-empty lists. */ - flecs_process_pending_tables(q->world); - if (!(impl->pub.flags & EcsQueryHasMonitor)) { flecs_query_init_query_monitors(impl); } diff --git a/src/query/engine/eval_iter.c b/src/query/engine/eval_iter.c index 7e2be7789..660ca9b4c 100644 --- a/src/query/engine/eval_iter.c +++ b/src/query/engine/eval_iter.c @@ -324,9 +324,11 @@ ecs_iter_t flecs_query_iter( if (cache->order_by_callback && cache->list.info.table_count) { flecs_query_cache_sort_tables(it.real_world, impl); - qit->node = ecs_vec_first(&cache->table_slices); - qit->last = ecs_vec_last_t( - &cache->table_slices, ecs_query_cache_table_match_t); + if (ecs_vec_count(&cache->table_slices)) { + qit->node = ecs_vec_first(&cache->table_slices); + qit->last = ecs_vec_last_t( + &cache->table_slices, ecs_query_cache_table_match_t); + } } cache->prev_match_count = cache->match_count; @@ -366,10 +368,6 @@ ecs_iter_t ecs_query_iter( { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); - } /* Ok, only for stats */ ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); diff --git a/src/query/engine/eval_up.c b/src/query/engine/eval_up.c index 571be94c0..cfa23e828 100644 --- a/src/query/engine/eval_up.c +++ b/src/query/engine/eval_up.c @@ -135,7 +135,7 @@ bool flecs_query_up_select( /* If id record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + !flecs_table_cache_count(&op_ctx->idr_trav->cache)) { if (!self) { /* If operation does not match owned components, return false */ @@ -266,7 +266,7 @@ bool flecs_query_up_with( } if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + !flecs_table_cache_count(&op_ctx->idr_trav->cache)) { /* If there are no tables with traversable relationship, there are no * matches. */ diff --git a/src/stage.c b/src/stage.c index 886384716..2a7c23971 100644 --- a/src/stage.c +++ b/src/stage.c @@ -811,8 +811,6 @@ bool ecs_readonly_begin( { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); - ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); diff --git a/src/storage/id_index.c b/src/storage/id_index.c index 841aabfb2..01d36421d 100644 --- a/src/storage/id_index.c +++ b/src/storage/id_index.c @@ -179,8 +179,6 @@ ecs_flags32_t flecs_id_record_event_flags( result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; - result |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; - result |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; return result; @@ -400,8 +398,6 @@ void flecs_id_record_assert_empty( (void)idr; ecs_assert(flecs_table_cache_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); } static diff --git a/src/storage/table.c b/src/storage/table.c index 46af419c9..1f9df1665 100644 --- a/src/storage/table.c +++ b/src/storage/table.c @@ -642,13 +642,12 @@ void flecs_table_add_trigger_flags( flags = EcsTableHasOnRemove; } else if (event == EcsOnSet) { flags = EcsTableHasOnSet; - } else if (event == EcsOnTableFill) { - flags = EcsTableHasOnTableFill; - } else if (event == EcsOnTableEmpty) { - flags = EcsTableHasOnTableEmpty; + } else if (event == EcsOnTableCreate) { + flags = EcsTableHasOnTableCreate; + } else if (event == EcsOnTableDelete) { + flags = EcsTableHasOnTableDelete; } else if (event == EcsWildcard) { flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| - EcsTableHasOnTableFill|EcsTableHasOnTableEmpty| EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; } @@ -946,11 +945,6 @@ void flecs_table_fini_data( } table->data.count = 0; - - if (deactivate && count) { - flecs_table_set_empty(world, table); - } - table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } @@ -1034,8 +1028,6 @@ void flecs_table_fini( ecs_log_push_2(); } - world->info.empty_table_count -= (ecs_table_count(table) == 0); - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ flecs_table_fini_data(world, table, false, true, false, true); flecs_table_clear_edges(world, table); @@ -1391,10 +1383,6 @@ int32_t flecs_table_grow_data( /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); - if (!(world->flags & EcsWorldReadonly) && !count) { - flecs_table_set_empty(world, table); - } - /* Return index of first added entity */ return count; } @@ -1454,9 +1442,6 @@ int32_t flecs_table_append( flecs_table_fast_append(world, table); table->data.count = v_entities.count; table->data.size = v_entities.size; - if (!count) { - flecs_table_set_empty(world, table); /* See below */ - } return count; } @@ -1468,12 +1453,6 @@ int32_t flecs_table_append( table->data.count = v_entities.count; table->data.size = v_entities.size; - /* If this is the first entity in this table, signal queries so that the - * table moves from an inactive table to an active table. */ - if (!count) { - flecs_table_set_empty(world, table); - } - /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ @@ -1583,9 +1562,6 @@ void flecs_table_delete( } table->data.count --; - if (!count) { - flecs_table_set_empty(world, table); - } flecs_table_check_sanity(world, table); return; @@ -1649,9 +1625,6 @@ void flecs_table_delete( } table->data.count --; - if (!count) { - flecs_table_set_empty(world, table); - } flecs_table_check_sanity(world, table); } @@ -2175,11 +2148,6 @@ void flecs_table_merge( flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); if (src_count) { - if (!dst_count) { - flecs_table_set_empty(world, dst_table); - } - flecs_table_set_empty(world, src_table); - flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); diff --git a/src/storage/table_cache.c b/src/storage/table_cache.c index ec4a82cbc..14a521306 100644 --- a/src/storage/table_cache.c +++ b/src/storage/table_cache.c @@ -31,20 +31,19 @@ void flecs_table_cache_list_remove( prev->next = next; } - cache->empty_tables.count -= !!elem->empty; - cache->tables.count -= !elem->empty; + cache->tables.count --; - if (cache->empty_tables.first == elem) { - cache->empty_tables.first = next; - } else if (cache->tables.first == elem) { + if (cache->tables.first == elem) { cache->tables.first = next; } - if (cache->empty_tables.last == elem) { - cache->empty_tables.last = prev; - } if (cache->tables.last == elem) { cache->tables.last = prev; } + + ecs_assert(cache->tables.first == NULL || cache->tables.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, + ECS_INTERNAL_ERROR, NULL); } static @@ -52,19 +51,10 @@ void flecs_table_cache_list_insert( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { - ecs_table_cache_hdr_t *last; - if (elem->empty) { - last = cache->empty_tables.last; - cache->empty_tables.last = elem; - if ((++ cache->empty_tables.count) == 1) { - cache->empty_tables.first = elem; - } - } else { - last = cache->tables.last; - cache->tables.last = elem; - if ((++ cache->tables.count) == 1) { - cache->tables.first = elem; - } + ecs_table_cache_hdr_t *last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; } elem->next = NULL; @@ -73,6 +63,10 @@ void flecs_table_cache_list_insert( if (last) { last->next = elem; } + + ecs_assert( + cache->tables.count != 1 || cache->tables.first == cache->tables.last, + ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_init( @@ -90,17 +84,10 @@ void ecs_table_cache_fini( ecs_map_fini(&cache->index); } -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) -{ - return ecs_map_count(&cache->index) == 0; -} - -void ecs_table_cache_insert_w_empty( +void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, - ecs_table_cache_hdr_t *result, - bool empty) + ecs_table_cache_hdr_t *result) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_cache_get(cache, table) == NULL, @@ -109,7 +96,6 @@ void ecs_table_cache_insert_w_empty( result->cache = cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); - result->empty = empty; flecs_table_cache_list_insert(cache, result); @@ -117,25 +103,7 @@ void ecs_table_cache_insert_w_empty( ecs_map_insert_ptr(&cache->index, table->id, result); } - ecs_assert(empty || cache->tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!empty || cache->empty_tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); -} - -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result) -{ - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; - } - - ecs_table_cache_insert_w_empty(cache, table, result, empty); + ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_replace( @@ -160,12 +128,6 @@ void ecs_table_cache_replace( next->prev = elem; } - if (cache->empty_tables.first == old) { - cache->empty_tables.first = elem; - } - if (cache->empty_tables.last == old) { - cache->empty_tables.last = elem; - } if (cache->tables.first == old) { cache->tables.first = elem; } @@ -217,24 +179,7 @@ bool ecs_table_cache_set_empty( const ecs_table_t *table, bool empty) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, - ecs_table_cache_hdr_t, table->id); - if (!elem) { - return false; - } - - if (elem->empty == empty) { - return false; - } - - flecs_table_cache_list_remove(cache, elem); - elem->empty = empty; - flecs_table_cache_list_insert(cache, elem); - - return true; + return false; } bool flecs_table_cache_iter( @@ -244,8 +189,9 @@ bool flecs_table_cache_iter( ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->tables.first; - out->next_list = NULL; out->cur = NULL; + out->iter_fill = true; + out->iter_empty = false; return out->next != NULL; } @@ -255,9 +201,10 @@ bool flecs_table_cache_empty_iter( { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = NULL; + out->next = cache->tables.first; out->cur = NULL; + out->iter_fill = false; + out->iter_empty = true; return out->next != NULL; } @@ -267,25 +214,35 @@ bool flecs_table_cache_all_iter( { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = cache->tables.first; + out->next = cache->tables.first; out->cur = NULL; - return out->next != NULL || out->next_list != NULL; + out->iter_fill = true; + out->iter_empty = true; + return out->next != NULL; } ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { - ecs_table_cache_hdr_t *next = it->next; - if (!next) { - next = it->next_list; - it->next_list = NULL; - if (!next) { - return NULL; + ecs_table_cache_hdr_t *next; + +repeat: + next = it->next; + it->cur = next; + + if (next) { + it->next = next->next; + + if (ecs_table_count(next->table)) { + if (!it->iter_fill) { + goto repeat; + } + } else { + if (!it->iter_empty) { + goto repeat; + } } } - it->cur = next; - it->next = next->next; return next; } diff --git a/src/storage/table_cache.h b/src/storage/table_cache.h index 1020d8a6b..2f01259dc 100644 --- a/src/storage/table_cache.h +++ b/src/storage/table_cache.h @@ -18,12 +18,6 @@ void ecs_table_cache_insert( const ecs_table_t *table, ecs_table_cache_hdr_t *result); -void ecs_table_cache_insert_w_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result, - bool is_empty); - void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, @@ -43,12 +37,7 @@ bool ecs_table_cache_set_empty( const ecs_table_t *table, bool empty); -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache); - #define flecs_table_cache_count(cache) (cache)->tables.count -#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count -#define flecs_table_cache_all_count(cache) ((cache)->tables.count + (cache)->empty_tables.count) bool flecs_table_cache_iter( ecs_table_cache_t *cache, diff --git a/src/storage/table_graph.c b/src/storage/table_graph.c index a55602451..be4a9dbca 100644 --- a/src/storage/table_graph.c +++ b/src/storage/table_graph.c @@ -594,7 +594,6 @@ ecs_table_t *flecs_table_new( /* Update counters */ world->info.table_count ++; - world->info.empty_table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); diff --git a/src/world.c b/src/world.c index 566f2294e..7dfa9754a 100644 --- a/src/world.c +++ b/src/world.c @@ -79,8 +79,6 @@ const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; -const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 47; -const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 48; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; @@ -713,8 +711,6 @@ void flecs_fini_roots( { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, @@ -972,13 +968,6 @@ ecs_world_t *ecs_mini(void) { ecs_id_record_t*, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); - world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); - flecs_sparse_init_t(world->pending_tables, a, - &world->allocators.sparse_chunk, ecs_table_t*); - world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); - flecs_sparse_init_t(world->pending_buffer, a, - &world->allocators.sparse_chunk, ecs_table_t*); - flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); @@ -1648,10 +1637,6 @@ int ecs_fini( ecs_dbg_1("#[bold]cleanup world data structures"); ecs_log_push_1(); flecs_entities_fini(world); - flecs_sparse_fini(world->pending_tables); - flecs_sparse_fini(world->pending_buffer); - ecs_os_free(world->pending_tables); - ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); @@ -1693,7 +1678,6 @@ void flecs_eval_component_monitors( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } @@ -1740,7 +1724,6 @@ void ecs_set_default_query_flags( ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); world->default_query_flags = flags; } @@ -2168,8 +2151,6 @@ void flecs_process_empty_queries( return; } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, world->stages[0]); @@ -2195,119 +2176,6 @@ void flecs_process_empty_queries( flecs_defer_end(world, world->stages[0]); } -/** Walk over tables that had a state change which requires bookkeeping */ -void flecs_process_pending_tables( - const ecs_world_t *world_r) -{ - flecs_poly_assert(world_r, ecs_world_t); - - /* We can't update the administration while in readonly mode, but we can - * ensure that when this function is called there are no pending events. */ - if (world_r->flags & EcsWorldReadonly) { - ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - return; - } - - /* Safe to cast, world is not readonly */ - ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); - - /* If pending buffer is NULL there already is a stackframe that's iterating - * the table list. This can happen when an observer for a table event results - * in a mutation that causes another table to change state. A typical - * example of this is a system that becomes active/inactive as the result of - * a query (and as a result, its matched tables) becoming empty/non empty */ - if (!world->pending_buffer) { - return; - } - - /* Swap buffer. The logic could in theory have been implemented with a - * single sparse set, but that would've complicated (and slowed down) the - * iteration. Additionally, by using a double buffer approach we can still - * keep most of the original ordering of events intact, which is desirable - * as it means that the ordering of tables in the internal data structures is - * more predictable. */ - int32_t i, count = flecs_sparse_count(world->pending_tables); - if (!count) { - return; - } - - ecs_os_perf_trace_push("flecs.process_pending_tables"); - - flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); - - do { - ecs_sparse_t *pending_tables = world->pending_tables; - world->pending_tables = world->pending_buffer; - world->pending_buffer = NULL; - - /* Make sure that any ECS operations that occur while delivering the - * events does not cause inconsistencies, like sending an Empty - * notification for a table that just became non-empty. */ - flecs_defer_begin(world, world->stages[0]); - - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense_t( - pending_tables, ecs_table_t*, i)[0]; - if (!table->id) { - /* Table is being deleted, ignore empty events */ - continue; - } - - /* For each id in the table, add it to the empty/non empty list - * based on its current state */ - if (flecs_table_records_update_empty(table)) { - int32_t table_count = ecs_table_count(table); - if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { - /* Only emit an event when there was a change in the - * administration. It is possible that a table ended up in the - * pending_tables list by going from empty->non-empty, but then - * became empty again. By the time we run this code, no changes - * in the administration would actually be made. */ - ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; - if (ecs_should_log_3()) { - ecs_dbg_3("table %u state change (%s)", - (uint32_t)table->id, - table_count ? "non-empty" : "empty"); - } - - ecs_log_push_3(); - - flecs_table_emit(world, table, evt); - - ecs_log_pop_3(); - } - world->info.empty_table_count += (table_count == 0) * 2 - 1; - } - } - - flecs_sparse_clear(pending_tables); - ecs_defer_end(world); - - world->pending_buffer = pending_tables; - } while ((count = flecs_sparse_count(world->pending_tables))); - - flecs_journal_end(); - - ecs_os_perf_trace_pop("flecs.process_pending_tables"); -} - -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_table_count(table)) { - table->_->generation = 0; - } - - flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, - (uint32_t)table->id)[0] = table; -} - bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) @@ -2316,8 +2184,8 @@ bool ecs_id_in_use( if (!idr) { return false; } - return (flecs_table_cache_count(&idr->cache) != 0) || - (flecs_table_cache_empty_count(&idr->cache) != 0); + + return (flecs_table_cache_count(&idr->cache) != 0); } void ecs_run_aperiodic( @@ -2325,10 +2193,6 @@ void ecs_run_aperiodic( ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); - - if (!flags || (flags & EcsAperiodicEmptyTables)) { - flecs_process_pending_tables(world); - } if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); @@ -2351,9 +2215,6 @@ int32_t ecs_delete_empty_tables( ecs_os_perf_trace_push("flecs.delete_empty_tables"); - /* Make sure empty tables are in the empty table lists */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0; bool time_budget = false; diff --git a/src/world.h b/src/world.h index fe183ba20..e4f9e9181 100644 --- a/src/world.h +++ b/src/world.h @@ -72,17 +72,10 @@ void flecs_unregister_table( ecs_world_t *world, ecs_table_t *table); -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table); - void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); -void flecs_process_pending_tables( - const ecs_world_t *world); - /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from diff --git a/test/core/project.json b/test/core/project.json index a40a8a666..33a32d10d 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -1640,7 +1640,6 @@ "notify_after_defer_batched", "notify_after_defer_batched_2_entities_in_table", "notify_after_defer_batched_2_entities_in_table_w_tgt", - "multi_observer_table_fill_w_singleton", "wildcard_propagate_w_other_table", "add_in_on_add_yield_existing", "add_in_on_add_yield_existing_multi", @@ -2075,7 +2074,6 @@ "testcases": [ "get_tick", "table_count", - "empty_table_count", "table_create_count", "table_delete_count", "id_tag_component_count", diff --git a/test/core/src/Entity.c b/test/core/src/Entity.c index ddcc899d4..4f67050bb 100644 --- a/test/core/src/Entity.c +++ b/test/core/src/Entity.c @@ -3109,6 +3109,7 @@ void Entity_entity_w_existing_id_and_double_dot(void) { void Entity_entity_w_large_id_name(void) { ecs_world_t *world = ecs_mini(); + ecs_log_set_level(-4); ecs_entity_t e = ecs_entity(world, { .name = "#44444444444444444444a" }); diff --git a/test/core/src/Observer.c b/test/core/src/Observer.c index 7146be88b..f223a6bc5 100644 --- a/test/core/src/Observer.c +++ b/test/core/src/Observer.c @@ -8824,37 +8824,6 @@ void Observer_notify_after_defer_batched_2_entities_in_table_w_tgt(void) { ecs_fini(world); } -void Observer_multi_observer_table_fill_w_singleton(void) { - ecs_world_t* world = ecs_mini(); - - ECS_COMPONENT(world, Position); - ECS_COMPONENT(world, Velocity); - - Probe ctx = {0}; - - ecs_observer(world, { - .query.terms = { - { .id = ecs_id(Position), .src.id = ecs_id(Position)|EcsIsEntity }, - { .id = ecs_id(Velocity) }, - }, - .callback = Observer, - .events = { EcsOnTableFill }, - .ctx = &ctx - }); - - ecs_singleton_add(world, Position); - test_int(ctx.invoked, 0); - - ecs_entity_t e = ecs_new(world); - ecs_add(world, e, Velocity); - test_int(ctx.invoked, 0); - - ecs_run_aperiodic(world, 0); - test_int(ctx.invoked, 1); - - ecs_fini(world); -} - ECS_COMPONENT_DECLARE(Velocity); static diff --git a/test/core/src/World.c b/test/core/src/World.c index df0beee3f..ffa5b2bbe 100644 --- a/test/core/src/World.c +++ b/test/core/src/World.c @@ -1134,7 +1134,7 @@ void World_delete_empty_tables_after_mini(void) { ecs_world_t *world = ecs_mini(); const ecs_world_info_t *info = ecs_get_world_info(world); - int32_t old_empty_table_count = info->empty_table_count; + int32_t empty_table_count = info->table_count; int32_t deleted; deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Increase to 1 */ @@ -1142,7 +1142,7 @@ void World_delete_empty_tables_after_mini(void) { deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Delete */ test_assert(deleted != 0); - test_int(info->empty_table_count + deleted, old_empty_table_count); + test_int(info->table_count + deleted, empty_table_count); ecs_fini(world); } @@ -1167,7 +1167,7 @@ void World_delete_1000_empty_tables(void) { ecs_run_aperiodic(world, 0); const ecs_world_info_t *info = ecs_get_world_info(world); - int32_t old_empty_table_count = info->empty_table_count; + int32_t old_table_count = info->table_count; ecs_entity_t e = ecs_new_w(world, Tag); for (int i = 0; i < 1000; i ++) { @@ -1175,7 +1175,7 @@ void World_delete_1000_empty_tables(void) { } ecs_run_aperiodic(world, 0); - test_int(info->empty_table_count, old_empty_table_count + 1000); + test_int(info->table_count, old_table_count + 1000 + 1); int32_t deleted; deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Increase to 1 */ @@ -1185,7 +1185,7 @@ void World_delete_1000_empty_tables(void) { test_assert(deleted != 0); test_assert(deleted >= 1000); - test_assert(info->empty_table_count <= old_empty_table_count); + test_assert(info->table_count <= old_table_count); ecs_fini(world); } @@ -1198,7 +1198,7 @@ void World_delete_empty_tables_for_id(void) { ecs_run_aperiodic(world, 0); const ecs_world_info_t *info = ecs_get_world_info(world); - int32_t old_empty_table_count = info->empty_table_count; + int32_t old_table_count = info->table_count; ecs_entity_t e1 = ecs_new_w(world, TagA); for (int i = 0; i < 500; i ++) { @@ -1210,8 +1210,7 @@ void World_delete_empty_tables_for_id(void) { ecs_add_id(world, e2, ecs_new(world)); } - ecs_run_aperiodic(world, 0); - test_int(info->empty_table_count, old_empty_table_count + 1000); + test_int(info->table_count, old_table_count + 1000 + 2); int32_t deleted; deleted = ecs_delete_empty_tables(world, TagA, 0, 1, 0, 0); /* Increase to 1 */ @@ -1222,8 +1221,6 @@ void World_delete_empty_tables_for_id(void) { test_assert(deleted >= 500); test_assert(deleted < 1000); - test_assert((info->empty_table_count - 500) <= old_empty_table_count); - ecs_fini(world); } diff --git a/test/core/src/WorldInfo.c b/test/core/src/WorldInfo.c index a5eb5726d..3f39be073 100644 --- a/test/core/src/WorldInfo.c +++ b/test/core/src/WorldInfo.c @@ -40,32 +40,6 @@ void WorldInfo_table_count(void) { ecs_fini(world); } -void WorldInfo_empty_table_count(void) { - ecs_world_t *world = ecs_mini(); - - const ecs_world_info_t *cur = ecs_get_world_info(world); - test_assert(cur != NULL); - - ecs_world_info_t prev = *cur; - - ecs_entity_t c = ecs_new(world); - ecs_entity_t e = ecs_new(world); - - ecs_add_id(world, e, c); - ecs_run_aperiodic(world, 0); - test_delta(&prev, cur, empty_table_count, 0); - - ecs_delete(world, e); - ecs_run_aperiodic(world, 0); - test_delta(&prev, cur, empty_table_count, 1); - - ecs_delete(world, c); - ecs_run_aperiodic(world, 0); - test_delta(&prev, cur, empty_table_count, -1); - - ecs_fini(world); -} - void WorldInfo_table_create_count(void) { ecs_world_t *world = ecs_mini(); diff --git a/test/core/src/main.c b/test/core/src/main.c index cb36cb32e..87b97801f 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -1579,7 +1579,6 @@ void Observer_filter_observer_after_observer(void); void Observer_notify_after_defer_batched(void); void Observer_notify_after_defer_batched_2_entities_in_table(void); void Observer_notify_after_defer_batched_2_entities_in_table_w_tgt(void); -void Observer_multi_observer_table_fill_w_singleton(void); void Observer_wildcard_propagate_w_other_table(void); void Observer_add_in_on_add_yield_existing(void); void Observer_add_in_on_add_yield_existing_multi(void); @@ -1998,7 +1997,6 @@ void World_exclusive_on_instantiate(void); // Testsuite 'WorldInfo' void WorldInfo_get_tick(void); void WorldInfo_table_count(void); -void WorldInfo_empty_table_count(void); void WorldInfo_table_create_count(void); void WorldInfo_table_delete_count(void); void WorldInfo_id_tag_component_count(void); @@ -8459,10 +8457,6 @@ bake_test_case Observer_testcases[] = { "notify_after_defer_batched_2_entities_in_table_w_tgt", Observer_notify_after_defer_batched_2_entities_in_table_w_tgt }, - { - "multi_observer_table_fill_w_singleton", - Observer_multi_observer_table_fill_w_singleton - }, { "wildcard_propagate_w_other_table", Observer_wildcard_propagate_w_other_table @@ -10083,10 +10077,6 @@ bake_test_case WorldInfo_testcases[] = { "table_count", WorldInfo_table_count }, - { - "empty_table_count", - WorldInfo_empty_table_count - }, { "table_create_count", WorldInfo_table_create_count @@ -11610,7 +11600,7 @@ static bake_test_suite suites[] = { "Observer", NULL, NULL, - 233, + 232, Observer_testcases }, { @@ -11666,7 +11656,7 @@ static bake_test_suite suites[] = { "WorldInfo", NULL, NULL, - 7, + 6, WorldInfo_testcases }, { diff --git a/test/query/project.json b/test/query/project.json index e097d63a2..f22ab1ec5 100644 --- a/test/query/project.json +++ b/test/query/project.json @@ -623,6 +623,7 @@ "match_empty_tables_w_wildcard", "match_empty_tables_w_no_empty_tables", "match_empty_tables_trivial", + "match_empty_tables_w_wildcard_delete_tables", "oneof_wildcard", "oneof_any", "instanced_w_singleton", diff --git a/test/query/src/Basic.c b/test/query/src/Basic.c index 5b78703f8..2a05eac60 100644 --- a/test/query/src/Basic.c +++ b/test/query/src/Basic.c @@ -7367,6 +7367,66 @@ void Basic_match_empty_tables_trivial(void) { ecs_fini(world); } +void Basic_match_empty_tables_w_wildcard_delete_tables(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + + ECS_TAG(world, TgtA); + ECS_TAG(world, TgtB); + ECS_TAG(world, Foo); + + ecs_entity_t e1 = ecs_new_w_pair(world, Rel, TgtA); + ecs_add_pair(world, e1, Rel, TgtB); + ecs_entity_t e2 = ecs_new_w_pair(world, Rel, TgtA); + ecs_add_pair(world, e2, Rel, TgtB); + ecs_add(world, e2, Foo); + + ecs_query_t *q = ecs_query(world, { + .terms = {{ ecs_pair(Rel, EcsWildcard) }}, + .flags = EcsQueryMatchEmptyTables, + .cache_kind = cache_kind + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(0, it.count); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(ecs_pair(Rel, TgtA), ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(ecs_pair(Rel, TgtB), ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(e2, it.entities[0]); + test_uint(ecs_pair(Rel, TgtA), ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(e2, it.entities[0]); + test_uint(ecs_pair(Rel, TgtB), ecs_field_id(&it, 0)); + test_bool( ecs_query_next(&it), false); + } + + ecs_delete_with(world, ecs_pair(Rel, EcsWildcard)); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(q); + + ecs_fini(world); +} + void Basic_oneof_wildcard(void) { ecs_world_t *world = ecs_mini(); @@ -9073,7 +9133,7 @@ void Basic_iter_nested_1(void) { .terms = { {Singleton, .src.id = Singleton}, {Tag} }, - .cache_kind = EcsQueryCacheAuto + .cache_kind = cache_kind }); ecs_query_t *qb = ecs_query(ecs, { @@ -10043,15 +10103,15 @@ void Basic_match_empty(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10060,12 +10120,12 @@ void Basic_match_empty(void) { { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); + test_assert(it.table == e1_table); test_int(it.count, 0); - test_assert(it.table == e2_table); test_bool(true, ecs_query_next(&it)); - test_assert(it.table == e1_table); test_int(it.count, 0); + test_assert(it.table == e2_table); test_bool(false, ecs_query_next(&it)); } @@ -10099,15 +10159,15 @@ void Basic_match_new_empty(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10117,11 +10177,11 @@ void Basic_match_new_empty(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_int(it.count, 0); - test_assert(it.table == e2_table); + test_assert(it.table == e1_table); test_bool(true, ecs_query_next(&it)); test_int(it.count, 0); - test_assert(it.table == e1_table); + test_assert(it.table == e2_table); test_bool(false, ecs_query_next(&it)); } @@ -10155,16 +10215,16 @@ void Basic_match_empty_w_component(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); test_assert(ecs_field(&it, Position, 0) != NULL); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10173,12 +10233,12 @@ void Basic_match_empty_w_component(void) { { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); + test_assert(it.table == e1_table); test_int(it.count, 0); - test_assert(it.table == e2_table); test_bool(true, ecs_query_next(&it)); - test_assert(it.table == e1_table); test_int(it.count, 0); + test_assert(it.table == e2_table); test_bool(false, ecs_query_next(&it)); } @@ -10212,16 +10272,16 @@ void Basic_match_new_empty_w_component(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); test_assert(ecs_field(&it, Position, 0) != NULL); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10231,11 +10291,11 @@ void Basic_match_new_empty_w_component(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_int(it.count, 0); - test_assert(it.table == e2_table); + test_assert(it.table == e1_table); test_bool(true, ecs_query_next(&it)); test_int(it.count, 0); - test_assert(it.table == e1_table); + test_assert(it.table == e2_table); test_bool(false, ecs_query_next(&it)); } @@ -10271,16 +10331,16 @@ void Basic_match_empty_w_ref(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); test_assert(ecs_field(&it, Position, 0) != NULL); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10289,12 +10349,12 @@ void Basic_match_empty_w_ref(void) { { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); + test_assert(it.table == e1_table); test_int(it.count, 0); - test_assert(it.table == e2_table); test_bool(true, ecs_query_next(&it)); - test_assert(it.table == e1_table); test_int(it.count, 0); + test_assert(it.table == e2_table); test_bool(false, ecs_query_next(&it)); } @@ -10330,16 +10390,16 @@ void Basic_match_new_empty_w_ref(void) { { ecs_iter_t it = ecs_query_iter(world, q); - test_bool(true, ecs_query_next(&it)); - test_int(it.count, 0); - test_assert(it.table == e2_table); - test_bool(true, ecs_query_next(&it)); test_int(it.count, 1); test_assert(it.table == e1_table); test_uint(it.entities[0], e1); test_assert(ecs_field(&it, Position, 0) != NULL); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_assert(it.table == e2_table); + test_bool(false, ecs_query_next(&it)); } @@ -10348,12 +10408,12 @@ void Basic_match_new_empty_w_ref(void) { { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); - test_assert(it.table == e2_table); test_int(it.count, 0); + test_assert(it.table == e1_table); test_bool(true, ecs_query_next(&it)); + test_assert(it.table == e2_table); test_int(it.count, 0); - test_assert(it.table == e1_table); test_bool(false, ecs_query_next(&it)); } @@ -10583,13 +10643,13 @@ void Basic_default_query_flags(void) { test_bool(true, ecs_query_next(&it)); test_int(0, it.count); } else { - test_bool(true, ecs_query_next(&it)); - test_int(0, it.count); - test_bool(true, ecs_query_next(&it)); test_int(2, it.count); test_uint(e1, it.entities[0]); test_uint(e2, it.entities[1]); + + test_bool(true, ecs_query_next(&it)); + test_int(0, it.count); } test_bool(false, ecs_query_next(&it)); diff --git a/test/query/src/Operators.c b/test/query/src/Operators.c index e061ea087..a0bba94a1 100644 --- a/test/query/src/Operators.c +++ b/test/query/src/Operators.c @@ -870,25 +870,25 @@ void Operators_2_and_not_pair_tgt_var_written(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); - test_uint(ecs_pair(RelA, TgtA), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); + test_uint(ecs_pair(RelA, TgtB), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtA, ecs_iter_get_var(&it, x_var)); - test_uint(e1, it.entities[0]); + test_uint(TgtB, ecs_iter_get_var(&it, x_var)); + test_uint(e2, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); - test_uint(ecs_pair(RelA, TgtB), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); + test_uint(ecs_pair(RelA, TgtA), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtB, ecs_iter_get_var(&it, x_var)); - test_uint(e2, it.entities[0]); + test_uint(TgtA, ecs_iter_get_var(&it, x_var)); + test_uint(e1, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -956,25 +956,25 @@ void Operators_2_and_not_pair_rel_tgt_var_written(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); - test_uint(ecs_pair(TgtA, TgtA), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); + test_uint(ecs_pair(TgtB, TgtB), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtA, ecs_iter_get_var(&it, x_var)); - test_uint(e1, it.entities[0]); + test_uint(TgtB, ecs_iter_get_var(&it, x_var)); + test_uint(e2, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); - test_uint(ecs_pair(TgtB, TgtB), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); + test_uint(ecs_pair(TgtA, TgtA), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtB, ecs_iter_get_var(&it, x_var)); - test_uint(e2, it.entities[0]); + test_uint(TgtA, ecs_iter_get_var(&it, x_var)); + test_uint(e1, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -1042,25 +1042,25 @@ void Operators_2_and_not_pair_rel_tgt_same_var_written(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); - test_uint(ecs_pair(TgtA, e1), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); + test_uint(ecs_pair(TgtB, e2), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtA, ecs_iter_get_var(&it, x_var)); - test_uint(e1, it.entities[0]); + test_uint(TgtB, ecs_iter_get_var(&it, x_var)); + test_uint(e2, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(ecs_pair(Tag, TgtB), ecs_field_id(&it, 0)); - test_uint(ecs_pair(TgtB, e2), ecs_field_id(&it, 1)); + test_uint(ecs_pair(Tag, TgtA), ecs_field_id(&it, 0)); + test_uint(ecs_pair(TgtA, e1), ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(TgtB, ecs_iter_get_var(&it, x_var)); - test_uint(e2, it.entities[0]); + test_uint(TgtA, ecs_iter_get_var(&it, x_var)); + test_uint(e1, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -6807,25 +6807,25 @@ void Operators_2_optional_first(void) { { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); - test_uint(2, it.count); + test_uint(1, it.count); test_uint(TagB, ecs_field_id(&it, 0)); test_uint(TagA, ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); - test_bool(true, ecs_field_is_set(&it, 0)); + test_bool(false, ecs_field_is_set(&it, 0)); test_bool(true, ecs_field_is_set(&it, 1)); - test_uint(e1, it.entities[0]); - test_uint(e2, it.entities[1]); + test_uint(e3, it.entities[0]); test_bool(true, ecs_query_next(&it)); - test_uint(1, it.count); + test_uint(2, it.count); test_uint(TagB, ecs_field_id(&it, 0)); test_uint(TagA, ecs_field_id(&it, 1)); test_uint(0, ecs_field_src(&it, 0)); test_uint(0, ecs_field_src(&it, 1)); - test_bool(false, ecs_field_is_set(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); test_bool(true, ecs_field_is_set(&it, 1)); - test_uint(e3, it.entities[0]); + test_uint(e1, it.entities[0]); + test_uint(e2, it.entities[1]); test_bool(false, ecs_query_next(&it)); } @@ -7151,12 +7151,12 @@ void Operators_root_entities(void) { test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); test_uint(ecs_pair(EcsChildOf, 0), ecs_field_id(&it, 0)); - test_uint(EcsFlecs, it.entities[0]); + test_uint(e1, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); test_uint(ecs_pair(EcsChildOf, 0), ecs_field_id(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(EcsFlecs, it.entities[0]); if (ecs_query_next(&it)) { test_uint(1, it.count); @@ -7246,20 +7246,20 @@ void Operators_root_entities_w_optional_children(void) { test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); test_uint(ecs_pair(EcsChildOf, 0), ecs_field_id(&it, 0)); - test_uint(ecs_pair(EcsChildOf, EcsFlecs), ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, Tag), ecs_field_id(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 1)); - test_uint(EcsFlecs, ecs_iter_get_var(&it, this_var)); - test_uint(EcsFlecs, it.entities[0]); + test_bool(false, ecs_field_is_set(&it, 1)); + test_uint(Tag, ecs_iter_get_var(&it, this_var)); + test_uint(Tag, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); test_uint(ecs_pair(EcsChildOf, 0), ecs_field_id(&it, 0)); - test_uint(ecs_pair(EcsChildOf, Tag), ecs_field_id(&it, 1)); + test_uint(ecs_pair(EcsChildOf, EcsFlecs), ecs_field_id(&it, 1)); test_bool(true, ecs_field_is_set(&it, 0)); - test_bool(false, ecs_field_is_set(&it, 1)); - test_uint(Tag, ecs_iter_get_var(&it, this_var)); - test_uint(Tag, it.entities[0]); + test_bool(true, ecs_field_is_set(&it, 1)); + test_uint(EcsFlecs, ecs_iter_get_var(&it, this_var)); + test_uint(EcsFlecs, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); diff --git a/test/query/src/OrderBy.c b/test/query/src/OrderBy.c index 6989b0e5d..4e4f71955 100644 --- a/test/query/src/OrderBy.c +++ b/test/query/src/OrderBy.c @@ -2229,6 +2229,8 @@ void OrderBy_order_empty_table_only(void) { }); ecs_iter_t it = ecs_query_iter(world, q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 0); test_assert(!ecs_query_next(&it)); ecs_query_fini(q); diff --git a/test/query/src/Sparse.c b/test/query/src/Sparse.c index d8e21b10c..6cec0f9fc 100644 --- a/test/query/src/Sparse.c +++ b/test/query/src/Sparse.c @@ -865,8 +865,8 @@ void Sparse_1_sparse_up(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e1, it.entities[0]); - test_uint(e2, it.entities[1]); + test_uint(e3, it.entities[0]); + test_uint(e4, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 0, 0); test_assert(p != NULL); @@ -875,8 +875,8 @@ void Sparse_1_sparse_up(void) { test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e3, it.entities[0]); - test_uint(e4, it.entities[1]); + test_uint(e1, it.entities[0]); + test_uint(e2, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 0, 0); test_assert(p != NULL); @@ -1027,8 +1027,8 @@ void Sparse_1_sparse_written_up(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e1, it.entities[0]); - test_uint(e2, it.entities[1]); + test_uint(e3, it.entities[0]); + test_uint(e4, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 1, 0); test_assert(p != NULL); @@ -1037,8 +1037,8 @@ void Sparse_1_sparse_written_up(void) { test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e3, it.entities[0]); - test_uint(e4, it.entities[1]); + test_uint(e1, it.entities[0]); + test_uint(e2, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 1, 0); test_assert(p != NULL); @@ -1079,34 +1079,34 @@ void Sparse_1_sparse_written_self_up(void) { ecs_iter_t it = ecs_query_iter(world, q); test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e1, it.entities[0]); + test_uint(e3, it.entities[0]); { Position *p = ecs_field_at(&it, Position, 1, 0); test_assert(p != NULL); - test_int(p->x, 10); test_int(p->y, 20); + test_int(p->x, 1); test_int(p->y, 2); } - test_uint(e2, it.entities[1]); + test_uint(e4, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 1, 1); test_assert(p != NULL); - test_int(p->x, 30); test_int(p->y, 40); + test_int(p->x, 1); test_int(p->y, 2); } test_bool(true, ecs_query_next(&it)); test_int(2, it.count); - test_uint(e3, it.entities[0]); + test_uint(e1, it.entities[0]); { Position *p = ecs_field_at(&it, Position, 1, 0); test_assert(p != NULL); - test_int(p->x, 1); test_int(p->y, 2); + test_int(p->x, 10); test_int(p->y, 20); } - test_uint(e4, it.entities[1]); + test_uint(e2, it.entities[1]); { Position *p = ecs_field_at(&it, Position, 1, 1); test_assert(p != NULL); - test_int(p->x, 1); test_int(p->y, 2); + test_int(p->x, 30); test_int(p->y, 40); } test_bool(false, ecs_query_next(&it)); diff --git a/test/query/src/main.c b/test/query/src/main.c index 2262646c7..fdcd5fa7f 100644 --- a/test/query/src/main.c +++ b/test/query/src/main.c @@ -606,6 +606,7 @@ void Basic_match_empty_tables_w_not(void); void Basic_match_empty_tables_w_wildcard(void); void Basic_match_empty_tables_w_no_empty_tables(void); void Basic_match_empty_tables_trivial(void); +void Basic_match_empty_tables_w_wildcard_delete_tables(void); void Basic_oneof_wildcard(void); void Basic_oneof_any(void); void Basic_instanced_w_singleton(void); @@ -4507,6 +4508,10 @@ bake_test_case Basic_testcases[] = { "match_empty_tables_trivial", Basic_match_empty_tables_trivial }, + { + "match_empty_tables_w_wildcard_delete_tables", + Basic_match_empty_tables_w_wildcard_delete_tables + }, { "oneof_wildcard", Basic_oneof_wildcard @@ -10581,7 +10586,7 @@ static bake_test_suite suites[] = { "Basic", Basic_setup, NULL, - 232, + 233, Basic_testcases, 1, Basic_params diff --git a/test/script/project.json b/test/script/project.json index 4aa3ccfd7..4755fd829 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -297,7 +297,8 @@ "for_range", "for_range_vars", "for_range_1_4", - "for_range_min_1_2" + "for_range_min_1_2", + "variable_assign_self" ] }, { "id": "Template", @@ -365,7 +366,9 @@ "template_w_for", "template_w_component_w_undefined_identifier", "template_w_child_component_w_undefined_identifier", - "template_w_anonymous_child_component_w_undefined_identifier" + "template_w_anonymous_child_component_w_undefined_identifier", + "clear_script_w_template_w_on_remove_observer", + "clear_script_w_template_w_on_remove_observer_added_after" ] }, { "id": "Error", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 26efc1eb3..9e704aa59 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -9666,3 +9666,18 @@ void Eval_for_range_min_1_2(void) { ecs_fini(world); } + +void Eval_variable_assign_self(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_t *s; + + const char *expr = + LINE "const v = $v" + LINE "" + ; + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 47d5d15ec..d9b2c96fc 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2988,3 +2988,117 @@ void Template_template_w_anonymous_child_component_w_undefined_identifier(void) ecs_fini(world); } + +static int on_foo_invoked = 0; + +static +void on_foo(ecs_iter_t *it) { + on_foo_invoked ++; +} + +void Template_clear_script_w_template_w_on_remove_observer(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_observer(world, { + .query.terms = {{ ecs_id(Position) }}, + .events = { EcsOnRemove }, + .callback = on_foo + }); + + const char *expr = + HEAD "e { Position: {10, 20} }" + LINE "" + LINE "parent {" + LINE " template Bar { }" + LINE "}"; + + ecs_entity_t s = ecs_script(world, { + .code = expr + }); + + test_assert(s != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + ecs_entity_t parent = ecs_lookup(world, "parent"); + test_assert(parent != 0); + ecs_entity_t bar = ecs_lookup(world, "parent.Bar"); + test_assert(bar != 0); + test_assert(ecs_has(world, e, Position)); + + test_int(on_foo_invoked, 0); + + ecs_script_clear(world, s, 0); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, parent)); + test_assert(!ecs_is_alive(world, bar)); + + test_int(on_foo_invoked, 1); + + ecs_fini(world); +} + +void Template_clear_script_w_template_w_on_remove_observer_added_after(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_observer(world, { + .query.terms = {{ ecs_id(Position) }}, + .events = { EcsOnRemove }, + .callback = on_foo + }); + + const char *expr = + HEAD "e { }" + LINE "" + LINE "parent {" + LINE " template Bar { }" + LINE "}"; + + ecs_entity_t s = ecs_script(world, { + .code = expr + }); + + test_assert(s != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + ecs_entity_t parent = ecs_lookup(world, "parent"); + test_assert(parent != 0); + ecs_entity_t bar = ecs_lookup(world, "parent.Bar"); + test_assert(bar != 0); + + ecs_add(world, e, Position); + + test_int(on_foo_invoked, 0); + + ecs_script_clear(world, s, 0); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, parent)); + test_assert(!ecs_is_alive(world, bar)); + + test_int(on_foo_invoked, 1); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index b0f8bfb81..013c986b5 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -294,6 +294,7 @@ void Eval_for_range(void); void Eval_for_range_vars(void); void Eval_for_range_1_4(void); void Eval_for_range_min_1_2(void); +void Eval_variable_assign_self(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -360,6 +361,8 @@ void Template_template_w_for(void); void Template_template_w_component_w_undefined_identifier(void); void Template_template_w_child_component_w_undefined_identifier(void); void Template_template_w_anonymous_child_component_w_undefined_identifier(void); +void Template_clear_script_w_template_w_on_remove_observer(void); +void Template_clear_script_w_template_w_on_remove_observer_added_after(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2012,6 +2015,10 @@ bake_test_case Eval_testcases[] = { { "for_range_min_1_2", Eval_for_range_min_1_2 + }, + { + "variable_assign_self", + Eval_variable_assign_self } }; @@ -2271,6 +2278,14 @@ bake_test_case Template_testcases[] = { { "template_w_anonymous_child_component_w_undefined_identifier", Template_template_w_anonymous_child_component_w_undefined_identifier + }, + { + "clear_script_w_template_w_on_remove_observer", + Template_clear_script_w_template_w_on_remove_observer + }, + { + "clear_script_w_template_w_on_remove_observer_added_after", + Template_clear_script_w_template_w_on_remove_observer_added_after } }; @@ -4289,14 +4304,14 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 285, + 286, Eval_testcases }, { "Template", NULL, NULL, - 64, + 66, Template_testcases }, { From 3ea8c8771f0dfaa14f448860d13842df221c946c Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Wed, 25 Dec 2024 21:37:03 -0800 Subject: [PATCH 12/36] Fix change detection for pipeline rebuilds --- distr/flecs.c | 104 +++++++++++++--------------- src/addons/app.c | 12 +++- src/addons/script/expr/visit_type.c | 5 ++ src/addons/script/functions_math.c | 15 +++- src/addons/script/visit_eval.c | 6 +- src/query/engine/cache.c | 10 ++- src/query/engine/cache_order_by.c | 12 +++- src/storage/table.c | 11 ++- src/storage/table.h | 4 -- src/storage/table_cache.c | 8 --- src/storage/table_cache.h | 5 -- src/storage/table_graph.c | 16 ----- test/addons/src/SystemPeriodic.c | 6 +- test/script/src/Eval.c | 15 +--- 14 files changed, 102 insertions(+), 127 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 1c6a3fd2e..3603b2861 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -568,10 +568,6 @@ void flecs_table_delete( int32_t index, bool destruct); -/* Make sure table records are in correct table cache list */ -bool flecs_table_records_update_empty( - ecs_table_t *table); - /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, @@ -1073,11 +1069,6 @@ void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty); - #define flecs_table_cache_count(cache) (cache)->tables.count bool flecs_table_cache_iter( @@ -21616,10 +21607,13 @@ static ecs_app_desc_t ecs_app_desc; #ifdef ECS_TARGET_EM #include -ecs_http_server_t *flecs_wasm_rest_server; +ecs_http_server_t *flecs_wasm_rest_server = NULL; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request) { + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, + "wasm REST server is not initialized yet"); + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); if (reply.code == 200) { @@ -21642,11 +21636,12 @@ int ecs_app_run( { ecs_app_desc = *desc; - /* Don't set FPS & threads if using emscripten */ -#ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } + + /* Don't set threads if using emscripten */ +#ifndef ECS_TARGET_EM if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } @@ -21657,6 +21652,8 @@ int ecs_app_run( #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INTERNAL_ERROR, + "failed to create wasm REST server (unexpected error)"); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); @@ -38121,7 +38118,6 @@ void flecs_table_fini_data( ecs_table_t *table, bool do_on_remove, bool is_delete, - bool deactivate, bool deallocate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); @@ -38195,7 +38191,7 @@ void ecs_table_clear_entities( ecs_world_t* world, ecs_table_t* table) { - flecs_table_fini_data(world, table, true, true, true, false); + flecs_table_fini_data(world, table, true, true, false); } /* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ @@ -38203,7 +38199,7 @@ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, false, false, true, true); + flecs_table_fini_data(world, table, false, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ @@ -38211,7 +38207,7 @@ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, false, true, true); + flecs_table_fini_data(world, table, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ @@ -38219,7 +38215,7 @@ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, true, true, true); + flecs_table_fini_data(world, table, true, true, true); } /* Unset all components in table. This function is called before a table is @@ -38264,7 +38260,7 @@ void flecs_table_fini( } /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - flecs_table_fini_data(world, table, false, true, false, true); + flecs_table_fini_data(world, table, false, true, true); flecs_table_clear_edges(world, table); if (!is_root) { @@ -39914,14 +39910,6 @@ void* ecs_table_cache_remove( return elem; } -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty) -{ - return false; -} - bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) @@ -40494,22 +40482,6 @@ void flecs_table_init_node( flecs_table_init_edges(&node->remove); } -bool flecs_table_records_update_empty( - ecs_table_t *table) -{ - bool result = false; - bool is_empty = ecs_table_count(table) == 0; - - int32_t i, count = table->_->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->_->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - result |= ecs_table_cache_set_empty(cache, table, is_empty); - } - - return result; -} - static void flecs_init_table( ecs_world_t *world, @@ -55875,7 +55847,7 @@ typedef struct ecs_script_rng_t { } ecs_script_rng_t; static -ecs_script_rng_t* flecs_script_rng_new() { +ecs_script_rng_t* flecs_script_rng_new(void) { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); result->x = 0; result->w = 0; @@ -55961,7 +55933,12 @@ void flecs_script_rng_get_float( uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; - *r = (double)x / ((double)UINT64_MAX / max); + + if (ECS_EQZERO(max)) { + ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); + } else { + *r = (double)x / ((double)UINT64_MAX / max); + } } void flecs_script_rng_get_uint( @@ -55982,7 +55959,11 @@ void flecs_script_rng_get_uint( uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; - *r = x % max; + if (!max) { + ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); + } else { + *r = x % max; + } } #define FLECS_MATH_FUNC_F64(name, ...)\ @@ -61496,9 +61477,9 @@ int flecs_script_find_template_entity( int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { - ecs_script_node_t *node = nodes[i]; - if (node->kind == EcsAstEntity) { - ecs_script_entity_t *entity_node = (ecs_script_entity_t*)node; + ecs_script_node_t *elem = nodes[i]; + if (elem->kind == EcsAstEntity) { + ecs_script_entity_t *entity_node = (ecs_script_entity_t*)elem; if (!entity_node->name) { continue; } @@ -68648,8 +68629,7 @@ void flecs_query_cache_match_tables( /* New table matched, add record to cache */ table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); - ecs_dbg_3("query cache matched existing table [%s]", - ecs_table_str(world, table)); + ecs_dbg_3("query cache matched existing table [%s]", NULL); } ecs_query_cache_table_match_t *qm = @@ -68807,7 +68787,6 @@ int flecs_query_cache_process_signature( static void flecs_query_cache_table_match_free( ecs_query_cache_t *cache, - ecs_query_cache_table_t *elem, ecs_query_cache_table_match_t *first) { ecs_query_cache_table_match_t *cur, *next; @@ -68841,7 +68820,7 @@ void flecs_query_cache_table_free( ecs_query_cache_t *cache, ecs_query_cache_table_t *elem) { - flecs_query_cache_table_match_free(cache, elem, elem->first); + flecs_query_cache_table_match_free(cache, elem->first); flecs_bfree(&cache->query->world->allocators.query_table, elem); } @@ -68896,7 +68875,7 @@ void flecs_query_cache_rematch_tables( while (ecs_query_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); + flecs_query_cache_table_match_free(cache, qm->next_match); qm->next_match = NULL; } @@ -68932,7 +68911,7 @@ void flecs_query_cache_rematch_tables( } if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); + flecs_query_cache_table_match_free(cache, qm->next_match); qm->next_match = NULL; } @@ -69923,7 +69902,7 @@ void flecs_query_cache_sort_tables( ecs_id_record_t *idr = flecs_id_record_get(world, order_by); ecs_table_cache_iter_t it; ecs_query_cache_table_t *qt; - flecs_table_cache_iter(&cache->cache, &it); + flecs_table_cache_all_iter(&cache->cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { ecs_table_t *table = qt->hdr.table; @@ -69932,6 +69911,16 @@ void flecs_query_cache_sort_tables( if (flecs_query_check_table_monitor(impl, qt, 0)) { tables_sorted = true; dirty = true; + + if (!ecs_table_count(table)) { + /* If table is empty, there's a chance the query won't iterate it + * so update the match monitor here. */ + ecs_query_cache_table_match_t *cur, *next; + for (cur = qt->first; cur != NULL; cur = next) { + flecs_query_sync_match_monitor(impl, cur); + next = cur->next_match; + } + } } int32_t column = -1; @@ -79944,6 +79933,11 @@ int flecs_expr_variable_visit_type( desc->vars, node->name, &node->sp); if (var) { node->node.type = var->value.type; + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "variable '%s' is not initialized", node->name); + goto error; + } } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/app.c b/src/addons/app.c index 8cd0d6f17..cbe68f7db 100644 --- a/src/addons/app.c +++ b/src/addons/app.c @@ -57,10 +57,13 @@ static ecs_app_desc_t ecs_app_desc; #ifdef ECS_TARGET_EM #include -ecs_http_server_t *flecs_wasm_rest_server; +ecs_http_server_t *flecs_wasm_rest_server = NULL; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request) { + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, + "wasm REST server is not initialized yet"); + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); if (reply.code == 200) { @@ -83,11 +86,12 @@ int ecs_app_run( { ecs_app_desc = *desc; - /* Don't set FPS & threads if using emscripten */ -#ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } + + /* Don't set threads if using emscripten */ +#ifndef ECS_TARGET_EM if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } @@ -98,6 +102,8 @@ int ecs_app_run( #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INTERNAL_ERROR, + "failed to create wasm REST server (unexpected error)"); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 5a9b88e50..09ba1150f 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -949,6 +949,11 @@ int flecs_expr_variable_visit_type( desc->vars, node->name, &node->sp); if (var) { node->node.type = var->value.type; + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "variable '%s' is not initialized", node->name); + goto error; + } } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; diff --git a/src/addons/script/functions_math.c b/src/addons/script/functions_math.c index aa582df34..029b5d294 100644 --- a/src/addons/script/functions_math.c +++ b/src/addons/script/functions_math.c @@ -18,7 +18,7 @@ typedef struct ecs_script_rng_t { } ecs_script_rng_t; static -ecs_script_rng_t* flecs_script_rng_new() { +ecs_script_rng_t* flecs_script_rng_new(void) { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); result->x = 0; result->w = 0; @@ -104,7 +104,12 @@ void flecs_script_rng_get_float( uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; - *r = (double)x / ((double)UINT64_MAX / max); + + if (ECS_EQZERO(max)) { + ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); + } else { + *r = (double)x / ((double)UINT64_MAX / max); + } } void flecs_script_rng_get_uint( @@ -125,7 +130,11 @@ void flecs_script_rng_get_uint( uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; - *r = x % max; + if (!max) { + ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); + } else { + *r = x % max; + } } #define FLECS_MATH_FUNC_F64(name, ...)\ diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index b8e720d5f..9ed9e69ca 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -274,9 +274,9 @@ int flecs_script_find_template_entity( int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { - ecs_script_node_t *node = nodes[i]; - if (node->kind == EcsAstEntity) { - ecs_script_entity_t *entity_node = (ecs_script_entity_t*)node; + ecs_script_node_t *elem = nodes[i]; + if (elem->kind == EcsAstEntity) { + ecs_script_entity_t *entity_node = (ecs_script_entity_t*)elem; if (!entity_node->name) { continue; } diff --git a/src/query/engine/cache.c b/src/query/engine/cache.c index b53a57282..565455f90 100644 --- a/src/query/engine/cache.c +++ b/src/query/engine/cache.c @@ -574,8 +574,7 @@ void flecs_query_cache_match_tables( /* New table matched, add record to cache */ table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); - ecs_dbg_3("query cache matched existing table [%s]", - ecs_table_str(world, table)); + ecs_dbg_3("query cache matched existing table [%s]", NULL); } ecs_query_cache_table_match_t *qm = @@ -733,7 +732,6 @@ int flecs_query_cache_process_signature( static void flecs_query_cache_table_match_free( ecs_query_cache_t *cache, - ecs_query_cache_table_t *elem, ecs_query_cache_table_match_t *first) { ecs_query_cache_table_match_t *cur, *next; @@ -767,7 +765,7 @@ void flecs_query_cache_table_free( ecs_query_cache_t *cache, ecs_query_cache_table_t *elem) { - flecs_query_cache_table_match_free(cache, elem, elem->first); + flecs_query_cache_table_match_free(cache, elem->first); flecs_bfree(&cache->query->world->allocators.query_table, elem); } @@ -822,7 +820,7 @@ void flecs_query_cache_rematch_tables( while (ecs_query_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); + flecs_query_cache_table_match_free(cache, qm->next_match); qm->next_match = NULL; } @@ -858,7 +856,7 @@ void flecs_query_cache_rematch_tables( } if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); + flecs_query_cache_table_match_free(cache, qm->next_match); qm->next_match = NULL; } diff --git a/src/query/engine/cache_order_by.c b/src/query/engine/cache_order_by.c index 275ddfb2f..9f2f355ce 100644 --- a/src/query/engine/cache_order_by.c +++ b/src/query/engine/cache_order_by.c @@ -275,7 +275,7 @@ void flecs_query_cache_sort_tables( ecs_id_record_t *idr = flecs_id_record_get(world, order_by); ecs_table_cache_iter_t it; ecs_query_cache_table_t *qt; - flecs_table_cache_iter(&cache->cache, &it); + flecs_table_cache_all_iter(&cache->cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { ecs_table_t *table = qt->hdr.table; @@ -284,6 +284,16 @@ void flecs_query_cache_sort_tables( if (flecs_query_check_table_monitor(impl, qt, 0)) { tables_sorted = true; dirty = true; + + if (!ecs_table_count(table)) { + /* If table is empty, there's a chance the query won't iterate it + * so update the match monitor here. */ + ecs_query_cache_table_match_t *cur, *next; + for (cur = qt->first; cur != NULL; cur = next) { + flecs_query_sync_match_monitor(impl, cur); + next = cur->next_match; + } + } } int32_t column = -1; diff --git a/src/storage/table.c b/src/storage/table.c index 1f9df1665..50e9f44bf 100644 --- a/src/storage/table.c +++ b/src/storage/table.c @@ -886,7 +886,6 @@ void flecs_table_fini_data( ecs_table_t *table, bool do_on_remove, bool is_delete, - bool deactivate, bool deallocate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); @@ -960,7 +959,7 @@ void ecs_table_clear_entities( ecs_world_t* world, ecs_table_t* table) { - flecs_table_fini_data(world, table, true, true, true, false); + flecs_table_fini_data(world, table, true, true, false); } /* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ @@ -968,7 +967,7 @@ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, false, false, true, true); + flecs_table_fini_data(world, table, false, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ @@ -976,7 +975,7 @@ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, false, true, true); + flecs_table_fini_data(world, table, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ @@ -984,7 +983,7 @@ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, true, true, true); + flecs_table_fini_data(world, table, true, true, true); } /* Unset all components in table. This function is called before a table is @@ -1029,7 +1028,7 @@ void flecs_table_fini( } /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - flecs_table_fini_data(world, table, false, true, false, true); + flecs_table_fini_data(world, table, false, true, true); flecs_table_clear_edges(world, table); if (!is_root) { diff --git a/src/storage/table.h b/src/storage/table.h index ec6c5a89c..0b9a1f47e 100644 --- a/src/storage/table.h +++ b/src/storage/table.h @@ -182,10 +182,6 @@ void flecs_table_delete( int32_t index, bool destruct); -/* Make sure table records are in correct table cache list */ -bool flecs_table_records_update_empty( - ecs_table_t *table); - /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, diff --git a/src/storage/table_cache.c b/src/storage/table_cache.c index 14a521306..b5a9d367b 100644 --- a/src/storage/table_cache.c +++ b/src/storage/table_cache.c @@ -174,14 +174,6 @@ void* ecs_table_cache_remove( return elem; } -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty) -{ - return false; -} - bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) diff --git a/src/storage/table_cache.h b/src/storage/table_cache.h index 2f01259dc..fe4c525c7 100644 --- a/src/storage/table_cache.h +++ b/src/storage/table_cache.h @@ -32,11 +32,6 @@ void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty); - #define flecs_table_cache_count(cache) (cache)->tables.count bool flecs_table_cache_iter( diff --git a/src/storage/table_graph.c b/src/storage/table_graph.c index be4a9dbca..982fb8cae 100644 --- a/src/storage/table_graph.c +++ b/src/storage/table_graph.c @@ -506,22 +506,6 @@ void flecs_table_init_node( flecs_table_init_edges(&node->remove); } -bool flecs_table_records_update_empty( - ecs_table_t *table) -{ - bool result = false; - bool is_empty = ecs_table_count(table) == 0; - - int32_t i, count = table->_->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->_->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - result |= ecs_table_cache_set_empty(cache, table, is_empty); - } - - return result; -} - static void flecs_init_table( ecs_world_t *world, diff --git a/test/addons/src/SystemPeriodic.c b/test/addons/src/SystemPeriodic.c index f10da1a13..c7d8c692e 100644 --- a/test/addons/src/SystemPeriodic.c +++ b/test/addons/src/SystemPeriodic.c @@ -725,9 +725,9 @@ void SystemPeriodic_2_type_1_and_1_optional(void) { test_int(ctx.term_count, 2); test_null(ctx.param); - test_int(ctx.e[0], e1); - test_int(ctx.e[1], e2); - test_int(ctx.e[2], e3); + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e1); + test_int(ctx.e[2], e2); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 9e704aa59..76b0a38e1 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -8943,9 +8943,6 @@ void Eval_eval_w_vars_different_order_var_component(void) { ((Position*)foo->value.ptr)->y = 20; ecs_script_eval_desc_t desc = { .vars = vars }; - s = ecs_script_parse(world, NULL, expr, &desc); - test_assert(s != NULL); - test_int(0, ecs_script_eval(s, &desc)); ecs_entity_t e = ecs_lookup(world, "e"); test_assert(e != 0); @@ -9032,9 +9029,6 @@ void Eval_eval_w_vars_different_order_with_var(void) { ((Position*)foo->value.ptr)->y = 20; ecs_script_eval_desc_t desc = { .vars = vars }; - s = ecs_script_parse(world, NULL, expr, &desc); - test_assert(s != NULL); - test_int(0, ecs_script_eval(s, &desc)); ecs_entity_t e = ecs_lookup(world, "e"); test_assert(e != 0); @@ -9095,9 +9089,6 @@ void Eval_eval_w_vars_different_order_pair_w_var(void) { *((ecs_entity_t*)foo->value.ptr) = Rel; ecs_script_eval_desc_t desc = { .vars = vars }; - s = ecs_script_parse(world, NULL, expr, &desc); - test_assert(s != NULL); - test_int(0, ecs_script_eval(s, &desc)); ecs_entity_t e = ecs_lookup(world, "e"); test_assert(e != 0); @@ -9150,9 +9141,6 @@ void Eval_eval_w_vars_different_order_pair_scope_w_var(void) { *((ecs_entity_t*)foo->value.ptr) = Rel; ecs_script_eval_desc_t desc = { .vars = vars }; - s = ecs_script_parse(world, NULL, expr, &desc); - test_assert(s != NULL); - test_int(0, ecs_script_eval(s, &desc)); ecs_entity_t e = ecs_lookup(world, "e"); test_assert(e != 0); @@ -9670,13 +9658,12 @@ void Eval_for_range_min_1_2(void) { void Eval_variable_assign_self(void) { ecs_world_t *world = ecs_init(); - ecs_script_t *s; - const char *expr = LINE "const v = $v" LINE "" ; + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, expr) != 0); ecs_fini(world); From d49297b82fa966f260ce18de3a11054ad6a76629 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 26 Dec 2024 18:33:45 -0800 Subject: [PATCH 13/36] Fix issue where assigning id value in script would not take into account using --- distr/flecs.c | 2 +- src/addons/script/expr/visit_type.c | 2 +- test/script/project.json | 8 +- test/script/src/Eval.c | 219 ++++++++++++++++++++++++++++ test/script/src/main.c | 32 +++- 5 files changed, 259 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 3603b2861..1499d9fe9 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -79858,7 +79858,7 @@ int flecs_expr_identifier_visit_type( ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); - if (type == ecs_id(ecs_entity_t)) { + if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); result->ptr = &result->storage.entity; diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 09ba1150f..e2100dea7 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -874,7 +874,7 @@ int flecs_expr_identifier_visit_type( ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); - if (type == ecs_id(ecs_entity_t)) { + if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { result->storage.entity = desc->lookup_action( script->world, node->value, desc->lookup_ctx); result->ptr = &result->storage.entity; diff --git a/test/script/project.json b/test/script/project.json index 4755fd829..38c8e972c 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -298,7 +298,13 @@ "for_range_vars", "for_range_1_4", "for_range_min_1_2", - "variable_assign_self" + "variable_assign_self", + "func_w_entity_arg", + "func_w_entity_arg_w_using", + "method_w_entity_arg", + "method_w_entity_arg_w_using", + "assign_id", + "assign_id_w_using" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 76b0a38e1..1d8cd7bfd 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -9668,3 +9668,222 @@ void Eval_variable_assign_self(void) { ecs_fini(world); } + +static +void func_is_component( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + test_assert(result != NULL); + test_int(argc, 1); + test_assert(argv != NULL); + + *(bool*)result->ptr = (*(ecs_entity_t*)argv[0].ptr) == ecs_id(EcsComponent); +} + +void Eval_func_w_entity_arg(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, True); + ECS_TAG(world, False); + + ecs_function(world, { + .name = "is_component", + .return_type = ecs_id(ecs_bool_t), + .params = { + { "a", ecs_id(ecs_entity_t) } + }, + .callback = func_is_component + }); + + const char *expr = + HEAD "e {" + LINE " (bool, True): {is_component(flecs.core.Component)}" + LINE " (bool, False): {is_component(flecs.core.Relationship)}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, True); + test_assert(v != NULL); + test_bool(*v, true); + } + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, False); + test_assert(v != NULL); + test_bool(*v, false); + } + + ecs_fini(world); +} + +void Eval_func_w_entity_arg_w_using(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, True); + ECS_TAG(world, False); + + ecs_function(world, { + .name = "is_component", + .return_type = ecs_id(ecs_bool_t), + .params = { + { "a", ecs_id(ecs_entity_t) } + }, + .callback = func_is_component + }); + + const char *expr = + HEAD "using flecs" + LINE "e {" + LINE " (bool, True): {is_component(core.Component)}" + LINE " (bool, False): {is_component(core.Relationship)}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, True); + test_assert(v != NULL); + test_bool(*v, true); + } + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, False); + test_assert(v != NULL); + test_bool(*v, false); + } + + ecs_fini(world); +} + +void Eval_method_w_entity_arg(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, True); + ECS_TAG(world, False); + + const char *expr = + HEAD "e {" + LINE " (bool, True): {flecs.core.Component.has(flecs.core.Component)}" + LINE " (bool, False): {flecs.core.Component.has(flecs.core.Relationship)}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, True); + test_assert(v != NULL); + test_bool(*v, true); + } + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, False); + test_assert(v != NULL); + test_bool(*v, false); + } + + ecs_fini(world); +} + +void Eval_method_w_entity_arg_w_using(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, True); + ECS_TAG(world, False); + + const char *expr = + HEAD "using flecs" + LINE "e {" + LINE " (bool, True): {core.Component.has(core.Component)}" + LINE " (bool, False): {core.Component.has(core.Relationship)}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, True); + test_assert(v != NULL); + test_bool(*v, true); + } + { + const bool *v = ecs_get_pair(world, e, ecs_bool_t, False); + test_assert(v != NULL); + test_bool(*v, false); + } + + ecs_fini(world); +} + +void Eval_assign_id(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + ecs_id_t value; + } Id; + + ecs_entity_t ecs_id(Id) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Id"}), + .members = { + {"value", ecs_id(ecs_id_t)} + } + }); + + const char *expr = + HEAD "Foo = Id: {flecs.core.Component}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + + test_assert(foo != 0); + + test_assert(ecs_has(world, foo, Id)); + + const Id *ptr = ecs_get(world, foo, Id); + test_assert(ptr != NULL); + + test_int(ptr->value, ecs_id(EcsComponent)); + + ecs_fini(world); +} + +void Eval_assign_id_w_using(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + ecs_id_t value; + } Id; + + ecs_entity_t ecs_id(Id) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Id"}), + .members = { + {"value", ecs_id(ecs_id_t)} + } + }); + + const char *expr = + HEAD "using flecs" + LINE "Foo = Id: {core.Component}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + + test_assert(foo != 0); + + test_assert(ecs_has(world, foo, Id)); + + const Id *ptr = ecs_get(world, foo, Id); + test_assert(ptr != NULL); + + test_int(ptr->value, ecs_id(EcsComponent)); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 013c986b5..1365dbb0a 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -295,6 +295,12 @@ void Eval_for_range_vars(void); void Eval_for_range_1_4(void); void Eval_for_range_min_1_2(void); void Eval_variable_assign_self(void); +void Eval_func_w_entity_arg(void); +void Eval_func_w_entity_arg_w_using(void); +void Eval_method_w_entity_arg(void); +void Eval_method_w_entity_arg_w_using(void); +void Eval_assign_id(void); +void Eval_assign_id_w_using(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -2019,6 +2025,30 @@ bake_test_case Eval_testcases[] = { { "variable_assign_self", Eval_variable_assign_self + }, + { + "func_w_entity_arg", + Eval_func_w_entity_arg + }, + { + "func_w_entity_arg_w_using", + Eval_func_w_entity_arg_w_using + }, + { + "method_w_entity_arg", + Eval_method_w_entity_arg + }, + { + "method_w_entity_arg_w_using", + Eval_method_w_entity_arg_w_using + }, + { + "assign_id", + Eval_assign_id + }, + { + "assign_id_w_using", + Eval_assign_id_w_using } }; @@ -4304,7 +4334,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 286, + 292, Eval_testcases }, { From affe11a672c9d16d1908eeecebfda88f06673703 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 26 Dec 2024 21:02:26 -0800 Subject: [PATCH 14/36] Implement else if --- distr/flecs.c | 87 ++++++++++++++++++++--------- src/addons/app.c | 4 +- src/addons/script/parser.c | 79 +++++++++++++++++++-------- src/addons/script/tokenizer.c | 4 ++ test/script/project.json | 5 ++ test/script/src/Eval.c | 100 ++++++++++++++++++++++++++++++++++ test/script/src/main.c | 27 ++++++++- 7 files changed, 251 insertions(+), 55 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 1499d9fe9..44cf90c2f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -21636,12 +21636,10 @@ int ecs_app_run( { ecs_app_desc = *desc; +#ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } - - /* Don't set threads if using emscripten */ -#ifndef ECS_TARGET_EM if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } @@ -56689,6 +56687,60 @@ const char* flecs_script_paren_expr( ParserEnd; } +/* Parse a single statement */ +static +const char* flecs_script_if_stmt( + ecs_script_parser_t *parser, + const char *pos) +{ + ParserBegin; + + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } + + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; + + Parse( + // if expr { } else if + case EcsTokKeywordIf: { + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + } + + // if expr { } else\n if + case EcsTokNewline: { + Parse_1(EcsTokKeywordIf, + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + ) + } + + // if expr { } else { + case '{': { + return flecs_script_scope(parser, stmt->if_false, pos); + } + ) + ) + + EndOfRule; + }); + ) + + ParserEnd; +} + /* Parse a single statement */ static const char* flecs_script_stmt( @@ -56948,30 +57000,7 @@ const_var: { // if if_stmt: { - // if expr - Expr('\0', - // if expr { - Parse_1('{', { - ecs_script_if_t *stmt = flecs_script_insert_if(parser); - stmt->expr = EXPR; - pos = flecs_script_scope(parser, stmt->if_true, pos); - if (!pos) { - goto error; - } - - // if expr { } else - LookAhead_1(EcsTokKeywordElse, - pos = lookahead; - - // if expr { } else { - Parse_1('{', - return flecs_script_scope(parser, stmt->if_false, pos); - ) - ) - - EndOfRule; - }); - ) + return flecs_script_if_stmt(parser, pos); } // for @@ -59592,6 +59621,10 @@ void flecs_script_template_import( #define Keyword(keyword, _kind)\ } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ + out->value = keyword;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(keyword);\ + } else if (!ecs_os_strncmp(pos, keyword "\n", ecs_os_strlen(keyword) + 1)) {\ out->value = keyword;\ out->kind = _kind;\ return pos + ecs_os_strlen(keyword); diff --git a/src/addons/app.c b/src/addons/app.c index cbe68f7db..e25e8aec5 100644 --- a/src/addons/app.c +++ b/src/addons/app.c @@ -86,12 +86,10 @@ int ecs_app_run( { ecs_app_desc = *desc; +#ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } - - /* Don't set threads if using emscripten */ -#ifndef ECS_TARGET_EM if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 7f55cfd14..5b911620c 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -230,6 +230,60 @@ const char* flecs_script_paren_expr( ParserEnd; } +/* Parse a single statement */ +static +const char* flecs_script_if_stmt( + ecs_script_parser_t *parser, + const char *pos) +{ + ParserBegin; + + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } + + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; + + Parse( + // if expr { } else if + case EcsTokKeywordIf: { + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + } + + // if expr { } else\n if + case EcsTokNewline: { + Parse_1(EcsTokKeywordIf, + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + ) + } + + // if expr { } else { + case '{': { + return flecs_script_scope(parser, stmt->if_false, pos); + } + ) + ) + + EndOfRule; + }); + ) + + ParserEnd; +} + /* Parse a single statement */ static const char* flecs_script_stmt( @@ -489,30 +543,7 @@ const_var: { // if if_stmt: { - // if expr - Expr('\0', - // if expr { - Parse_1('{', { - ecs_script_if_t *stmt = flecs_script_insert_if(parser); - stmt->expr = EXPR; - pos = flecs_script_scope(parser, stmt->if_true, pos); - if (!pos) { - goto error; - } - - // if expr { } else - LookAhead_1(EcsTokKeywordElse, - pos = lookahead; - - // if expr { } else { - Parse_1('{', - return flecs_script_scope(parser, stmt->if_false, pos); - ) - ) - - EndOfRule; - }); - ) + return flecs_script_if_stmt(parser, pos); } // for diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 3793c065e..ee6bcbf9d 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -10,6 +10,10 @@ #define Keyword(keyword, _kind)\ } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ + out->value = keyword;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(keyword);\ + } else if (!ecs_os_strncmp(pos, keyword "\n", ecs_os_strlen(keyword) + 1)) {\ out->value = keyword;\ out->kind = _kind;\ return pos + ecs_os_strlen(keyword); diff --git a/test/script/project.json b/test/script/project.json index 38c8e972c..78207ee8b 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -227,6 +227,11 @@ "if_false_in_scope", "if_lt", "if_lt_const", + "if_else_if", + "if_else_if_else", + "if_else_if_else_if", + "if_else_newline_if", + "if_else_space_newline_if", "isa_in_module", "isa_hierarchy", "isa_hierarchy_in_module", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 1d8cd7bfd..4a956064d 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -6895,6 +6895,106 @@ void Eval_if_lt_const(void) { ecs_fini(world); } +void Eval_if_else_if(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const v = 1" + LINE "if $v == 1 {" + LINE " a{}" + LINE "} else if $v == 0 {" + LINE " b{}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + test_assert(ecs_lookup(world, "a") != 0); + test_assert(ecs_lookup(world, "b") == 0); + + ecs_fini(world); +} + +void Eval_if_else_if_else(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const v = 1" + LINE "if $v == 1 {" + LINE " a{}" + LINE "} else if $v == 0 {" + LINE " b{}" + LINE "} else {" + LINE " c{}" + LINE "}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + test_assert(ecs_lookup(world, "a") != 0); + test_assert(ecs_lookup(world, "b") == 0); + test_assert(ecs_lookup(world, "c") == 0); + + ecs_fini(world); +} + +void Eval_if_else_if_else_if(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const v = 1" + LINE "if $v == 1 {" + LINE " a{}" + LINE "} else if $v == 0 {" + LINE " b{}" + LINE "} else if $v == 2 {" + LINE " c{}" + LINE "}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + test_assert(ecs_lookup(world, "a") != 0); + test_assert(ecs_lookup(world, "b") == 0); + test_assert(ecs_lookup(world, "c") == 0); + + ecs_fini(world); +} + +void Eval_if_else_newline_if(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const v = 1" + LINE "if $v == 1 {" + LINE " a{}" + LINE "} else" + LINE "if $v == 0 {" + LINE " b{}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + test_assert(ecs_lookup(world, "a") != 0); + test_assert(ecs_lookup(world, "b") == 0); + + ecs_fini(world); +} + +void Eval_if_else_space_newline_if(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const v = 1" + LINE "if $v == 1 {" + LINE " a{}" + LINE "} else " + LINE "if $v == 0 {" + LINE " b{}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + test_assert(ecs_lookup(world, "a") != 0); + test_assert(ecs_lookup(world, "b") == 0); + + ecs_fini(world); +} + void Eval_isa_in_module(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 1365dbb0a..7bebca986 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -223,6 +223,11 @@ void Eval_if_true_in_scope(void); void Eval_if_false_in_scope(void); void Eval_if_lt(void); void Eval_if_lt_const(void); +void Eval_if_else_if(void); +void Eval_if_else_if_else(void); +void Eval_if_else_if_else_if(void); +void Eval_if_else_newline_if(void); +void Eval_if_else_space_newline_if(void); void Eval_isa_in_module(void); void Eval_isa_hierarchy(void); void Eval_isa_hierarchy_in_module(void); @@ -1738,6 +1743,26 @@ bake_test_case Eval_testcases[] = { "if_lt_const", Eval_if_lt_const }, + { + "if_else_if", + Eval_if_else_if + }, + { + "if_else_if_else", + Eval_if_else_if_else + }, + { + "if_else_if_else_if", + Eval_if_else_if_else_if + }, + { + "if_else_newline_if", + Eval_if_else_newline_if + }, + { + "if_else_space_newline_if", + Eval_if_else_space_newline_if + }, { "isa_in_module", Eval_isa_in_module @@ -4334,7 +4359,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 292, + 297, Eval_testcases }, { From a5539a63be8a9f6da033c4e0313bc10f3c7312e3 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 26 Dec 2024 22:01:20 -0800 Subject: [PATCH 15/36] Change meaning of standalone script scope to anonymous entity --- distr/flecs.c | 8 ++++-- docs/FlecsScript.md | 33 ++++++++++++++++++++++- src/addons/script/parser.c | 8 ++++-- test/script/src/Eval.c | 55 +++++++++++++++++++++++++++++--------- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 44cf90c2f..3d4b9691d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56755,8 +56755,7 @@ const char* flecs_script_stmt( Parse( case EcsTokIdentifier: goto identifier; case EcsTokString: goto string_name; - case '{': return flecs_script_scope(parser, - flecs_script_insert_scope(parser), pos); + case '{': goto anonymous_entity; case '(': goto paren; case '@': goto annotation; case EcsTokKeywordWith: goto with_stmt; @@ -56770,6 +56769,11 @@ const char* flecs_script_stmt( EcsTokEndOfStatement: EndOfRule; ); +anonymous_entity: { + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, "_", false)->scope, pos); +} + string_name: /* If this is an interpolated string, we need to evaluate it as expression * at evaluation time. Otherwise we can just use the string as name. The diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 85342d6e2..11dbea4d5 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -60,7 +60,15 @@ my_parent { Note how a scope is also added to the child entity. -To create anonymous entities, use the `_` identifier: +To create anonymous entities, leave out the entity name: + +```c +{ + my_child {} // named child with anonymous parent +} +``` + +Alternatively, the `_` placeholder can be used to indicate an anomyous entity: ```c _ { @@ -68,6 +76,13 @@ _ { } ``` +The `_` placeholder can be useful in combination with syntax constructs that require an identifier token, such as inheritance: + +```c +// anonymous entity that inherits from SpaceShip +_ : SpaceShip { } +``` + Entity names can be specified using a string. This allows for entities with names that contain special characters, like spaces: ```c @@ -970,6 +985,22 @@ lantern { } ``` +If statements can be chained with `else if`: + +```c +const state = 0 + +traffic_light { + if $state == 0 { + Color: {0, 1, 0} + } else if $state == 1 { + Color: {0.5, 0.5, 0} + } else if $state == 1 { + Color: {1, 0, 0} + } +} +``` + ### For statement Parts of a script can be repeated with a for loop. Example: diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 5b911620c..076df0308 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -298,8 +298,7 @@ const char* flecs_script_stmt( Parse( case EcsTokIdentifier: goto identifier; case EcsTokString: goto string_name; - case '{': return flecs_script_scope(parser, - flecs_script_insert_scope(parser), pos); + case '{': goto anonymous_entity; case '(': goto paren; case '@': goto annotation; case EcsTokKeywordWith: goto with_stmt; @@ -313,6 +312,11 @@ const char* flecs_script_stmt( EcsTokEndOfStatement: EndOfRule; ); +anonymous_entity: { + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, "_", false)->scope, pos); +} + string_name: /* If this is an interpolated string, we need to evaluate it as expression * at evaluation time. Otherwise we can just use the string as name. The diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 4a956064d..423947b6e 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -2297,12 +2297,14 @@ void Eval_dot_assign_binary_expr(void) { void Eval_open_scope_no_parent(void) { ecs_world_t *world = ecs_init(); + ECS_TAG(world, Tag); + const char *expr = HEAD "Foo {" LINE " Bar {}" LINE "}" LINE "{" - LINE " Zoo {}" + LINE " Zoo { Tag }" LINE "}" LINE "Hello {}"; @@ -2314,11 +2316,16 @@ void Eval_open_scope_no_parent(void) { test_assert(foo != 0); test_assert(bar != 0); - test_assert(zoo != 0); + test_assert(zoo == 0); test_assert(hello != 0); + ecs_iter_t it = ecs_each(world, Tag); + zoo = ecs_iter_first(&it); + test_assert(zoo != 0); + test_str("Zoo", ecs_get_name(world, zoo)); + test_assert(ecs_has_pair(world, bar, EcsChildOf, foo)); - test_assert(!ecs_has_pair(world, zoo, EcsChildOf, EcsWildcard)); + test_assert(ecs_has_pair(world, zoo, EcsChildOf, EcsWildcard)); test_assert(!ecs_has_pair(world, hello, EcsChildOf, EcsWildcard)); ecs_fini(world); @@ -2469,6 +2476,7 @@ void Eval_using_nested_in_scope(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Zoo); + ECS_TAG(world, Tag); const char *expr = HEAD "Foo {" @@ -2478,7 +2486,7 @@ void Eval_using_nested_in_scope(void) { LINE "}" LINE "{" LINE " using Foo.Bar" - LINE " Zoo Hello {}" + LINE " Zoo Hello { Tag }" LINE "}" LINE "Zoo World {}"; @@ -2494,11 +2502,15 @@ void Eval_using_nested_in_scope(void) { test_assert(foo != 0); test_assert(bar != 0); test_assert(zoo != 0); - test_assert(hello != 0); + test_assert(hello == 0); test_assert(_world != 0); test_assert(not_bar == 0); test_assert(zoo_root != 0); + ecs_iter_t it = ecs_each(world, Tag); + hello = ecs_iter_first(&it); + test_assert(hello != 0); + test_assert(_world != EcsWorld); /* sanity check, verified by other tests */ test_assert(ecs_has_id(world, hello, zoo)); @@ -2970,25 +2982,38 @@ void Eval_multiple_vars_single_line(void) { void Eval_2_stmts_in_scope_w_no_parent(void) { ecs_world_t *world = ecs_init(); + ECS_TAG(world, Tag); + const char *expr = HEAD "{" - LINE "Bar { }" - LINE "Foo { }" + LINE "Bar { Tag }" + LINE "Foo { Tag }" LINE "}"; test_assert(ecs_script_run(world, NULL, expr) == 0); ecs_entity_t foo = ecs_lookup(world, "Foo"); ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(foo == 0); + test_assert(bar == 0); + + ecs_iter_t it = ecs_each(world, Tag); + ecs_entity_t p = ecs_iter_first(&it); + test_assert(p != 0); + p = ecs_get_target(world, p, EcsChildOf, 0); + foo = ecs_lookup_from(world, p, "Foo"); + bar = ecs_lookup_from(world, p, "Bar"); test_assert(foo != 0); test_assert(bar != 0); test_assert( !ecs_has_id(world, foo, bar)); - test_assert( !ecs_has_pair(world, foo, EcsChildOf, bar)); + test_assert( ecs_has_id(world, foo, Tag)); + test_assert( ecs_has_pair(world, foo, EcsChildOf, p)); test_assert( !ecs_has_id(world, bar, foo)); - test_assert( !ecs_has_pair(world, bar, EcsChildOf, foo)); + test_assert( ecs_has_id(world, bar, Tag)); + test_assert( ecs_has_pair(world, bar, EcsChildOf, p)); ecs_fini(world); } @@ -2996,17 +3021,23 @@ void Eval_2_stmts_in_scope_w_no_parent(void) { void Eval_empty_scope_after_using(void) { ecs_world_t *world = ecs_init(); + ECS_TAG(world, Tag); + const char *expr = HEAD "using flecs.meta" LINE "{" - LINE " Foo {}" + LINE " Foo { Tag }" LINE "}"; test_assert(ecs_script_run(world, NULL, expr) == 0); ecs_entity_t foo = ecs_lookup(world, "Foo"); - test_assert( foo != 0); - test_assert( !ecs_has_pair(world, foo, EcsChildOf, EcsWildcard)); + test_assert( foo == 0); + + ecs_iter_t it = ecs_each(world, Tag); + foo = ecs_iter_first(&it); + + test_assert( ecs_has_pair(world, foo, EcsChildOf, EcsWildcard)); ecs_fini(world); } From 9332fe2c1f76bee8265c237f1f0839efdb94a659 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 26 Dec 2024 22:40:17 -0800 Subject: [PATCH 16/36] Fix issue with parsing empty initializers --- distr/flecs.c | 5 ++- src/addons/script/expr/parser.c | 4 +- src/addons/script/expr/visit_type.c | 1 + test/script/project.json | 2 + test/script/src/Deserialize.c | 67 +++++++++++++++++++++++++++++ test/script/src/main.c | 12 +++++- 6 files changed, 86 insertions(+), 5 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 3d4b9691d..677fda15f 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -75979,7 +75979,7 @@ const char* flecs_script_parse_collection_initializer( /* End of initializer */ LookAhead_1(']', { - if (first) { + if (first) { node->node.kind = EcsExprEmptyInitializer; } pos = lookahead - 1; @@ -75998,7 +75998,7 @@ const char* flecs_script_parse_collection_initializer( } { - /* Parse next element or end of initializer*/ + /* Parse next element or end of initializer */ LookAhead( case ',': { pos = lookahead; @@ -80347,6 +80347,7 @@ int flecs_expr_visit_type_priv( } break; case EcsExprEmptyInitializer: + node->type = ecs_meta_get_type(cur); break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index d0ac57368..f9ad8d3dc 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -163,7 +163,7 @@ const char* flecs_script_parse_collection_initializer( /* End of initializer */ LookAhead_1(']', { - if (first) { + if (first) { node->node.kind = EcsExprEmptyInitializer; } pos = lookahead - 1; @@ -182,7 +182,7 @@ const char* flecs_script_parse_collection_initializer( } { - /* Parse next element or end of initializer*/ + /* Parse next element or end of initializer */ LookAhead( case ',': { pos = lookahead; diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index e2100dea7..0cdead1ba 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -1326,6 +1326,7 @@ int flecs_expr_visit_type_priv( } break; case EcsExprEmptyInitializer: + node->type = ecs_meta_get_type(cur); break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( diff --git a/test/script/project.json b/test/script/project.json index 78207ee8b..377be0211 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -852,6 +852,7 @@ "struct_nested_i32", "struct_nested_i32_i32", "struct_2_nested_i32_i32", + "struct_nested_empty", "struct_member_i32", "struct_member_i32_neg", "struct_member_i32_i32", @@ -867,6 +868,7 @@ "struct_nested_member_auto_vars", "struct_auto_vars", "struct_i32_array_3", + "struct_i32_array_3_empty", "struct_struct_i32_array_3", "struct_struct_i32_i32_array_3", "struct_w_array_type_i32_i32", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 28b7ab6da..e70eb61c4 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -1120,6 +1120,44 @@ void Deserialize_struct_nested_i32(void) { ecs_fini(world); } +void Deserialize_struct_nested_empty(void) { + typedef struct { + ecs_i32_t x; + } N1; + + typedef struct { + N1 n_1; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t n1 = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "N1"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"n_1", n1}, + } + }); + + T value = {{0}}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, + "{{}}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_int(value.n_1.x, 0); + + ecs_fini(world); +} + void Deserialize_struct_nested_i32_i32(void) { typedef struct { ecs_i32_t x; @@ -1816,6 +1854,35 @@ void Deserialize_struct_i32_array_3(void) { ecs_fini(world); } +void Deserialize_struct_i32_array_3_empty(void) { + typedef struct { + int32_t x[3]; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"x", ecs_id(ecs_i32_t), 3} + } + }); + + T value = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, + "{x: []}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_int(value.x[0], 0); + test_int(value.x[1], 0); + test_int(value.x[2], 0); + + ecs_fini(world); +} + void Deserialize_struct_struct_i32_array_3(void) { typedef struct { ecs_i32_t x; diff --git a/test/script/src/main.c b/test/script/src/main.c index 7bebca986..ffb7d3572 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -828,6 +828,7 @@ void Deserialize_struct_id(void); void Deserialize_struct_nested_i32(void); void Deserialize_struct_nested_i32_i32(void); void Deserialize_struct_2_nested_i32_i32(void); +void Deserialize_struct_nested_empty(void); void Deserialize_struct_member_i32(void); void Deserialize_struct_member_i32_neg(void); void Deserialize_struct_member_i32_i32(void); @@ -843,6 +844,7 @@ void Deserialize_struct_nested_member_auto_var(void); void Deserialize_struct_nested_member_auto_vars(void); void Deserialize_struct_auto_vars(void); void Deserialize_struct_i32_array_3(void); +void Deserialize_struct_i32_array_3_empty(void); void Deserialize_struct_struct_i32_array_3(void); void Deserialize_struct_struct_i32_i32_array_3(void); void Deserialize_struct_w_array_type_i32_i32(void); @@ -4120,6 +4122,10 @@ bake_test_case Deserialize_testcases[] = { "struct_2_nested_i32_i32", Deserialize_struct_2_nested_i32_i32 }, + { + "struct_nested_empty", + Deserialize_struct_nested_empty + }, { "struct_member_i32", Deserialize_struct_member_i32 @@ -4180,6 +4186,10 @@ bake_test_case Deserialize_testcases[] = { "struct_i32_array_3", Deserialize_struct_i32_array_3 }, + { + "struct_i32_array_3_empty", + Deserialize_struct_i32_array_3_empty + }, { "struct_struct_i32_array_3", Deserialize_struct_struct_i32_array_3 @@ -4410,7 +4420,7 @@ static bake_test_suite suites[] = { "Deserialize", Deserialize_setup, NULL, - 90, + 92, Deserialize_testcases, 1, Deserialize_params From 37c374b088bbef01da2a914ef26ddf2759f4c603 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 26 Dec 2024 23:43:35 -0800 Subject: [PATCH 17/36] Implement syntax for match expressions --- distr/flecs.c | 122 +++++++++++++++++++++++++++++++- src/addons/script/expr/ast.c | 8 +++ src/addons/script/expr/ast.h | 17 ++++- src/addons/script/expr/parser.c | 89 +++++++++++++++++++++++ src/addons/script/tokenizer.c | 7 +- src/addons/script/tokenizer.h | 1 + 6 files changed, 238 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 677fda15f..502fc20e1 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4557,6 +4557,7 @@ typedef enum ecs_script_token_kind_t { EcsTokKeywordTemplate = 122, EcsTokKeywordProp = 130, EcsTokKeywordConst = 131, + EcsTokKeywordMatch = 132, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { @@ -4970,7 +4971,8 @@ typedef enum ecs_expr_node_kind_t { EcsExprElement, EcsExprComponent, EcsExprCast, - EcsExprCastNumber + EcsExprCastNumber, + EcsExprMatch } ecs_expr_node_kind_t; struct ecs_expr_node_t { @@ -5068,6 +5070,17 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; +typedef struct ecs_expr_match_element_t { + ecs_expr_node_t *compare; + ecs_expr_node_t *expr; +} ecs_expr_match_element_t; + +typedef struct ecs_expr_match_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_vec_t elements; +} ecs_expr_match_t; + ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, @@ -5132,6 +5145,9 @@ ecs_expr_function_t* flecs_expr_function( ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); +ecs_expr_match_t* flecs_expr_match( + ecs_script_parser_t *parser); + ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *node, @@ -59686,14 +59702,15 @@ const char* flecs_script_token_kind_str( return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: - case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordTemplate: case EcsTokKeywordModule: + case EcsTokKeywordMatch: return "keyword "; case EcsTokIdentifier: return "identifier "; @@ -59751,13 +59768,14 @@ const char* flecs_script_token_str( case EcsTokShiftRight: return ">>"; case EcsTokKeywordWith: return "with"; case EcsTokKeywordUsing: return "using"; - case EcsTokKeywordTemplate: return "template"; case EcsTokKeywordProp: return "prop"; case EcsTokKeywordConst: return "const"; + case EcsTokKeywordMatch: return "match"; case EcsTokKeywordIf: return "if"; case EcsTokKeywordElse: return "else"; case EcsTokKeywordFor: return "for"; case EcsTokKeywordIn: return "in"; + case EcsTokKeywordTemplate: return "template"; case EcsTokKeywordModule: return "module"; case EcsTokIdentifier: return "identifier"; case EcsTokString: return "string"; @@ -60196,6 +60214,7 @@ const char* flecs_script_token( Keyword ("else", EcsTokKeywordElse) Keyword ("for", EcsTokKeywordFor) Keyword ("in", EcsTokKeywordIn) + Keyword ("match", EcsTokKeywordMatch) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { @@ -75716,6 +75735,14 @@ ecs_expr_element_t* flecs_expr_element( return result; } +ecs_expr_match_t* flecs_expr_match( + ecs_script_parser_t *parser) +{ + ecs_expr_match_t *result = flecs_expr_ast_new( + parser, ecs_expr_match_t, EcsExprMatch); + return result; +} + static bool flecs_expr_explicit_cast_allowed( ecs_world_t *world, @@ -75888,6 +75915,66 @@ ecs_entity_t flecs_script_default_lookup( return ecs_lookup(world, name); } +static +const char* flecs_script_parse_match_elems( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_match_t *node) +{ + ecs_allocator_t *a = &parser->script->allocator; + bool old_significant_newline = parser->significant_newline; + parser->significant_newline = true; + + printf("parse match elems: %s\n", pos); + + do { + ParserBegin; + + LookAhead( + case '\n': { + pos = lookahead; + continue; + } + + case '}': { + /* Return last character of initializer */ + pos = lookahead - 1; + printf("return '%s'\n", pos); + parser->significant_newline = old_significant_newline; + EndOfRule; + } + ) + + ecs_expr_match_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_match_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->compare); + if (!pos) { + goto error; + } + + Parse_1(':', { + pos = flecs_script_parse_expr(parser, pos, 0, &elem->expr); + if (!pos) { + goto error; + } + + Parse( + case ';': + case '\n': { + break; + } + ) + + break; + }) + + } while (true); + + ParserEnd; +} + const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, @@ -76244,6 +76331,35 @@ const char* flecs_script_parse_lhs( break; } + case EcsTokKeywordMatch: { + ecs_expr_match_t *node = flecs_expr_match(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &node->expr); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } + + Parse_1('{', { + pos = flecs_script_parse_match_elems(parser, pos, node); + if (!pos) { + flecs_script_parser_expr_free( + parser, (ecs_expr_node_t*)node); + goto error; + } + + Parse_1('}', { + *out = (ecs_expr_node_t*)node; + break; + }) + + break; + }) + + can_have_rhs = false; + + break; + } + case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); if (!pos) { diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 10efdaf19..8ecbf68d2 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -221,6 +221,14 @@ ecs_expr_element_t* flecs_expr_element( return result; } +ecs_expr_match_t* flecs_expr_match( + ecs_script_parser_t *parser) +{ + ecs_expr_match_t *result = flecs_expr_ast_new( + parser, ecs_expr_match_t, EcsExprMatch); + return result; +} + static bool flecs_expr_explicit_cast_allowed( ecs_world_t *world, diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index 1d599d43d..e0ff067ec 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -24,7 +24,8 @@ typedef enum ecs_expr_node_kind_t { EcsExprElement, EcsExprComponent, EcsExprCast, - EcsExprCastNumber + EcsExprCastNumber, + EcsExprMatch } ecs_expr_node_kind_t; struct ecs_expr_node_t { @@ -122,6 +123,17 @@ typedef struct ecs_expr_cast_t { ecs_expr_node_t *expr; } ecs_expr_cast_t; +typedef struct ecs_expr_match_element_t { + ecs_expr_node_t *compare; + ecs_expr_node_t *expr; +} ecs_expr_match_element_t; + +typedef struct ecs_expr_match_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_vec_t elements; +} ecs_expr_match_t; + ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, @@ -186,6 +198,9 @@ ecs_expr_function_t* flecs_expr_function( ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); +ecs_expr_match_t* flecs_expr_match( + ecs_script_parser_t *parser); + ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *node, diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index f9ad8d3dc..025eff9fe 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -72,6 +72,66 @@ ecs_entity_t flecs_script_default_lookup( return ecs_lookup(world, name); } +static +const char* flecs_script_parse_match_elems( + ecs_script_parser_t *parser, + const char *pos, + ecs_expr_match_t *node) +{ + ecs_allocator_t *a = &parser->script->allocator; + bool old_significant_newline = parser->significant_newline; + parser->significant_newline = true; + + printf("parse match elems: %s\n", pos); + + do { + ParserBegin; + + LookAhead( + case '\n': { + pos = lookahead; + continue; + } + + case '}': { + /* Return last character of initializer */ + pos = lookahead - 1; + printf("return '%s'\n", pos); + parser->significant_newline = old_significant_newline; + EndOfRule; + } + ) + + ecs_expr_match_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_match_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->compare); + if (!pos) { + goto error; + } + + Parse_1(':', { + pos = flecs_script_parse_expr(parser, pos, 0, &elem->expr); + if (!pos) { + goto error; + } + + Parse( + case ';': + case '\n': { + break; + } + ) + + break; + }) + + } while (true); + + ParserEnd; +} + const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, @@ -428,6 +488,35 @@ const char* flecs_script_parse_lhs( break; } + case EcsTokKeywordMatch: { + ecs_expr_match_t *node = flecs_expr_match(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &node->expr); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } + + Parse_1('{', { + pos = flecs_script_parse_match_elems(parser, pos, node); + if (!pos) { + flecs_script_parser_expr_free( + parser, (ecs_expr_node_t*)node); + goto error; + } + + Parse_1('}', { + *out = (ecs_expr_node_t*)node; + break; + }) + + break; + }) + + can_have_rhs = false; + + break; + } + case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); if (!pos) { diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index ee6bcbf9d..2976294fe 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -71,14 +71,15 @@ const char* flecs_script_token_kind_str( return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: - case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordTemplate: case EcsTokKeywordModule: + case EcsTokKeywordMatch: return "keyword "; case EcsTokIdentifier: return "identifier "; @@ -136,13 +137,14 @@ const char* flecs_script_token_str( case EcsTokShiftRight: return ">>"; case EcsTokKeywordWith: return "with"; case EcsTokKeywordUsing: return "using"; - case EcsTokKeywordTemplate: return "template"; case EcsTokKeywordProp: return "prop"; case EcsTokKeywordConst: return "const"; + case EcsTokKeywordMatch: return "match"; case EcsTokKeywordIf: return "if"; case EcsTokKeywordElse: return "else"; case EcsTokKeywordFor: return "for"; case EcsTokKeywordIn: return "in"; + case EcsTokKeywordTemplate: return "template"; case EcsTokKeywordModule: return "module"; case EcsTokIdentifier: return "identifier"; case EcsTokString: return "string"; @@ -581,6 +583,7 @@ const char* flecs_script_token( Keyword ("else", EcsTokKeywordElse) Keyword ("for", EcsTokKeywordFor) Keyword ("in", EcsTokKeywordIn) + Keyword ("match", EcsTokKeywordMatch) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index 98f3cac1f..bbc062f48 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -57,6 +57,7 @@ typedef enum ecs_script_token_kind_t { EcsTokKeywordTemplate = 122, EcsTokKeywordProp = 130, EcsTokKeywordConst = 131, + EcsTokKeywordMatch = 132, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { From a87c5566505e70b6fd63d7c600bb76216b979728 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 02:07:36 -0800 Subject: [PATCH 18/36] Implement basic evaluation of match expressions --- distr/flecs.c | 316 ++++++++++++++++++++++++-- src/addons/script/expr/parser.c | 3 - src/addons/script/expr/util.c | 2 + src/addons/script/expr/visit_eval.c | 65 ++++++ src/addons/script/expr/visit_fold.c | 36 +++ src/addons/script/expr/visit_free.c | 22 ++ src/addons/script/expr/visit_to_str.c | 40 ++++ src/addons/script/expr/visit_type.c | 148 ++++++++++-- test/script/project.json | 6 +- test/script/src/Eval.c | 44 ++++ test/script/src/main.c | 22 +- 11 files changed, 658 insertions(+), 46 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 502fc20e1..70c591b14 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -75925,8 +75925,6 @@ const char* flecs_script_parse_match_elems( bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; - printf("parse match elems: %s\n", pos); - do { ParserBegin; @@ -75939,7 +75937,6 @@ const char* flecs_script_parse_match_elems( case '}': { /* Return last character of initializer */ pos = lookahead - 1; - printf("return '%s'\n", pos); parser->significant_newline = old_significant_newline; EndOfRule; } @@ -76838,6 +76835,7 @@ int flecs_value_unary( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -76947,6 +76945,7 @@ int flecs_value_binary( case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordTemplate: + case EcsTokKeywordMatch: case EcsTokKeywordProp: case EcsTokKeywordConst: default: @@ -77752,6 +77751,64 @@ int flecs_expr_element_visit_eval( return -1; } +static +int flecs_expr_match_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_match_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + + flecs_expr_stack_push(ctx->stack); + ecs_expr_value_t *compare = flecs_expr_stack_result( + ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, elem->compare, compare)) { + goto error; + } + + bool value = false; + ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; + + if (flecs_value_binary( + ctx->script, &expr->value, &compare->value, &result, EcsTokEq)) + { + goto error; + } + + flecs_expr_stack_pop(ctx->stack); + + if (value) { + if (flecs_expr_visit_eval_priv(ctx, elem->expr, out)) { + goto error; + } + break; + } + } + + if (i == count) { + flecs_expr_visit_error(ctx->script, node, + "match value not handled by case"); + goto error; + } + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_component_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -77893,6 +77950,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_eval( + ctx, (ecs_expr_match_t*)node, out)) + { + goto error; + } + break; case EcsExprComponent: if (flecs_expr_component_visit_eval( ctx, (ecs_expr_element_t*)node, out)) @@ -78500,6 +78564,37 @@ int flecs_expr_element_visit_fold( return -1; } +static +int flecs_expr_match_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; + + if (flecs_expr_visit_fold(script, &node->expr, desc)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_fold(script, &elem->compare, desc)) { + goto error; + } + + if (flecs_expr_visit_fold(script, &elem->expr, desc)) { + goto error; + } + } + + return 0; +error: + return -1; +} + int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -78564,6 +78659,11 @@ int flecs_expr_visit_fold( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { @@ -78683,6 +78783,23 @@ void flecs_expr_element_visit_free( flecs_expr_visit_free(script, node->index); } +static +void flecs_expr_match_visit_free( + ecs_script_t *script, + ecs_expr_match_t *node) +{ + flecs_expr_visit_free(script, node->expr); + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + flecs_expr_visit_free(script, elem->compare); + flecs_expr_visit_free(script, elem->expr); + } +} + static void flecs_expr_cast_visit_free( ecs_script_t *script, @@ -78754,6 +78871,11 @@ void flecs_expr_visit_free( script, (ecs_expr_element_t*)node); flecs_free_t(a, ecs_expr_element_t, node); break; + case EcsExprMatch: + flecs_expr_match_visit_free( + script, (ecs_expr_match_t*)node); + flecs_free_t(a, ecs_expr_match_t, node); + break; case EcsExprCast: case EcsExprCastNumber: flecs_expr_cast_visit_free( @@ -78996,6 +79118,39 @@ int flecs_expr_element_to_str( return 0; } +static +int flecs_expr_match_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_match_t *node) +{ + ecs_strbuf_appendlit(v->buf, "match "); + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "{\n"); + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_node_to_str(v, elem->compare)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, ": "); + + if (flecs_expr_node_to_str(v, elem->expr)) { + return -1; + } + } + + ecs_strbuf_appendlit(v->buf, "}\n"); + + return 0; +} + static int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, @@ -79106,6 +79261,13 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_to_str(v, + (const ecs_expr_match_t*)node)) + { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_to_str(v, @@ -79418,6 +79580,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -79428,16 +79591,19 @@ bool flecs_expr_oper_valid_for_type( } static -int flecs_expr_type_for_oper( +int flecs_expr_type_for_operator( ecs_script_t *script, - ecs_expr_binary_t *node, + ecs_expr_node_t *node, /* Only used for error reporting */ + ecs_entity_t node_type, + ecs_expr_node_t *left, + ecs_expr_node_t *right, + ecs_script_token_kind_t operator, ecs_entity_t *operand_type, ecs_entity_t *result_type) { ecs_world_t *world = script->world; - ecs_expr_node_t *left = node->left, *right = node->right; - if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (operator == EcsTokDiv || operator == EcsTokMod) { if (right->kind == EcsExprValue) { ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; @@ -79449,7 +79615,7 @@ int flecs_expr_type_for_oper( } } - switch(node->operator) { + switch(operator) { case EcsTokDiv: /* Result type of a division is always a float */ if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ @@ -79521,6 +79687,7 @@ int flecs_expr_type_for_oper( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -79543,7 +79710,7 @@ int flecs_expr_type_for_oper( char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, - "invalid non-primitive type in binary expression (%s, %s)", + "invalid non-primitive type in expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); @@ -79558,19 +79725,19 @@ int flecs_expr_type_for_oper( /* If types are not the same, find the smallest type for literals that can * represent the value without losing precision. */ - ecs_entity_t ltype = flecs_expr_narrow_type(node->node.type, left); - ecs_entity_t rtype = flecs_expr_narrow_type(node->node.type, right); + ecs_entity_t ltype = flecs_expr_narrow_type(node_type, left); + ecs_entity_t rtype = flecs_expr_narrow_type(node_type, right); /* If types are not the same, try to implicitly cast to expression type */ - ltype = flecs_expr_cast_to_lvalue(node->node.type, ltype); - rtype = flecs_expr_cast_to_lvalue(node->node.type, rtype); + ltype = flecs_expr_cast_to_lvalue(node_type, ltype); + rtype = flecs_expr_cast_to_lvalue(node_type, rtype); if (ltype == rtype) { *operand_type = ltype; goto done; } - if (node->operator == EcsTokEq || node->operator == EcsTokNeq) { + if (operator == EcsTokEq || operator == EcsTokNeq) { /* If this is an equality comparison and one of the operands is a bool, * cast the other operand to a bool as well. This ensures that * expressions such as true == 2 evaluate to true. */ @@ -79643,7 +79810,7 @@ int flecs_expr_type_for_oper( char *ltype_str = ecs_id_str(world, ltype); char *rtype_str = ecs_id_str(world, rtype); flecs_expr_visit_error(script, node, - "incompatible types in binary expression (%s vs %s)", + "incompatible types in expression (%s vs %s)", ltype_str, rtype_str); ecs_os_free(ltype_str); ecs_os_free(rtype_str); @@ -79651,7 +79818,7 @@ int flecs_expr_type_for_oper( return -1; done: - if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + if (operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ *operand_type = ecs_id(ecs_i64_t); } @@ -79663,6 +79830,18 @@ int flecs_expr_type_for_oper( return 0; } +static +int flecs_expr_type_for_binary_expr( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) +{ + return flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, + node->node.type, node->left, node->right, node->operator, + operand_type, result_type); +} + static int flecs_expr_interpolated_string_visit_type( ecs_script_t *script, @@ -79950,7 +80129,7 @@ int flecs_expr_binary_visit_type( goto error; } - if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + if (flecs_expr_type_for_binary_expr(script, node, &operand_type, &result_type)) { goto error; } @@ -80443,6 +80622,90 @@ not_a_collection: { return -1; } +static +int flecs_expr_match_visit_type( + ecs_script_t *script, + ecs_expr_match_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + if (!count) { + flecs_expr_visit_error(script, node, + "match statement must have at least one case"); + goto error; + } + + /* Determine most expressive type of all elements */ + node->node.type = ecs_meta_get_type(cur); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_type_priv(script, elem->expr, cur, desc)) { + goto error; + } + + if (!node->node.type) { + node->node.type = elem->expr->type; + continue; + } + + ecs_entity_t result_type = 0, operand_type = 0; + + if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, + 0, + (ecs_expr_node_t*)node, elem->expr, EcsTokAdd, + &operand_type, &result_type)) + { + goto error; + } + + /* "Accumulate" most expressive type in result node */ + node->node.type = result_type; + } + + /* Loop over elements again, cast values to result type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (elem->expr->type != node->node.type) { + elem->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, elem->expr, node->node.type); + if (!elem->expr) { + goto error; + } + } + } + + /* Make sure that case values match the input type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_type_priv(script, elem->compare, cur, desc)) { + goto error; + } + + ecs_expr_node_t *compare = elem->compare; + if (compare->type != node->expr->type) { + compare->type = (ecs_expr_node_t*) + flecs_expr_cast(script, compare, node->expr->type); + if (!compare->type) { + goto error; + } + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_visit_type_priv( ecs_script_t *script, @@ -80464,6 +80727,11 @@ int flecs_expr_visit_type_priv( break; case EcsExprEmptyInitializer: node->type = ecs_meta_get_type(cur); + if (!node->type) { + flecs_expr_visit_error(script, node, + "unknown type for initializer"); + goto error; + } break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( @@ -80528,6 +80796,13 @@ int flecs_expr_visit_type_priv( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_type( + script, (ecs_expr_match_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: break; @@ -80538,11 +80813,8 @@ int flecs_expr_visit_type_priv( } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); - - if (node->type) { - node->type_info = ecs_get_type_info(script->world, node->type); - ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - } + node->type_info = ecs_get_type_info(script->world, node->type); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); return 0; error: diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index 025eff9fe..cdd3ac86f 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -82,8 +82,6 @@ const char* flecs_script_parse_match_elems( bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; - printf("parse match elems: %s\n", pos); - do { ParserBegin; @@ -96,7 +94,6 @@ const char* flecs_script_parse_match_elems( case '}': { /* Return last character of initializer */ pos = lookahead - 1; - printf("return '%s'\n", pos); parser->significant_newline = old_significant_newline; EndOfRule; } diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 8be1efc01..e3125ed00 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -123,6 +123,7 @@ int flecs_value_unary( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -232,6 +233,7 @@ int flecs_value_binary( case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordTemplate: + case EcsTokKeywordMatch: case EcsTokKeywordProp: case EcsTokKeywordConst: default: diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 53c81ec85..67804113e 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -691,6 +691,64 @@ int flecs_expr_element_visit_eval( return -1; } +static +int flecs_expr_match_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_match_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); + + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + + flecs_expr_stack_push(ctx->stack); + ecs_expr_value_t *compare = flecs_expr_stack_result( + ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, elem->compare, compare)) { + goto error; + } + + bool value = false; + ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; + + if (flecs_value_binary( + ctx->script, &expr->value, &compare->value, &result, EcsTokEq)) + { + goto error; + } + + flecs_expr_stack_pop(ctx->stack); + + if (value) { + if (flecs_expr_visit_eval_priv(ctx, elem->expr, out)) { + goto error; + } + break; + } + } + + if (i == count) { + flecs_expr_visit_error(ctx->script, node, + "match value not handled by case"); + goto error; + } + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} + static int flecs_expr_component_visit_eval( ecs_script_eval_ctx_t *ctx, @@ -832,6 +890,13 @@ int flecs_expr_visit_eval_priv( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_eval( + ctx, (ecs_expr_match_t*)node, out)) + { + goto error; + } + break; case EcsExprComponent: if (flecs_expr_component_visit_eval( ctx, (ecs_expr_element_t*)node, out)) diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 4569f4725..546139194 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -507,6 +507,37 @@ int flecs_expr_element_visit_fold( return -1; } +static +int flecs_expr_match_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; + + if (flecs_expr_visit_fold(script, &node->expr, desc)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_fold(script, &elem->compare, desc)) { + goto error; + } + + if (flecs_expr_visit_fold(script, &elem->expr, desc)) { + goto error; + } + } + + return 0; +error: + return -1; +} + int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, @@ -571,6 +602,11 @@ int flecs_expr_visit_fold( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 59daf83b3..cfd404198 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -104,6 +104,23 @@ void flecs_expr_element_visit_free( flecs_expr_visit_free(script, node->index); } +static +void flecs_expr_match_visit_free( + ecs_script_t *script, + ecs_expr_match_t *node) +{ + flecs_expr_visit_free(script, node->expr); + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + flecs_expr_visit_free(script, elem->compare); + flecs_expr_visit_free(script, elem->expr); + } +} + static void flecs_expr_cast_visit_free( ecs_script_t *script, @@ -175,6 +192,11 @@ void flecs_expr_visit_free( script, (ecs_expr_element_t*)node); flecs_free_t(a, ecs_expr_element_t, node); break; + case EcsExprMatch: + flecs_expr_match_visit_free( + script, (ecs_expr_match_t*)node); + flecs_free_t(a, ecs_expr_match_t, node); + break; case EcsExprCast: case EcsExprCastNumber: flecs_expr_cast_visit_free( diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 97756c5a5..9b5754f01 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -231,6 +231,39 @@ int flecs_expr_element_to_str( return 0; } +static +int flecs_expr_match_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_match_t *node) +{ + ecs_strbuf_appendlit(v->buf, "match "); + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, "{\n"); + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_node_to_str(v, elem->compare)) { + return -1; + } + + ecs_strbuf_appendlit(v->buf, ": "); + + if (flecs_expr_node_to_str(v, elem->expr)) { + return -1; + } + } + + ecs_strbuf_appendlit(v->buf, "}\n"); + + return 0; +} + static int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, @@ -341,6 +374,13 @@ int flecs_expr_node_to_str( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_to_str(v, + (const ecs_expr_match_t*)node)) + { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_to_str(v, diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 0cdead1ba..03aca9fe5 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -281,6 +281,7 @@ bool flecs_expr_oper_valid_for_type( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -291,16 +292,19 @@ bool flecs_expr_oper_valid_for_type( } static -int flecs_expr_type_for_oper( +int flecs_expr_type_for_operator( ecs_script_t *script, - ecs_expr_binary_t *node, + ecs_expr_node_t *node, /* Only used for error reporting */ + ecs_entity_t node_type, + ecs_expr_node_t *left, + ecs_expr_node_t *right, + ecs_script_token_kind_t operator, ecs_entity_t *operand_type, ecs_entity_t *result_type) { ecs_world_t *world = script->world; - ecs_expr_node_t *left = node->left, *right = node->right; - if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (operator == EcsTokDiv || operator == EcsTokMod) { if (right->kind == EcsExprValue) { ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; @@ -312,7 +316,7 @@ int flecs_expr_type_for_oper( } } - switch(node->operator) { + switch(operator) { case EcsTokDiv: /* Result type of a division is always a float */ if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ @@ -384,6 +388,7 @@ int flecs_expr_type_for_oper( case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: + case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: @@ -406,7 +411,7 @@ int flecs_expr_type_for_oper( char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, - "invalid non-primitive type in binary expression (%s, %s)", + "invalid non-primitive type in expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); @@ -421,19 +426,19 @@ int flecs_expr_type_for_oper( /* If types are not the same, find the smallest type for literals that can * represent the value without losing precision. */ - ecs_entity_t ltype = flecs_expr_narrow_type(node->node.type, left); - ecs_entity_t rtype = flecs_expr_narrow_type(node->node.type, right); + ecs_entity_t ltype = flecs_expr_narrow_type(node_type, left); + ecs_entity_t rtype = flecs_expr_narrow_type(node_type, right); /* If types are not the same, try to implicitly cast to expression type */ - ltype = flecs_expr_cast_to_lvalue(node->node.type, ltype); - rtype = flecs_expr_cast_to_lvalue(node->node.type, rtype); + ltype = flecs_expr_cast_to_lvalue(node_type, ltype); + rtype = flecs_expr_cast_to_lvalue(node_type, rtype); if (ltype == rtype) { *operand_type = ltype; goto done; } - if (node->operator == EcsTokEq || node->operator == EcsTokNeq) { + if (operator == EcsTokEq || operator == EcsTokNeq) { /* If this is an equality comparison and one of the operands is a bool, * cast the other operand to a bool as well. This ensures that * expressions such as true == 2 evaluate to true. */ @@ -506,7 +511,7 @@ int flecs_expr_type_for_oper( char *ltype_str = ecs_id_str(world, ltype); char *rtype_str = ecs_id_str(world, rtype); flecs_expr_visit_error(script, node, - "incompatible types in binary expression (%s vs %s)", + "incompatible types in expression (%s vs %s)", ltype_str, rtype_str); ecs_os_free(ltype_str); ecs_os_free(rtype_str); @@ -514,7 +519,7 @@ int flecs_expr_type_for_oper( return -1; done: - if (node->operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + if (operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ *operand_type = ecs_id(ecs_i64_t); } @@ -526,6 +531,18 @@ int flecs_expr_type_for_oper( return 0; } +static +int flecs_expr_type_for_binary_expr( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) +{ + return flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, + node->node.type, node->left, node->right, node->operator, + operand_type, result_type); +} + static int flecs_expr_interpolated_string_visit_type( ecs_script_t *script, @@ -813,7 +830,7 @@ int flecs_expr_binary_visit_type( goto error; } - if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + if (flecs_expr_type_for_binary_expr(script, node, &operand_type, &result_type)) { goto error; } @@ -1306,6 +1323,90 @@ not_a_collection: { return -1; } +static +int flecs_expr_match_visit_type( + ecs_script_t *script, + ecs_expr_match_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { + goto error; + } + + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); + + if (!count) { + flecs_expr_visit_error(script, node, + "match statement must have at least one case"); + goto error; + } + + /* Determine most expressive type of all elements */ + node->node.type = ecs_meta_get_type(cur); + + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_type_priv(script, elem->expr, cur, desc)) { + goto error; + } + + if (!node->node.type) { + node->node.type = elem->expr->type; + continue; + } + + ecs_entity_t result_type = 0, operand_type = 0; + + if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, + 0, + (ecs_expr_node_t*)node, elem->expr, EcsTokAdd, + &operand_type, &result_type)) + { + goto error; + } + + /* "Accumulate" most expressive type in result node */ + node->node.type = result_type; + } + + /* Loop over elements again, cast values to result type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (elem->expr->type != node->node.type) { + elem->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, elem->expr, node->node.type); + if (!elem->expr) { + goto error; + } + } + } + + /* Make sure that case values match the input type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_type_priv(script, elem->compare, cur, desc)) { + goto error; + } + + ecs_expr_node_t *compare = elem->compare; + if (compare->type != node->expr->type) { + compare->type = (ecs_expr_node_t*) + flecs_expr_cast(script, compare, node->expr->type); + if (!compare->type) { + goto error; + } + } + } + + return 0; +error: + return -1; +} + static int flecs_expr_visit_type_priv( ecs_script_t *script, @@ -1327,6 +1428,11 @@ int flecs_expr_visit_type_priv( break; case EcsExprEmptyInitializer: node->type = ecs_meta_get_type(cur); + if (!node->type) { + flecs_expr_visit_error(script, node, + "unknown type for initializer"); + goto error; + } break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( @@ -1391,6 +1497,13 @@ int flecs_expr_visit_type_priv( goto error; } break; + case EcsExprMatch: + if (flecs_expr_match_visit_type( + script, (ecs_expr_match_t*)node, cur, desc)) + { + goto error; + } + break; case EcsExprCast: case EcsExprCastNumber: break; @@ -1401,11 +1514,8 @@ int flecs_expr_visit_type_priv( } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); - - if (node->type) { - node->type_info = ecs_get_type_info(script->world, node->type); - ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - } + node->type_info = ecs_get_type_info(script->world, node->type); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); return 0; error: diff --git a/test/script/project.json b/test/script/project.json index 377be0211..8e7dabaab 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -309,7 +309,11 @@ "method_w_entity_arg", "method_w_entity_arg_w_using", "assign_id", - "assign_id_w_using" + "assign_id_w_using", + "const_assign_empty_initializer", + "const_assign_empty_collection_initializer", + "const_i32_assign_empty_initializer", + "const_i32_assign_empty_collection_initializer" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 423947b6e..45ea742db 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10018,3 +10018,47 @@ void Eval_assign_id_w_using(void) { ecs_fini(world); } + +void Eval_const_assign_empty_initializer(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const x = {}"; + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Eval_const_assign_empty_collection_initializer(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const x = []"; + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Eval_const_i32_assign_empty_initializer(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const x = i32: {}"; + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} + +void Eval_const_i32_assign_empty_collection_initializer(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const x = i32: []"; + + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index ffb7d3572..c1dff84b8 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -306,6 +306,10 @@ void Eval_method_w_entity_arg(void); void Eval_method_w_entity_arg_w_using(void); void Eval_assign_id(void); void Eval_assign_id_w_using(void); +void Eval_const_assign_empty_initializer(void); +void Eval_const_assign_empty_collection_initializer(void); +void Eval_const_i32_assign_empty_initializer(void); +void Eval_const_i32_assign_empty_collection_initializer(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -2076,6 +2080,22 @@ bake_test_case Eval_testcases[] = { { "assign_id_w_using", Eval_assign_id_w_using + }, + { + "const_assign_empty_initializer", + Eval_const_assign_empty_initializer + }, + { + "const_assign_empty_collection_initializer", + Eval_const_assign_empty_collection_initializer + }, + { + "const_i32_assign_empty_initializer", + Eval_const_i32_assign_empty_initializer + }, + { + "const_i32_assign_empty_collection_initializer", + Eval_const_i32_assign_empty_collection_initializer } }; @@ -4369,7 +4389,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 297, + 301, Eval_testcases }, { From 2bd34e750493961c7b9674b58251b3974a247427 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 17:57:03 -0800 Subject: [PATCH 19/36] Add match test cases, fix bugs --- distr/flecs.c | 393 +++-- src/addons/script/expr/ast.c | 2 +- src/addons/script/expr/expr.h | 91 -- src/addons/script/expr/parser.c | 4 +- src/addons/script/expr/util.c | 101 ++ src/addons/script/expr/visit_free.c | 3 + src/addons/script/expr/visit_to_str.c | 30 +- src/addons/script/expr/visit_type.c | 143 +- src/addons/script/parser.c | 19 + test/script/project.json | 31 +- test/script/src/Expr.c | 2027 ++++++++++++++++++++++--- test/script/src/main.c | 147 +- 12 files changed, 2500 insertions(+), 491 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 70c591b14..73a8a0c8b 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5252,97 +5252,6 @@ bool flecs_expr_is_type_integer( bool flecs_expr_is_type_number( ecs_entity_t type); -#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) - -#define ECS_BOP(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) - -#define ECS_BOP_COND(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -/* Unsigned operations */ -#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_u32_t)) { \ - OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ - } else if ((left)->type == ecs_id(ecs_u16_t)) { \ - OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ - } - -/* Unsigned + signed operations */ -#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_i64_t)) { \ - OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_i32_t)) { \ - OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ - } else if ((left)->type == ecs_id(ecs_i16_t)) { \ - OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ - } else if ((left)->type == ecs_id(ecs_i8_t)) { \ - OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ - } - -/* Unsigned + signed + floating point operations */ -#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ - ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_f64_t)) { \ - OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else if ((left)->type == ecs_id(ecs_f32_t)) { \ - OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ - } - - -/* Combinations + error checking */ - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - #endif /** @@ -57181,6 +57090,13 @@ identifier_colon: { ) } + { + // Position: match + LookAhead_1(EcsTokKeywordMatch, + goto component_expr_match; + ) + } + // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( @@ -57356,6 +57272,18 @@ component_expr_collection: { }) } +// Position: match +component_expr_match: { + + // Position: match expr + Expr('\n', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) +} + ParserEnd; } @@ -75788,7 +75716,7 @@ bool flecs_expr_explicit_cast_allowed( if (to_type->kind == EcsEnumType || to_type->kind == EcsBitmaskType) { - /* Can integers to enums/bitmasks */ + /* Can cast integers to enums/bitmasks */ return true; } } @@ -75925,6 +75853,8 @@ const char* flecs_script_parse_match_elems( bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; + ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); + do { ParserBegin; @@ -76478,7 +76408,7 @@ ecs_script_t* ecs_expr_parse( goto error; } - // printf("%s\n", ecs_script_ast_to_str(script)); + // printf("%s\n", ecs_script_ast_to_str(script, true)); if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { @@ -76846,6 +76776,107 @@ int flecs_value_unary( return 0; } +#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) + +#define ECS_BOP(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) + +#define ECS_BOP_COND(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +/* Unsigned operations */ +#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ + } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ + } + +/* Unsigned + signed operations */ +#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_i64_t)) { \ + OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ + } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ + } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ + } + +/* Unsigned + signed + floating point operations */ +#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ + ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_f64_t)) { \ + OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ + } + + +/* Combinations + error checking */ + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else if ((left)->type == ecs_id(ecs_entity_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ + } else if ((left)->type == ecs_id(ecs_string_t)) { \ + char *lstr = *(char**)(left)->ptr;\ + char *rstr = *(char**)(right)->ptr;\ + if (lstr && rstr) {\ + *(bool*)(result)->ptr = ecs_os_strcmp(lstr, rstr) op 0;\ + } else {\ + *(bool*)(result)->ptr = lstr == rstr;\ + }\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + int flecs_value_binary( const ecs_script_t *script, const ecs_value_t *left, @@ -78798,6 +78829,9 @@ void flecs_expr_match_visit_free( flecs_expr_visit_free(script, elem->compare); flecs_expr_visit_free(script, elem->expr); } + + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->elements, ecs_expr_match_element_t); } static @@ -79123,17 +79157,35 @@ int flecs_expr_match_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_match_t *node) { + if (node->node.type) { + flecs_expr_color_to_str(v, ECS_BLUE); + const char *name = ecs_get_name(v->world, node->node.type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, node->node.type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); + } + + flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "match "); + flecs_expr_color_to_str(v, ECS_GREEN); if (flecs_expr_node_to_str(v, node->expr)) { return -1; } - ecs_strbuf_appendlit(v->buf, "{\n"); + ecs_strbuf_appendlit(v->buf, " {\n"); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { + ecs_strbuf_appendlit(v->buf, " "); + ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_node_to_str(v, elem->compare)) { return -1; @@ -79144,9 +79196,17 @@ int flecs_expr_match_to_str( if (flecs_expr_node_to_str(v, elem->expr)) { return -1; } + + ecs_strbuf_appendlit(v->buf, "\n"); } - ecs_strbuf_appendlit(v->buf, "}\n"); + ecs_strbuf_appendlit(v->buf, "}"); + + if (node->node.type) { + ecs_strbuf_appendlit(v->buf, ")"); + } + + ecs_strbuf_appendlit(v->buf, "\n"); return 0; } @@ -79317,23 +79377,7 @@ int flecs_expr_visit_type_priv( bool flecs_expr_is_type_integer( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return false; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -bool flecs_expr_is_type_number( - ecs_entity_t type) -{ - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return true; + if (type == ecs_id(ecs_u8_t)) return true; else if (type == ecs_id(ecs_u16_t)) return true; else if (type == ecs_id(ecs_u32_t)) return true; else if (type == ecs_id(ecs_u64_t)) return true; @@ -79343,10 +79387,15 @@ bool flecs_expr_is_type_number( else if (type == ecs_id(ecs_i32_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_iptr_t)) return true; - else if (type == ecs_id(ecs_f32_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +bool flecs_expr_is_type_number( + ecs_entity_t type) +{ + if (flecs_expr_is_type_integer(type)) return true; + else if (type == ecs_id(ecs_f32_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; else return false; } @@ -79696,10 +79745,18 @@ int flecs_expr_type_for_operator( ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } + /* If one of the types is an entity or id, the other one should be also */ + if (left->type == ecs_id(ecs_entity_t) || + right->type == ecs_id(ecs_entity_t)) + { + *operand_type = ecs_id(ecs_entity_t); + goto done; + } + const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { - /* Only primitives and bitmask constants are allowed */ + /* Only primitives, bitmask constants and enums are allowed */ if (left->type == right->type) { if (ecs_get(world, left->type, EcsBitmask) != NULL) { *operand_type = left->type; @@ -79707,6 +79764,22 @@ int flecs_expr_type_for_operator( } } + { + const EcsEnum *ptr = ecs_get(script->world, left->type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; + } + } + + { + const EcsEnum *ptr = ecs_get(script->world, right->type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; + } + } + char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, @@ -79827,6 +79900,10 @@ int flecs_expr_type_for_operator( *result_type = *operand_type; } + if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { + *operand_type = ecs_id(ecs_u64_t); + } + return 0; } @@ -80119,6 +80196,13 @@ int flecs_expr_binary_visit_type( /* Provides a hint to the type visitor. The lvalue type will be used to * reduce the number of casts where possible. */ node->node.type = ecs_meta_get_type(cur); + + /* If the result of the binary expression is a boolean it's likely a + * conditional expression. We don't want to hint that the operands + * of conditional expressions should be casted to booleans. */ + if (node->node.type == ecs_id(ecs_bool_t)) { + ecs_os_zeromem(cur); + } } if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { @@ -80129,7 +80213,9 @@ int flecs_expr_binary_visit_type( goto error; } - if (flecs_expr_type_for_binary_expr(script, node, &operand_type, &result_type)) { + if (flecs_expr_type_for_binary_expr( + script, node, &operand_type, &result_type)) + { goto error; } @@ -80143,10 +80229,6 @@ int flecs_expr_binary_visit_type( goto error; } - if (ecs_get(script->world, result_type, EcsBitmask) != NULL) { - operand_type = ecs_id(ecs_u64_t); - } - if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); @@ -80205,9 +80287,9 @@ int flecs_expr_identifier_visit_type( result = NULL; } } else { - ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&tmp_cur, node->value)) { + if (ecs_meta_set_string(&expr_cur, node->value)) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } @@ -80631,7 +80713,9 @@ int flecs_expr_match_visit_type( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { + ecs_meta_cursor_t expr_cur; + ecs_os_zeromem(&expr_cur); + if (flecs_expr_visit_type_priv(script, node->expr, &expr_cur, desc)) { goto error; } @@ -80649,7 +80733,14 @@ int flecs_expr_match_visit_type( for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - if (flecs_expr_visit_type_priv(script, elem->expr, cur, desc)) { + + if (node->node.type) { + expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); + } else { + ecs_os_zeromem(&expr_cur); + } + + if (flecs_expr_visit_type_priv(script, elem->expr, &expr_cur, desc)) { goto error; } @@ -80658,18 +80749,31 @@ int flecs_expr_match_visit_type( continue; } - ecs_entity_t result_type = 0, operand_type = 0; + if (flecs_expr_is_type_number(node->node.type)) { + ecs_entity_t result_type = 0, operand_type = 0; + if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, 0, + (ecs_expr_node_t*)node, elem->expr, + EcsTokAdd, /* Use operator that doesn't change types */ + &operand_type, &result_type)) + { + goto error; + } - if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, - 0, - (ecs_expr_node_t*)node, elem->expr, EcsTokAdd, - &operand_type, &result_type)) - { - goto error; + /* "Accumulate" most expressive type in result node */ + node->node.type = result_type; + } else { + /* If type is not a number it must match exactly */ + if (elem->expr->type != node->node.type) { + char *got = ecs_get_path(script->world, elem->expr->type); + char *expect = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid type for case %d in match (got %s, expected %s)", + i + 1, got, expect); + ecs_os_free(got); + ecs_os_free(expect); + goto error; + } } - - /* "Accumulate" most expressive type in result node */ - node->node.type = result_type; } /* Loop over elements again, cast values to result type */ @@ -80684,18 +80788,29 @@ int flecs_expr_match_visit_type( } } + /* If this is an enum type, cast to the underlying type. This is necessary + * because the compare operation executed by the match evaluation code isn't + * implemented for enums. */ + ecs_entity_t expr_type = node->expr->type; + const EcsEnum *ptr = ecs_get(script->world, expr_type, EcsEnum); + if (ptr) { + node->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, node->expr, ptr->underlying_type); + } + /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - if (flecs_expr_visit_type_priv(script, elem->compare, cur, desc)) { + expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); + if (flecs_expr_visit_type_priv(script, elem->compare, &expr_cur, desc)) { goto error; } ecs_expr_node_t *compare = elem->compare; if (compare->type != node->expr->type) { - compare->type = (ecs_expr_node_t*) + elem->compare = (ecs_expr_node_t*) flecs_expr_cast(script, compare, node->expr->type); - if (!compare->type) { + if (!elem->compare) { goto error; } } diff --git a/src/addons/script/expr/ast.c b/src/addons/script/expr/ast.c index 8ecbf68d2..e2680945a 100644 --- a/src/addons/script/expr/ast.c +++ b/src/addons/script/expr/ast.c @@ -274,7 +274,7 @@ bool flecs_expr_explicit_cast_allowed( if (to_type->kind == EcsEnumType || to_type->kind == EcsBitmaskType) { - /* Can integers to enums/bitmasks */ + /* Can cast integers to enums/bitmasks */ return true; } } diff --git a/src/addons/script/expr/expr.h b/src/addons/script/expr/expr.h index 94f54b842..29381d7ae 100644 --- a/src/addons/script/expr/expr.h +++ b/src/addons/script/expr/expr.h @@ -66,95 +66,4 @@ bool flecs_expr_is_type_integer( bool flecs_expr_is_type_number( ecs_entity_t type); -#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) - -#define ECS_BOP(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) - -#define ECS_BOP_COND(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -/* Unsigned operations */ -#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ - OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_u32_t)) { \ - OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ - } else if ((left)->type == ecs_id(ecs_u16_t)) { \ - OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ - } - -/* Unsigned + signed operations */ -#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_i64_t)) { \ - OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_i32_t)) { \ - OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ - } else if ((left)->type == ecs_id(ecs_i16_t)) { \ - OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ - } else if ((left)->type == ecs_id(ecs_i8_t)) { \ - OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ - } - -/* Unsigned + signed + floating point operations */ -#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ - ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_f64_t)) { \ - OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else if ((left)->type == ecs_id(ecs_f32_t)) { \ - OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ - } - - -/* Combinations + error checking */ - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_OP(left, right, result, op)\ - ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_bool_t)) { \ - ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - #endif diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index cdd3ac86f..db91b7741 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -82,6 +82,8 @@ const char* flecs_script_parse_match_elems( bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; + ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); + do { ParserBegin; @@ -635,7 +637,7 @@ ecs_script_t* ecs_expr_parse( goto error; } - // printf("%s\n", ecs_script_ast_to_str(script)); + // printf("%s\n", ecs_script_ast_to_str(script, true)); if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index e3125ed00..378d439cc 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -134,6 +134,107 @@ int flecs_value_unary( return 0; } +#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) + +#define ECS_BOP(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) + +#define ECS_BOP_COND(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +/* Unsigned operations */ +#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + if ((left)->type == ecs_id(ecs_u64_t)) { \ + OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ + } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ + } + +/* Unsigned + signed operations */ +#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_i64_t)) { \ + OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ + } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ + } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ + } + +/* Unsigned + signed + floating point operations */ +#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ + ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + else if ((left)->type == ecs_id(ecs_f64_t)) { \ + OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ + } + + +/* Combinations + error checking */ + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else if ((left)->type == ecs_id(ecs_entity_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ + } else if ((left)->type == ecs_id(ecs_string_t)) { \ + char *lstr = *(char**)(left)->ptr;\ + char *rstr = *(char**)(right)->ptr;\ + if (lstr && rstr) {\ + *(bool*)(result)->ptr = ecs_os_strcmp(lstr, rstr) op 0;\ + } else {\ + *(bool*)(result)->ptr = lstr == rstr;\ + }\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((left)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if ((left)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + int flecs_value_binary( const ecs_script_t *script, const ecs_value_t *left, diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index cfd404198..6d2c6e87d 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -119,6 +119,9 @@ void flecs_expr_match_visit_free( flecs_expr_visit_free(script, elem->compare); flecs_expr_visit_free(script, elem->expr); } + + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->elements, ecs_expr_match_element_t); } static diff --git a/src/addons/script/expr/visit_to_str.c b/src/addons/script/expr/visit_to_str.c index 9b5754f01..1a0529f5a 100644 --- a/src/addons/script/expr/visit_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -236,17 +236,35 @@ int flecs_expr_match_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_match_t *node) { + if (node->node.type) { + flecs_expr_color_to_str(v, ECS_BLUE); + const char *name = ecs_get_name(v->world, node->node.type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, node->node.type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); + } + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); + } + + flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "match "); + flecs_expr_color_to_str(v, ECS_GREEN); if (flecs_expr_node_to_str(v, node->expr)) { return -1; } - ecs_strbuf_appendlit(v->buf, "{\n"); + ecs_strbuf_appendlit(v->buf, " {\n"); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { + ecs_strbuf_appendlit(v->buf, " "); + ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_node_to_str(v, elem->compare)) { return -1; @@ -257,9 +275,17 @@ int flecs_expr_match_to_str( if (flecs_expr_node_to_str(v, elem->expr)) { return -1; } + + ecs_strbuf_appendlit(v->buf, "\n"); + } + + ecs_strbuf_appendlit(v->buf, "}"); + + if (node->node.type) { + ecs_strbuf_appendlit(v->buf, ")"); } - ecs_strbuf_appendlit(v->buf, "}\n"); + ecs_strbuf_appendlit(v->buf, "\n"); return 0; } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 03aca9fe5..07a043246 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -18,23 +18,7 @@ int flecs_expr_visit_type_priv( bool flecs_expr_is_type_integer( ecs_entity_t type) { - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return false; - else if (type == ecs_id(ecs_u64_t)) return true; - else if (type == ecs_id(ecs_i64_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return false; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -bool flecs_expr_is_type_number( - ecs_entity_t type) -{ - if (type == ecs_id(ecs_bool_t)) return false; - else if (type == ecs_id(ecs_char_t)) return false; - else if (type == ecs_id(ecs_u8_t)) return true; + if (type == ecs_id(ecs_u8_t)) return true; else if (type == ecs_id(ecs_u16_t)) return true; else if (type == ecs_id(ecs_u32_t)) return true; else if (type == ecs_id(ecs_u64_t)) return true; @@ -44,10 +28,15 @@ bool flecs_expr_is_type_number( else if (type == ecs_id(ecs_i32_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_iptr_t)) return true; - else if (type == ecs_id(ecs_f32_t)) return true; - else if (type == ecs_id(ecs_f64_t)) return true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +bool flecs_expr_is_type_number( + ecs_entity_t type) +{ + if (flecs_expr_is_type_integer(type)) return true; + else if (type == ecs_id(ecs_f32_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; else return false; } @@ -397,10 +386,18 @@ int flecs_expr_type_for_operator( ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } + /* If one of the types is an entity or id, the other one should be also */ + if (left->type == ecs_id(ecs_entity_t) || + right->type == ecs_id(ecs_entity_t)) + { + *operand_type = ecs_id(ecs_entity_t); + goto done; + } + const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { - /* Only primitives and bitmask constants are allowed */ + /* Only primitives, bitmask constants and enums are allowed */ if (left->type == right->type) { if (ecs_get(world, left->type, EcsBitmask) != NULL) { *operand_type = left->type; @@ -408,6 +405,22 @@ int flecs_expr_type_for_operator( } } + { + const EcsEnum *ptr = ecs_get(script->world, left->type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; + } + } + + { + const EcsEnum *ptr = ecs_get(script->world, right->type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; + } + } + char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, @@ -528,6 +541,10 @@ int flecs_expr_type_for_operator( *result_type = *operand_type; } + if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { + *operand_type = ecs_id(ecs_u64_t); + } + return 0; } @@ -820,6 +837,13 @@ int flecs_expr_binary_visit_type( /* Provides a hint to the type visitor. The lvalue type will be used to * reduce the number of casts where possible. */ node->node.type = ecs_meta_get_type(cur); + + /* If the result of the binary expression is a boolean it's likely a + * conditional expression. We don't want to hint that the operands + * of conditional expressions should be casted to booleans. */ + if (node->node.type == ecs_id(ecs_bool_t)) { + ecs_os_zeromem(cur); + } } if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { @@ -830,7 +854,9 @@ int flecs_expr_binary_visit_type( goto error; } - if (flecs_expr_type_for_binary_expr(script, node, &operand_type, &result_type)) { + if (flecs_expr_type_for_binary_expr( + script, node, &operand_type, &result_type)) + { goto error; } @@ -844,10 +870,6 @@ int flecs_expr_binary_visit_type( goto error; } - if (ecs_get(script->world, result_type, EcsBitmask) != NULL) { - operand_type = ecs_id(ecs_u64_t); - } - if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); @@ -906,9 +928,9 @@ int flecs_expr_identifier_visit_type( result = NULL; } } else { - ecs_meta_cursor_t tmp_cur = ecs_meta_cursor( + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&tmp_cur, node->value)) { + if (ecs_meta_set_string(&expr_cur, node->value)) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } @@ -1332,7 +1354,9 @@ int flecs_expr_match_visit_type( { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { + ecs_meta_cursor_t expr_cur; + ecs_os_zeromem(&expr_cur); + if (flecs_expr_visit_type_priv(script, node->expr, &expr_cur, desc)) { goto error; } @@ -1350,7 +1374,14 @@ int flecs_expr_match_visit_type( for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - if (flecs_expr_visit_type_priv(script, elem->expr, cur, desc)) { + + if (node->node.type) { + expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); + } else { + ecs_os_zeromem(&expr_cur); + } + + if (flecs_expr_visit_type_priv(script, elem->expr, &expr_cur, desc)) { goto error; } @@ -1359,18 +1390,31 @@ int flecs_expr_match_visit_type( continue; } - ecs_entity_t result_type = 0, operand_type = 0; + if (flecs_expr_is_type_number(node->node.type)) { + ecs_entity_t result_type = 0, operand_type = 0; + if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, 0, + (ecs_expr_node_t*)node, elem->expr, + EcsTokAdd, /* Use operator that doesn't change types */ + &operand_type, &result_type)) + { + goto error; + } - if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, - 0, - (ecs_expr_node_t*)node, elem->expr, EcsTokAdd, - &operand_type, &result_type)) - { - goto error; + /* "Accumulate" most expressive type in result node */ + node->node.type = result_type; + } else { + /* If type is not a number it must match exactly */ + if (elem->expr->type != node->node.type) { + char *got = ecs_get_path(script->world, elem->expr->type); + char *expect = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid type for case %d in match (got %s, expected %s)", + i + 1, got, expect); + ecs_os_free(got); + ecs_os_free(expect); + goto error; + } } - - /* "Accumulate" most expressive type in result node */ - node->node.type = result_type; } /* Loop over elements again, cast values to result type */ @@ -1385,18 +1429,29 @@ int flecs_expr_match_visit_type( } } + /* If this is an enum type, cast to the underlying type. This is necessary + * because the compare operation executed by the match evaluation code isn't + * implemented for enums. */ + ecs_entity_t expr_type = node->expr->type; + const EcsEnum *ptr = ecs_get(script->world, expr_type, EcsEnum); + if (ptr) { + node->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, node->expr, ptr->underlying_type); + } + /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - if (flecs_expr_visit_type_priv(script, elem->compare, cur, desc)) { + expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); + if (flecs_expr_visit_type_priv(script, elem->compare, &expr_cur, desc)) { goto error; } ecs_expr_node_t *compare = elem->compare; if (compare->type != node->expr->type) { - compare->type = (ecs_expr_node_t*) + elem->compare = (ecs_expr_node_t*) flecs_expr_cast(script, compare, node->expr->type); - if (!compare->type) { + if (!elem->compare) { goto error; } } diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 076df0308..cb493570a 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -708,6 +708,13 @@ identifier_colon: { ) } + { + // Position: match + LookAhead_1(EcsTokKeywordMatch, + goto component_expr_match; + ) + } + // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( @@ -883,6 +890,18 @@ component_expr_collection: { }) } +// Position: match +component_expr_match: { + + // Position: match expr + Expr('\n', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) +} + ParserEnd; } diff --git a/test/script/project.json b/test/script/project.json index 8e7dabaab..f9b3efb35 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -568,8 +568,14 @@ "int_cond_or_bool", "cond_eq_bool", "cond_eq_int", + "cond_eq_enum", + "cond_eq_string", + "cond_eq_entity", "cond_neq_bool", "cond_neq_int", + "cond_neq_enum", + "cond_neq_string", + "cond_neq_entity", "cond_eq_bool_int", "cond_eq_int_flt", "cond_eq_cond_and", @@ -708,7 +714,30 @@ "newline_at_start", "global_const_var", "scoped_global_const_var", - "escape_newline" + "escape_newline", + "match_i32_1_i_case", + "match_i32_2_i_cases", + "match_i32_2_i_f_cases", + "match_i32_2_f_i_cases", + "match_i32_3_i_cases", + "match_i32_3_i_i_f_cases", + "match_i32_3_i_f_i_cases", + "match_i32_3_f_i_i_cases", + "match_i32_1_struct_case", + "match_i32_2_struct_cases", + "match_i32_3_struct_cases", + "match_i32_empty_struct_cases", + "match_i32_struct_cases_unknown_type", + "match_i32_1_collection_case", + "match_i32_2_collection_cases", + "match_i32_3_collection_cases", + "match_i32_empty_collection_case", + "match_i32_collection_case_unknown_type", + "match_i32_struct_invalid_case_type", + "match_i32_collection_invalid_case_type", + "match_i32_string", + "match_enum_string", + "match_string_i" ] }, { "id": "ExprAst", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 75b71c577..3b12ca9a9 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -249,7 +249,6 @@ void Expr_mul_no_space(void) { ecs_fini(world); } - void Expr_add_no_space_var(void) { ecs_world_t *world = ecs_init(); @@ -2103,228 +2102,204 @@ void Expr_cond_eq_int(void) { ecs_fini(world); } -void Expr_cond_neq_bool(void) { +void Expr_cond_eq_enum(void) { ecs_world_t *world = ecs_init(); - ecs_value_t v = {0}; - ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true != true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); - - test_assert(ecs_expr_run(world, "true != false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); - - test_assert(ecs_expr_run(world, "false != true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + ecs_enum(world, { + .entity = ecs_entity(world, { .name = "Color" }), + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); - test_assert(ecs_expr_run(world, "false != false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); - ecs_value_free(world, v.type, v.ptr); + typedef enum { + Red, Green, Blue + } Color; - ecs_fini(world); -} + ecs_entity_t ecs_id(Color) = ecs_enum(world, { + .entity = ecs_entity(world, { .name = "Color" }), + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); -void Expr_cond_neq_int(void) { - ecs_world_t *world = ecs_init(); + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *x_var = ecs_script_vars_define(vars, "x", Color); + ecs_script_var_t *y_var = ecs_script_vars_define(vars, "y", Color); ecs_value_t v = {0}; - ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10 != 10", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); - - test_assert(ecs_expr_run(world, "10 != 20", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); - - test_assert(ecs_expr_run(world, "10 != 0", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); - - test_assert(ecs_expr_run(world, "0 != 0", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); - ecs_value_free(world, v.type, v.ptr); - - ecs_fini(world); -} - -void Expr_cond_eq_bool_int(void) { - ecs_world_t *world = ecs_init(); + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "true == 1", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x == $y", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == true); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); } { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "true == 2", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x == $y", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == true); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); } { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "true == 0", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x == $y", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == false); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); } { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "false == 1", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == false); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); } { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "false == 2", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == false); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); } { - ecs_value_t v = {0}; - test_assert(ecs_expr_run(world, "false == 0", &v, NULL) != NULL); + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(*(bool*)v.ptr == true); - ecs_value_free(world, v.type, v.ptr); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); } - ecs_fini(world); -} - -void Expr_cond_eq_int_flt(void) { - ecs_world_t *world = ecs_init(); - - ecs_value_t v = {0}; - - ecs_log_set_level(-4); - test_assert(ecs_expr_run(world, "1 == 1.0", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "1 == 0.0", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "0 == 0.0", &v, NULL) == NULL); + { + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x == 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } - test_assert(ecs_expr_run(world, "1 != 1.0", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "1 != 0.0", &v, NULL) == NULL); - test_assert(ecs_expr_run(world, "0 != 0.0", &v, NULL) == NULL); + { + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x == 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } - ecs_fini(world); -} + { + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x == 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } -void Expr_cond_eq_cond_and(void) { - ecs_world_t *world = ecs_init(); + { + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x == 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } - ecs_value_t v = {0}; - ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true == true && false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true == true && false); + { + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x == 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } - test_assert(ecs_expr_run(world, "true && false == false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true && false == false); + { + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x == 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } - test_assert(ecs_expr_run(world, "true && true == true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true && true == true); - ecs_value_free(world, v.type, v.ptr); + ecs_script_vars_fini(vars); ecs_fini(world); } -void Expr_cond_eq_cond_or(void) { +void Expr_cond_eq_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true == true || false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "\"foo\" == \"foo\"", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true == true || false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "true || false == false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "\"foo\" == \"bar\"", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true || false == false); - ecs_value_free(world, v.type, v.ptr); + test_bool(*(bool*)v.ptr, false); ecs_fini(world); } -void Expr_cond_gt_bool(void) { +void Expr_cond_eq_entity(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true > false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "flecs.core == flecs.core", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "true > true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); - - test_assert(ecs_expr_run(world, "false > true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "flecs.core == flecs", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "false > false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "flecs.core == 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } -void Expr_cond_gt_int(void) { +void Expr_cond_neq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10 > 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "10 > 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "5 > 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false != true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "5 > 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false != false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2333,27 +2308,27 @@ void Expr_cond_gt_int(void) { ecs_fini(world); } -void Expr_cond_gt_flt(void) { - ecs_world_t *world = ecs_init(); +void Expr_cond_neq_int(void) { + ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10.5 > 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 != 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "10.5 > 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 != 20", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "5.5 > 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "5.5 > 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "0 != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2362,114 +2337,318 @@ void Expr_cond_gt_flt(void) { ecs_fini(world); } -void Expr_cond_gteq_bool(void) { +void Expr_cond_neq_enum(void) { ecs_world_t *world = ecs_init(); - ecs_value_t v = {0}; - ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true >= false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + ecs_enum(world, { + .entity = ecs_entity(world, { .name = "Color" }), + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); - test_assert(ecs_expr_run(world, "true >= true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + typedef enum { + Red, Green, Blue + } Color; - test_assert(ecs_expr_run(world, "false >= true", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + ecs_entity_t ecs_id(Color) = ecs_enum(world, { + .entity = ecs_entity(world, { .name = "Color" }), + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); - test_assert(ecs_expr_run(world, "false >= false", &v, &desc) != NULL); - test_assert(v.type == ecs_id(ecs_bool_t)); - test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); - ecs_value_free(world, v.type, v.ptr); + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *x_var = ecs_script_vars_define(vars, "x", Color); + ecs_script_var_t *y_var = ecs_script_vars_define(vars, "y", Color); - ecs_fini(world); -} + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; -void Expr_cond_gteq_int(void) { + { + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x != $y", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } + + { + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x != $y", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Red; + *(Color*)y_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x != $y", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x != 0", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } + + { + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x != 0", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x != 0", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x != 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x != 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } + + { + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x != 1", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Red; + test_assert(ecs_expr_run(world, "$x != 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Green; + test_assert(ecs_expr_run(world, "$x != 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + } + + { + *(Color*)x_var->value.ptr = Blue; + test_assert(ecs_expr_run(world, "$x != 2", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + } + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_cond_neq_string(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10 >= 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "\"foo\" != \"foo\"", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "10 >= 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "\"foo\" != \"bar\"", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "5 >= 10", &v, &desc) != NULL); + ecs_fini(world); +} + +void Expr_cond_neq_entity(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "flecs.core != flecs.core", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "5 >= 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "flecs.core != flecs", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "flecs.core != 0", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } -void Expr_cond_gteq_flt(void) { +void Expr_cond_eq_bool_int(void) { + ecs_world_t *world = ecs_init(); + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 1", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 2", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "true == 0", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 1", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 2", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == false); + ecs_value_free(world, v.type, v.ptr); + } + + { + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "false == 0", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(*(bool*)v.ptr == true); + ecs_value_free(world, v.type, v.ptr); + } + + ecs_fini(world); +} + +void Expr_cond_eq_int_flt(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + + ecs_log_set_level(-4); + test_assert(ecs_expr_run(world, "1 == 1.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 == 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "0 == 0.0", &v, NULL) == NULL); + + test_assert(ecs_expr_run(world, "1 != 1.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "1 != 0.0", &v, NULL) == NULL); + test_assert(ecs_expr_run(world, "0 != 0.0", &v, NULL) == NULL); + + ecs_fini(world); +} + +void Expr_cond_eq_cond_and(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10.5 >= 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true == true && false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, true == true && false); - test_assert(ecs_expr_run(world, "10.5 >= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && false == false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, true && false == false); - test_assert(ecs_expr_run(world, "5.5 >= 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true && true == true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true && true == true); + ecs_value_free(world, v.type, v.ptr); - test_assert(ecs_expr_run(world, "5.5 >= 5.5", &v, &desc) != NULL); + ecs_fini(world); +} + +void Expr_cond_eq_cond_or(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true == true || false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, true == true || false); + + test_assert(ecs_expr_run(world, "true || false == false", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true || false == false); ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } -void Expr_cond_lt_bool(void) { +void Expr_cond_gt_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true < false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "true < true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "false < true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false > true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "false < false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false > false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2478,27 +2657,27 @@ void Expr_cond_lt_bool(void) { ecs_fini(world); } -void Expr_cond_lt_int(void) { +void Expr_cond_gt_int(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10 < 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "10 < 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "5 < 10", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 > 10", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "5 < 5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5 > 5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2507,27 +2686,27 @@ void Expr_cond_lt_int(void) { ecs_fini(world); } -void Expr_cond_lt_flt(void) { - ecs_world_t *world = ecs_init(); +void Expr_cond_gt_flt(void) { + ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "10.5 < 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "10.5 < 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "10.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "5.5 < 10.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 > 10.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); - test_assert(ecs_expr_run(world, "5.5 < 5.5", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "5.5 > 5.5", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); @@ -2536,25 +2715,199 @@ void Expr_cond_lt_flt(void) { ecs_fini(world); } -void Expr_cond_lteq_bool(void) { +void Expr_cond_gteq_bool(void) { ecs_world_t *world = ecs_init(); ecs_value_t v = {0}; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; - test_assert(ecs_expr_run(world, "true <= false", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true >= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, false); + test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "true <= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "true >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); - test_assert(ecs_expr_run(world, "false <= true", &v, &desc) != NULL); + test_assert(ecs_expr_run(world, "false >= true", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); - test_bool(*(bool*)v.ptr, true); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "false >= false", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_gteq_int(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 >= 5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "10 >= 10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "5 >= 10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "5 >= 5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_gteq_flt(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 >= 5.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "10.5 >= 10.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "5.5 >= 10.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "5.5 >= 5.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_lt_bool(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true < false", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "true < true", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "false < true", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "false < false", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_lt_int(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10 < 5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "10 < 10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "5 < 10", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "5 < 5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_lt_flt(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "10.5 < 5.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "10.5 < 10.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "5.5 < 10.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "5.5 < 5.5", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + +void Expr_cond_lteq_bool(void) { + ecs_world_t *world = ecs_init(); + + ecs_value_t v = {0}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + test_assert(ecs_expr_run(world, "true <= false", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, false); + + test_assert(ecs_expr_run(world, "true <= true", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); + + test_assert(ecs_expr_run(world, "false <= true", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_bool_t)); + test_assert(v.ptr != NULL); + test_bool(*(bool*)v.ptr, true); test_assert(ecs_expr_run(world, "false <= false", &v, &desc) != NULL); test_assert(v.type == ecs_id(ecs_bool_t)); @@ -5974,3 +6327,1255 @@ void Expr_escape_newline(void) { ecs_fini(world); } + +void Expr_match_i32_1_i_case(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 10); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_2_i_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 20); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_2_i_f_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20.5" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20.5); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_2_f_i_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10.5" + LINE " 2: 20" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10.5); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_i_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 20); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 30); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_i_i_f_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30.5" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 30.5); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_i_f_i_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20.5" + LINE " 3: 30" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20.5); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 30); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_f_i_i_cases(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10.5" + LINE " 2: 20" + LINE " 3: 30" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10.5); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 30); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_1_struct_case(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Position" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Position) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: {10, 20}" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 10); + test_int(p.y, 20); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_2_struct_cases(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Position" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Position) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: {10, 20}" + LINE " 2: {30, 40}" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 10); + test_int(p.y, 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 30); + test_int(p.y, 40); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_struct_cases(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Position" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Position) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: {10, 20}" + LINE " 2: {30, 40}" + LINE " 3: {50, 60}" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 10); + test_int(p.y, 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 30); + test_int(p.y, 40); + } + + { + *(int32_t*)var->value.ptr = 3; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 50); + test_int(p.y, 60); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_empty_struct_cases(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Position" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Position) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: {10, 20}" + LINE " 2: {30, 40}" + LINE " 3: {}" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 10); + test_int(p.y, 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 30); + test_int(p.y, 40); + } + + { + *(int32_t*)var->value.ptr = 3; + Position p = {0}; + ecs_value_t result = { .type = ecs_id(Position), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Position)); + test_int(p.x, 0); + test_int(p.y, 0); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_struct_cases_unknown_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: {10, 20}" + LINE " 2: {30, 40}" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_i32_1_collection_case(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Ints) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: [10, 20]" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 10); + test_int(p[1], 20); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_2_collection_cases(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Ints) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: [10, 20]" + LINE " 2: [20, 30]" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 10); + test_int(p[1], 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 20); + test_int(p[1], 30); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_3_collection_cases(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Ints) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: [10, 20]" + LINE " 2: [20, 30]" + LINE " 3: [30, 40]" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 10); + test_int(p[1], 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 20); + test_int(p[1], 30); + } + + { + *(int32_t*)var->value.ptr = 3; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 30); + test_int(p[1], 40); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_empty_collection_case(void) { + ecs_world_t *world = ecs_init(); + + typedef int32_t Ints[2]; + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Ints) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: [10, 20]" + LINE " 2: [20, 30]" + LINE " 3: []" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 10); + test_int(p[1], 20); + } + + { + *(int32_t*)var->value.ptr = 2; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 20); + test_int(p[1], 30); + } + + { + *(int32_t*)var->value.ptr = 3; + Ints p = {}; + ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(Ints)); + test_int(p[0], 0); + test_int(p[1], 0); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_i32_collection_case_unknown_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: [10, 20]" + LINE " 2: [30, 40]" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_i32_struct_invalid_case_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "Position" }), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Position) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_i32_collection_invalid_case_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .type = ecs_id(ecs_i32_t), + .count = 2 + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding, + .type = ecs_id(Ints) }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_i32_string(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: \"100\"" + LINE " 2: \"200\"" + LINE " 3: \"300\"" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "100"); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "200"); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "300"); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_enum_string(void) { + ecs_world_t *world = ecs_init(); + + typedef enum { + Red, Green, Blue + } Color; + + ecs_entity_t ecs_id(Color) = ecs_enum(world, { + .entity = ecs_entity(world, { .name = "Color" }), + .constants = { + {"Red"}, + {"Green"}, + {"Blue"} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", Color); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " Red: \"Red\"" + LINE " Green: \"Green\"" + LINE " Blue: \"Blue\"" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(Color*)var->value.ptr = 100; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(Color*)var->value.ptr = Red; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "Red"); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(Color*)var->value.ptr = Green; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "Green"); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(Color*)var->value.ptr = Blue; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_string_t)); + test_str(*(char**)result.ptr, "Blue"); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_string_i(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_string_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " \"100\": 1" + LINE " \"200\": 2" + LINE " \"300\": 3" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(char**)var->value.ptr = NULL; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(char**)var->value.ptr = "10"; + ecs_value_t result = {0}; + ecs_log_set_level(-4); + test_assert(0 != ecs_expr_eval(s, &result, &desc)); + ecs_log_set_level(-1); + } + + { + *(char**)var->value.ptr = "100"; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 1); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(char**)var->value.ptr = "200"; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 2); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(char**)var->value.ptr = "300"; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 3); + ecs_value_free(world, result.type, result.ptr); + } + + *(char**)var->value.ptr = NULL; + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index c1dff84b8..456f5cfd3 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -555,8 +555,14 @@ void Expr_bool_cond_or_int(void); void Expr_int_cond_or_bool(void); void Expr_cond_eq_bool(void); void Expr_cond_eq_int(void); +void Expr_cond_eq_enum(void); +void Expr_cond_eq_string(void); +void Expr_cond_eq_entity(void); void Expr_cond_neq_bool(void); void Expr_cond_neq_int(void); +void Expr_cond_neq_enum(void); +void Expr_cond_neq_string(void); +void Expr_cond_neq_entity(void); void Expr_cond_eq_bool_int(void); void Expr_cond_eq_int_flt(void); void Expr_cond_eq_cond_and(void); @@ -696,6 +702,29 @@ void Expr_newline_at_start(void); void Expr_global_const_var(void); void Expr_scoped_global_const_var(void); void Expr_escape_newline(void); +void Expr_match_i32_1_i_case(void); +void Expr_match_i32_2_i_cases(void); +void Expr_match_i32_2_i_f_cases(void); +void Expr_match_i32_2_f_i_cases(void); +void Expr_match_i32_3_i_cases(void); +void Expr_match_i32_3_i_i_f_cases(void); +void Expr_match_i32_3_i_f_i_cases(void); +void Expr_match_i32_3_f_i_i_cases(void); +void Expr_match_i32_1_struct_case(void); +void Expr_match_i32_2_struct_cases(void); +void Expr_match_i32_3_struct_cases(void); +void Expr_match_i32_empty_struct_cases(void); +void Expr_match_i32_struct_cases_unknown_type(void); +void Expr_match_i32_1_collection_case(void); +void Expr_match_i32_2_collection_cases(void); +void Expr_match_i32_3_collection_cases(void); +void Expr_match_i32_empty_collection_case(void); +void Expr_match_i32_collection_case_unknown_type(void); +void Expr_match_i32_struct_invalid_case_type(void); +void Expr_match_i32_collection_invalid_case_type(void); +void Expr_match_i32_string(void); +void Expr_match_enum_string(void); +void Expr_match_string_i(void); // Testsuite 'ExprAst' void ExprAst_binary_f32_var_add_f32_var(void); @@ -3058,6 +3087,18 @@ bake_test_case Expr_testcases[] = { "cond_eq_int", Expr_cond_eq_int }, + { + "cond_eq_enum", + Expr_cond_eq_enum + }, + { + "cond_eq_string", + Expr_cond_eq_string + }, + { + "cond_eq_entity", + Expr_cond_eq_entity + }, { "cond_neq_bool", Expr_cond_neq_bool @@ -3066,6 +3107,18 @@ bake_test_case Expr_testcases[] = { "cond_neq_int", Expr_cond_neq_int }, + { + "cond_neq_enum", + Expr_cond_neq_enum + }, + { + "cond_neq_string", + Expr_cond_neq_string + }, + { + "cond_neq_entity", + Expr_cond_neq_entity + }, { "cond_eq_bool_int", Expr_cond_eq_bool_int @@ -3621,6 +3674,98 @@ bake_test_case Expr_testcases[] = { { "escape_newline", Expr_escape_newline + }, + { + "match_i32_1_i_case", + Expr_match_i32_1_i_case + }, + { + "match_i32_2_i_cases", + Expr_match_i32_2_i_cases + }, + { + "match_i32_2_i_f_cases", + Expr_match_i32_2_i_f_cases + }, + { + "match_i32_2_f_i_cases", + Expr_match_i32_2_f_i_cases + }, + { + "match_i32_3_i_cases", + Expr_match_i32_3_i_cases + }, + { + "match_i32_3_i_i_f_cases", + Expr_match_i32_3_i_i_f_cases + }, + { + "match_i32_3_i_f_i_cases", + Expr_match_i32_3_i_f_i_cases + }, + { + "match_i32_3_f_i_i_cases", + Expr_match_i32_3_f_i_i_cases + }, + { + "match_i32_1_struct_case", + Expr_match_i32_1_struct_case + }, + { + "match_i32_2_struct_cases", + Expr_match_i32_2_struct_cases + }, + { + "match_i32_3_struct_cases", + Expr_match_i32_3_struct_cases + }, + { + "match_i32_empty_struct_cases", + Expr_match_i32_empty_struct_cases + }, + { + "match_i32_struct_cases_unknown_type", + Expr_match_i32_struct_cases_unknown_type + }, + { + "match_i32_1_collection_case", + Expr_match_i32_1_collection_case + }, + { + "match_i32_2_collection_cases", + Expr_match_i32_2_collection_cases + }, + { + "match_i32_3_collection_cases", + Expr_match_i32_3_collection_cases + }, + { + "match_i32_empty_collection_case", + Expr_match_i32_empty_collection_case + }, + { + "match_i32_collection_case_unknown_type", + Expr_match_i32_collection_case_unknown_type + }, + { + "match_i32_struct_invalid_case_type", + Expr_match_i32_struct_invalid_case_type + }, + { + "match_i32_collection_invalid_case_type", + Expr_match_i32_collection_invalid_case_type + }, + { + "match_i32_string", + Expr_match_i32_string + }, + { + "match_enum_string", + Expr_match_enum_string + }, + { + "match_string_i", + Expr_match_string_i } }; @@ -4410,7 +4555,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 232, + 261, Expr_testcases, 1, Expr_params From 80da4de0d7ad48088b13dd1cb4d8e3f418b54e32 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 19:34:01 -0800 Subject: [PATCH 20/36] Fix more issues with empty initializers --- distr/flecs.c | 129 ++++++++++++++++++++++++---- src/addons/script/expr/visit_type.c | 128 +++++++++++++++++++++++---- src/addons/script/visit_eval.c | 1 - test/script/src/Eval.c | 16 +++- test/script/src/Expr.c | 1 + 5 files changed, 233 insertions(+), 42 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 73a8a0c8b..911762435 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -61620,7 +61620,6 @@ int flecs_script_eval_expr( if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } - if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } @@ -80050,6 +80049,105 @@ int flecs_expr_interpolated_string_visit_type( return -1; } +static +int flecs_expr_initializer_collection_check( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur) +{ + if (cur) { + if (ecs_meta_is_collection(cur) != node->is_collection) { + char *type_str = ecs_get_path(script->world, node->node.type); + if (node->is_collection) { + flecs_expr_visit_error(script, node, + "invalid collection literal for non-collection type '%s'", + type_str); + } else { + flecs_expr_visit_error(script, node, + "invalid object literal for collection type '%s'", + type_str); + } + + ecs_os_free(type_str); + goto error; + } + } + + ecs_entity_t type = node->node.type; + if (type) { + const EcsOpaque *op = ecs_get(script->world, type, EcsOpaque); + if (op) { + type = op->as_type; + } + + const EcsType *ptr = ecs_get(script->world, type, EcsType); + if (ptr) { + ecs_type_kind_t kind = ptr->kind; + if (node->is_collection) { + /* Only do this check if no cursor is provided. Cursors also + * handle inline arrays. */ + if (!cur) { + if (kind != EcsArrayType && kind != EcsVectorType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid collection literal for type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } + } + } else { + if (kind != EcsStructType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid object literal for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } + } + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_empty_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + node->node.type = ecs_meta_get_type(cur); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unknown type for initializer"); + goto error; + } + + if (ecs_meta_push(cur)) { + goto error; + } + + if (flecs_expr_initializer_collection_check(script, node, cur)) { + goto error; + } + + if (ecs_meta_pop(cur)) { + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_visit_type( ecs_script_t *script, @@ -80073,19 +80171,7 @@ int flecs_expr_initializer_visit_type( goto error; } - if (ecs_meta_is_collection(cur) != node->is_collection) { - char *type_str = ecs_get_path(script->world, type); - if (node->is_collection) { - flecs_expr_visit_error(script, node, - "invalid collection literal for non-collection type '%s'" - " (expected '[]')", type_str); - } else { - flecs_expr_visit_error(script, node, - "invalid object literal for collection type '%s' (expected {})", - type_str); - } - - ecs_os_free(type_str); + if (flecs_expr_initializer_collection_check(script, node, cur)) { goto error; } @@ -80841,10 +80927,9 @@ int flecs_expr_visit_type_priv( } break; case EcsExprEmptyInitializer: - node->type = ecs_meta_get_type(cur); - if (!node->type) { - flecs_expr_visit_error(script, node, - "unknown type for initializer"); + if (flecs_expr_empty_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { goto error; } break; @@ -80944,9 +81029,15 @@ int flecs_expr_visit_type( if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; if (node->type) { + if (flecs_expr_initializer_collection_check( + script, (ecs_expr_initializer_t*)node, NULL)) + { + return -1; + } + node->type_info = ecs_get_type_info(script->world, node->type); + return 0; } - return 0; } if (desc->type) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 07a043246..5efff0815 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -691,6 +691,105 @@ int flecs_expr_interpolated_string_visit_type( return -1; } +static +int flecs_expr_initializer_collection_check( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur) +{ + if (cur) { + if (ecs_meta_is_collection(cur) != node->is_collection) { + char *type_str = ecs_get_path(script->world, node->node.type); + if (node->is_collection) { + flecs_expr_visit_error(script, node, + "invalid collection literal for non-collection type '%s'", + type_str); + } else { + flecs_expr_visit_error(script, node, + "invalid object literal for collection type '%s'", + type_str); + } + + ecs_os_free(type_str); + goto error; + } + } + + ecs_entity_t type = node->node.type; + if (type) { + const EcsOpaque *op = ecs_get(script->world, type, EcsOpaque); + if (op) { + type = op->as_type; + } + + const EcsType *ptr = ecs_get(script->world, type, EcsType); + if (ptr) { + ecs_type_kind_t kind = ptr->kind; + if (node->is_collection) { + /* Only do this check if no cursor is provided. Cursors also + * handle inline arrays. */ + if (!cur) { + if (kind != EcsArrayType && kind != EcsVectorType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid collection literal for type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } + } + } else { + if (kind != EcsStructType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid object literal for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } + } + } + + return 0; +error: + return -1; +} + +static +int flecs_expr_empty_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + (void)desc; + + node->node.type = ecs_meta_get_type(cur); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unknown type for initializer"); + goto error; + } + + if (ecs_meta_push(cur)) { + goto error; + } + + if (flecs_expr_initializer_collection_check(script, node, cur)) { + goto error; + } + + if (ecs_meta_pop(cur)) { + goto error; + } + + return 0; +error: + return -1; +} + static int flecs_expr_initializer_visit_type( ecs_script_t *script, @@ -714,19 +813,7 @@ int flecs_expr_initializer_visit_type( goto error; } - if (ecs_meta_is_collection(cur) != node->is_collection) { - char *type_str = ecs_get_path(script->world, type); - if (node->is_collection) { - flecs_expr_visit_error(script, node, - "invalid collection literal for non-collection type '%s'" - " (expected '[]')", type_str); - } else { - flecs_expr_visit_error(script, node, - "invalid object literal for collection type '%s' (expected {})", - type_str); - } - - ecs_os_free(type_str); + if (flecs_expr_initializer_collection_check(script, node, cur)) { goto error; } @@ -1482,10 +1569,9 @@ int flecs_expr_visit_type_priv( } break; case EcsExprEmptyInitializer: - node->type = ecs_meta_get_type(cur); - if (!node->type) { - flecs_expr_visit_error(script, node, - "unknown type for initializer"); + if (flecs_expr_empty_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { goto error; } break; @@ -1585,9 +1671,15 @@ int flecs_expr_visit_type( if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; if (node->type) { + if (flecs_expr_initializer_collection_check( + script, (ecs_expr_initializer_t*)node, NULL)) + { + return -1; + } + node->type_info = ecs_get_type_info(script->world, node->type); + return 0; } - return 0; } if (desc->type) { diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 9ed9e69ca..3568bb025 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -433,7 +433,6 @@ int flecs_script_eval_expr( if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } - if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 45ea742db..4bde0abb0 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10023,8 +10023,10 @@ void Eval_const_assign_empty_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = {}"; + HEAD "const x = {}" + LINE; + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, expr) != 0); ecs_fini(world); @@ -10034,8 +10036,10 @@ void Eval_const_assign_empty_collection_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = []"; + HEAD "const x = []" + LINE; + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, expr) != 0); ecs_fini(world); @@ -10045,8 +10049,10 @@ void Eval_const_i32_assign_empty_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = i32: {}"; + HEAD "const x = i32: {}" + LINE; + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, expr) != 0); ecs_fini(world); @@ -10056,8 +10062,10 @@ void Eval_const_i32_assign_empty_collection_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = i32: []"; + HEAD "const x = i32: []" + LINE; + ecs_log_set_level(-4); test_assert(ecs_script_run(world, NULL, expr) != 0); ecs_fini(world); diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 3b12ca9a9..3f4cb033b 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -7234,6 +7234,7 @@ void Expr_match_i32_empty_collection_case(void) { typedef int32_t Ints[2]; ecs_entity_t ecs_id(Ints) = ecs_array(world, { + .entity = ecs_entity(world, { .name = "Ints" }), .type = ecs_id(ecs_i32_t), .count = 2 }); From f5ccc563ebe9a003f9bc2c41361a18649efcda21 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 19:54:58 -0800 Subject: [PATCH 21/36] Implement match any case --- distr/flecs.c | 63 +++++++-- src/addons/script/expr/ast.h | 1 + src/addons/script/expr/visit_eval.c | 15 +- src/addons/script/expr/visit_type.c | 47 +++++-- test/script/project.json | 7 +- test/script/src/Expr.c | 205 ++++++++++++++++++++++++++++ test/script/src/main.c | 27 +++- 7 files changed, 339 insertions(+), 26 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 911762435..2d421bfe1 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5079,6 +5079,7 @@ typedef struct ecs_expr_match_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_vec_t elements; + ecs_expr_match_element_t any; } ecs_expr_match_t; ecs_expr_value_node_t* flecs_expr_value_from( @@ -77827,9 +77828,18 @@ int flecs_expr_match_visit_eval( } if (i == count) { - flecs_expr_visit_error(ctx->script, node, - "match value not handled by case"); - goto error; + if (node->any.expr) { + if (flecs_expr_visit_eval_priv(ctx, node->any.expr, out)) { + goto error; + } + } else { + char *str = ecs_ptr_to_str( + ctx->world, expr->value.type, expr->value.ptr); + flecs_expr_visit_error(ctx->script, node, + "match value '%s' not handled by case", str); + ecs_os_free(str); + goto error; + } } flecs_expr_stack_pop(ctx->stack); @@ -80790,6 +80800,19 @@ not_a_collection: { return -1; } +static +bool flecs_expr_identifier_is_any( + ecs_expr_node_t *node) +{ + if (node->kind == EcsExprIdentifier) { + ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)node; + if (id->value && !ecs_os_strcmp(id->value, "_")) { + return true; + } + } + return false; +} + static int flecs_expr_match_visit_type( ecs_script_t *script, @@ -80887,18 +80910,34 @@ int flecs_expr_match_visit_type( /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); - if (flecs_expr_visit_type_priv(script, elem->compare, &expr_cur, desc)) { - goto error; - } - ecs_expr_node_t *compare = elem->compare; - if (compare->type != node->expr->type) { - elem->compare = (ecs_expr_node_t*) - flecs_expr_cast(script, compare, node->expr->type); - if (!elem->compare) { + if (flecs_expr_identifier_is_any(elem->compare)) { + if (i != count - 1) { + flecs_expr_visit_error(script, node, + "any (_) must be the last case in match"); goto error; } + + node->any.compare = elem->compare; + node->any.expr = elem->expr; + elem = &node->any; + ecs_vec_remove_last(&node->elements); + } else { + expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); + if (flecs_expr_visit_type_priv( + script, elem->compare, &expr_cur, desc)) + { + goto error; + } + + ecs_expr_node_t *compare = elem->compare; + if (compare->type != node->expr->type) { + elem->compare = (ecs_expr_node_t*) + flecs_expr_cast(script, compare, node->expr->type); + if (!elem->compare) { + goto error; + } + } } } diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index e0ff067ec..f98fb400a 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -132,6 +132,7 @@ typedef struct ecs_expr_match_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_vec_t elements; + ecs_expr_match_element_t any; } ecs_expr_match_t; ecs_expr_value_node_t* flecs_expr_value_from( diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 67804113e..7f517a5ef 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -737,9 +737,18 @@ int flecs_expr_match_visit_eval( } if (i == count) { - flecs_expr_visit_error(ctx->script, node, - "match value not handled by case"); - goto error; + if (node->any.expr) { + if (flecs_expr_visit_eval_priv(ctx, node->any.expr, out)) { + goto error; + } + } else { + char *str = ecs_ptr_to_str( + ctx->world, expr->value.type, expr->value.ptr); + flecs_expr_visit_error(ctx->script, node, + "match value '%s' not handled by case", str); + ecs_os_free(str); + goto error; + } } flecs_expr_stack_pop(ctx->stack); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 5efff0815..62fd1ca2c 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -1432,6 +1432,19 @@ not_a_collection: { return -1; } +static +bool flecs_expr_identifier_is_any( + ecs_expr_node_t *node) +{ + if (node->kind == EcsExprIdentifier) { + ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)node; + if (id->value && !ecs_os_strcmp(id->value, "_")) { + return true; + } + } + return false; +} + static int flecs_expr_match_visit_type( ecs_script_t *script, @@ -1529,18 +1542,34 @@ int flecs_expr_match_visit_type( /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; - expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); - if (flecs_expr_visit_type_priv(script, elem->compare, &expr_cur, desc)) { - goto error; - } - ecs_expr_node_t *compare = elem->compare; - if (compare->type != node->expr->type) { - elem->compare = (ecs_expr_node_t*) - flecs_expr_cast(script, compare, node->expr->type); - if (!elem->compare) { + if (flecs_expr_identifier_is_any(elem->compare)) { + if (i != count - 1) { + flecs_expr_visit_error(script, node, + "any (_) must be the last case in match"); + goto error; + } + + node->any.compare = elem->compare; + node->any.expr = elem->expr; + elem = &node->any; + ecs_vec_remove_last(&node->elements); + } else { + expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); + if (flecs_expr_visit_type_priv( + script, elem->compare, &expr_cur, desc)) + { goto error; } + + ecs_expr_node_t *compare = elem->compare; + if (compare->type != node->expr->type) { + elem->compare = (ecs_expr_node_t*) + flecs_expr_cast(script, compare, node->expr->type); + if (!elem->compare) { + goto error; + } + } } } diff --git a/test/script/project.json b/test/script/project.json index f9b3efb35..c2dc167d3 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -737,7 +737,12 @@ "match_i32_collection_invalid_case_type", "match_i32_string", "match_enum_string", - "match_string_i" + "match_string_i", + "match_w_any", + "match_w_any_not_last", + "match_w_any_first", + "match_w_any_mismatching_type", + "match_i_w_any_f" ] }, { "id": "ExprAst", diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 3f4cb033b..131ca82d8 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -7580,3 +7580,208 @@ void Expr_match_string_i(void) { ecs_fini(world); } + +void Expr_match_w_any(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30" + LINE " _: 40" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 40); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 20); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 30); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 4; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_i64_t)); + test_int(*(int64_t*)result.ptr, 40); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Expr_match_w_any_not_last(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " _: 40" + LINE " 3: 30" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_w_any_first(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " _: 40" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_w_any_mismatching_type(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30" + LINE " _: \"Hello\"" + LINE "}"; + + ecs_log_set_level(-4); + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s == NULL); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_match_i_w_any_f(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + ecs_expr_eval_desc_t desc = { + .vars = vars, .disable_folding = disable_folding }; + + const char *expr = + HEAD "match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " _: 30.5" + LINE "}"; + + ecs_script_t *s = ecs_expr_parse(world, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 0; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 30.5); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 1; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 10); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 2; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 20); + ecs_value_free(world, result.type, result.ptr); + } + + { + *(int32_t*)var->value.ptr = 3; + ecs_value_t result = {0}; + test_assert(0 == ecs_expr_eval(s, &result, &desc)); + test_assert(result.type == ecs_id(ecs_f64_t)); + test_assert(*(ecs_f64_t*)result.ptr == 30.5); + ecs_value_free(world, result.type, result.ptr); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 456f5cfd3..c0370bf6f 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -725,6 +725,11 @@ void Expr_match_i32_collection_invalid_case_type(void); void Expr_match_i32_string(void); void Expr_match_enum_string(void); void Expr_match_string_i(void); +void Expr_match_w_any(void); +void Expr_match_w_any_not_last(void); +void Expr_match_w_any_first(void); +void Expr_match_w_any_mismatching_type(void); +void Expr_match_i_w_any_f(void); // Testsuite 'ExprAst' void ExprAst_binary_f32_var_add_f32_var(void); @@ -3766,6 +3771,26 @@ bake_test_case Expr_testcases[] = { { "match_string_i", Expr_match_string_i + }, + { + "match_w_any", + Expr_match_w_any + }, + { + "match_w_any_not_last", + Expr_match_w_any_not_last + }, + { + "match_w_any_first", + Expr_match_w_any_first + }, + { + "match_w_any_mismatching_type", + Expr_match_w_any_mismatching_type + }, + { + "match_i_w_any_f", + Expr_match_i_w_any_f } }; @@ -4555,7 +4580,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 261, + 266, Expr_testcases, 1, Expr_params From 5c2830940796d705f4fe351e4961f0099850c046 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 20:22:54 -0800 Subject: [PATCH 22/36] Add tests for assigning match expressions to components --- distr/flecs.c | 47 +++++++-- src/addons/script/parser.c | 47 +++++++-- test/script/project.json | 6 +- test/script/src/Eval.c | 199 +++++++++++++++++++++++++++++++++++++ test/script/src/main.c | 22 +++- 5 files changed, 299 insertions(+), 22 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 2d421bfe1..e8c468409 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56942,7 +56942,8 @@ for_stmt: { Parse_1(EcsTokRange, { Expr(0, { ecs_expr_node_t *to = EXPR; - ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); + ecs_script_for_range_t *stmt = + flecs_script_insert_for_range(parser); stmt->loop_var = Token(1); stmt->from = from; stmt->to = to; @@ -56982,16 +56983,29 @@ pair: { // (Eats, Apples): case ':': { - // (Eats, Apples): { - Parse_1('{', - // (Eats, Apples): { expr } - Initializer('}', + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); - comp->expr = INITIALIZER; - EndOfRule; - ) + comp->expr = EXPR; + EndOfRule; + }) + }) + + // (Eats, Apples): { + Parse_1('{', { + // (Eats, Apples): { expr } + Initializer('}', + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = INITIALIZER; + EndOfRule; + ) + } ) } @@ -57061,8 +57075,9 @@ identifier_flag: { Parse_1('{', // auto_override | Position: {expr} Expr('}', { - ecs_script_component_t *comp = flecs_script_insert_component( - parser, Token(2)); + ecs_script_component_t *comp = + flecs_script_insert_component( + parser, Token(2)); comp->expr = EXPR; EndOfRule; }) @@ -57133,6 +57148,18 @@ identifier_assign: { LookAhead_2(EcsTokIdentifier, ':', pos = lookahead; + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + // x = Position: { Parse_1('{', { // x = Position: {expr} diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index cb493570a..9a87eb83c 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -559,7 +559,8 @@ for_stmt: { Parse_1(EcsTokRange, { Expr(0, { ecs_expr_node_t *to = EXPR; - ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); + ecs_script_for_range_t *stmt = + flecs_script_insert_for_range(parser); stmt->loop_var = Token(1); stmt->from = from; stmt->to = to; @@ -599,16 +600,29 @@ pair: { // (Eats, Apples): case ':': { - // (Eats, Apples): { - Parse_1('{', - // (Eats, Apples): { expr } - Initializer('}', + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); - comp->expr = INITIALIZER; - EndOfRule; - ) + comp->expr = EXPR; + EndOfRule; + }) + }) + + // (Eats, Apples): { + Parse_1('{', { + // (Eats, Apples): { expr } + Initializer('}', + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = INITIALIZER; + EndOfRule; + ) + } ) } @@ -678,8 +692,9 @@ identifier_flag: { Parse_1('{', // auto_override | Position: {expr} Expr('}', { - ecs_script_component_t *comp = flecs_script_insert_component( - parser, Token(2)); + ecs_script_component_t *comp = + flecs_script_insert_component( + parser, Token(2)); comp->expr = EXPR; EndOfRule; }) @@ -750,6 +765,18 @@ identifier_assign: { LookAhead_2(EcsTokIdentifier, ':', pos = lookahead; + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + // x = Position: { Parse_1('{', { // x = Position: {expr} diff --git a/test/script/project.json b/test/script/project.json index c2dc167d3..b93522fcf 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -313,7 +313,11 @@ "const_assign_empty_initializer", "const_assign_empty_collection_initializer", "const_i32_assign_empty_initializer", - "const_i32_assign_empty_collection_initializer" + "const_i32_assign_empty_collection_initializer", + "component_w_match", + "component_w_match_invalid", + "pair_component_w_match", + "component_assign_w_match" ] }, { "id": "Template", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 4bde0abb0..29fd3057e 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10070,3 +10070,202 @@ void Eval_const_i32_assign_empty_collection_initializer(void) { ecs_fini(world); } + +void Eval_component_w_match(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {" + LINE " Position: match $i {" + LINE " 1: {10, 20}" + LINE " 2: {20, 30}" + LINE " 3: {30, 40}" + LINE " _: {40, 50}" + LINE " }" + LINE "}" + ; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 1; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + } + + { + *(int32_t*)var->value.ptr = 2; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 30); + } + + { + *(int32_t*)var->value.ptr = 3; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 30); + test_int(ptr->y, 40); + } + + { + *(int32_t*)var->value.ptr = 4; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 40); + test_int(ptr->y, 50); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_component_w_match_invalid(void) { + ecs_world_t *world = ecs_init(); + + ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {" + LINE " Position: match $i {" + LINE " 1: {10, 20}" + LINE " 2: {20, 30}" + LINE " 3: {30, 40}" + LINE " }" + LINE "}" + ; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 4; + ecs_log_set_level(-4); + test_assert(ecs_script_eval(s, &desc) != 0); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_pair_component_w_match(void) { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tgt); + + ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {" + LINE " (Position, Tgt): match $i {" + LINE " 1: {10, 20}" + LINE " 2: {20, 30}" + LINE " 3: {30, 40}" + LINE " }" + LINE "}" + ; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 4; + ecs_log_set_level(-4); + test_assert(ecs_script_eval(s, &desc) != 0); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_component_assign_w_match(void) { + ecs_world_t *world = ecs_init(); + + ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo = Position: match $i {" + LINE " 1: {10, 20}" + LINE " 2: {20, 30}" + LINE " 3: {30, 40}" + LINE "}" + ; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 4; + ecs_log_set_level(-4); + test_assert(ecs_script_eval(s, &desc) != 0); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index c0370bf6f..6aef7f463 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -310,6 +310,10 @@ void Eval_const_assign_empty_initializer(void); void Eval_const_assign_empty_collection_initializer(void); void Eval_const_i32_assign_empty_initializer(void); void Eval_const_i32_assign_empty_collection_initializer(void); +void Eval_component_w_match(void); +void Eval_component_w_match_invalid(void); +void Eval_pair_component_w_match(void); +void Eval_component_assign_w_match(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -2130,6 +2134,22 @@ bake_test_case Eval_testcases[] = { { "const_i32_assign_empty_collection_initializer", Eval_const_i32_assign_empty_collection_initializer + }, + { + "component_w_match", + Eval_component_w_match + }, + { + "component_w_match_invalid", + Eval_component_w_match_invalid + }, + { + "pair_component_w_match", + Eval_pair_component_w_match + }, + { + "component_assign_w_match", + Eval_component_assign_w_match } }; @@ -4559,7 +4579,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 301, + 305, Eval_testcases }, { From 6132d9138c9efa4e61b3dc463ec7e35008c1fcf2 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 21:30:08 -0800 Subject: [PATCH 23/36] Remove need to use $ to refer to script variables --- distr/flecs.c | 131 +++++++++++++++++++++------- src/addons/script/expr/ast.h | 12 +-- src/addons/script/expr/visit_free.c | 7 ++ src/addons/script/expr/visit_type.c | 112 ++++++++++++++++++------ test/script/project.json | 8 +- test/script/src/Eval.c | 64 ++++++++++++++ test/script/src/Expr.c | 81 +++++++++++++++++ test/script/src/main.c | 24 ++++- 8 files changed, 373 insertions(+), 66 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index e8c468409..d2c4c9f08 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -5011,12 +5011,6 @@ typedef struct ecs_expr_initializer_t { bool is_dynamic; } ecs_expr_initializer_t; -typedef struct ecs_expr_identifier_t { - ecs_expr_node_t node; - const char *value; - ecs_expr_node_t *expr; -} ecs_expr_identifier_t; - typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; @@ -5024,6 +5018,12 @@ typedef struct ecs_expr_variable_t { int32_t sp; /* For fast variable lookups */ } ecs_expr_variable_t; +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; + ecs_expr_node_t *expr; +} ecs_expr_identifier_t; + typedef struct ecs_expr_unary_t { ecs_expr_node_t node; ecs_expr_node_t *expr; @@ -78865,6 +78865,13 @@ void flecs_expr_match_visit_free( flecs_expr_visit_free(script, elem->compare); flecs_expr_visit_free(script, elem->expr); } + + if (node->any.compare) { + flecs_expr_visit_free(script, node->any.compare); + } + if (node->any.expr) { + flecs_expr_visit_free(script, node->any.expr); + } ecs_vec_fini_t(&flecs_script_impl(script)->allocator, &node->elements, ecs_expr_match_element_t); @@ -80375,6 +80382,34 @@ int flecs_expr_binary_visit_type( return -1; } +// else { +// type = ecs_id(ecs_entity_t); +// *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); +// } + +static +int flecs_expr_constant_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); + + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( + script->world, node->node.type, &result->storage.u64); + if (ecs_meta_set_string(&expr_cur, node->value)) { + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } + + result->ptr = &result->storage.u64; + node->expr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + static int flecs_expr_identifier_visit_type( ecs_script_t *script, @@ -80383,43 +80418,78 @@ int flecs_expr_identifier_visit_type( const ecs_expr_eval_desc_t *desc) { (void)desc; + + ecs_entity_t type = node->node.type; if (cur->valid) { - node->node.type = ecs_meta_get_type(cur); - } else { - node->node.type = ecs_id(ecs_entity_t); - *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); + type = ecs_meta_get_type(cur); } - ecs_entity_t type = node->node.type; + const EcsType *type_ptr = NULL; + if (type) { + type_ptr = ecs_get(script->world, type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + } - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); + if (type_ptr && + (type_ptr->kind == EcsEnumType || type_ptr->kind == EcsBitmaskType)) + { + /* If the requested type is an enum or bitmask, use cursor to resolve + * identifier to correct type constant. This lets us type 'Red' in places + * where we expect a value of type Color, instead of Color.Red. */ + node->node.type = type; + if (flecs_expr_constant_identifier_visit_type(script, node)) { + goto error; + } - if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { - result->storage.entity = desc->lookup_action( + return 0; + } else { + /* If not, try to resolve the identifier as entity */ + ecs_entity_t e = desc->lookup_action( script->world, node->value, desc->lookup_ctx); - result->ptr = &result->storage.entity; - if (!result->storage.entity) { - flecs_expr_visit_free(script, (ecs_expr_node_t*)result); - if (!desc->allow_unresolved_identifiers) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); + if (e) { + if (!type) { + type = ecs_id(ecs_entity_t); + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + result->storage.entity = e; + result->ptr = &result->storage.entity; + node->expr = (ecs_expr_node_t*)result; + node->node.type = type; + return 0; + } + + /* If identifier could not be resolved as entity, try as variable */ + int32_t var_sp = -1; + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->value, &var_sp); + if (var) { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = var->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { goto error; } - result = NULL; + return 0; } - } else { - ecs_meta_cursor_t expr_cur = ecs_meta_cursor( - script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&expr_cur, node->value)) { - flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + + /* If unresolved identifiers aren't allowed here, throw error */ + if (!desc->allow_unresolved_identifiers) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); goto error; } - result->ptr = &result->storage.u64; - } - node->expr = (ecs_expr_node_t*)result; + /* Identifier will be resolved at eval time, default to entity */ + node->node.type = ecs_id(ecs_entity_t); + } return 0; error: @@ -80947,7 +81017,6 @@ int flecs_expr_match_visit_type( node->any.compare = elem->compare; node->any.expr = elem->expr; - elem = &node->any; ecs_vec_remove_last(&node->elements); } else { expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index f98fb400a..ab020f692 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -64,12 +64,6 @@ typedef struct ecs_expr_initializer_t { bool is_dynamic; } ecs_expr_initializer_t; -typedef struct ecs_expr_identifier_t { - ecs_expr_node_t node; - const char *value; - ecs_expr_node_t *expr; -} ecs_expr_identifier_t; - typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; @@ -77,6 +71,12 @@ typedef struct ecs_expr_variable_t { int32_t sp; /* For fast variable lookups */ } ecs_expr_variable_t; +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; + ecs_expr_node_t *expr; +} ecs_expr_identifier_t; + typedef struct ecs_expr_unary_t { ecs_expr_node_t node; ecs_expr_node_t *expr; diff --git a/src/addons/script/expr/visit_free.c b/src/addons/script/expr/visit_free.c index 6d2c6e87d..ca9798602 100644 --- a/src/addons/script/expr/visit_free.c +++ b/src/addons/script/expr/visit_free.c @@ -119,6 +119,13 @@ void flecs_expr_match_visit_free( flecs_expr_visit_free(script, elem->compare); flecs_expr_visit_free(script, elem->expr); } + + if (node->any.compare) { + flecs_expr_visit_free(script, node->any.compare); + } + if (node->any.expr) { + flecs_expr_visit_free(script, node->any.expr); + } ecs_vec_fini_t(&flecs_script_impl(script)->allocator, &node->elements, ecs_expr_match_element_t); diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 62fd1ca2c..e4160050f 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -980,6 +980,34 @@ int flecs_expr_binary_visit_type( return -1; } +// else { +// type = ecs_id(ecs_entity_t); +// *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); +// } + +static +int flecs_expr_constant_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); + + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( + script->world, node->node.type, &result->storage.u64); + if (ecs_meta_set_string(&expr_cur, node->value)) { + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } + + result->ptr = &result->storage.u64; + node->expr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; +} + static int flecs_expr_identifier_visit_type( ecs_script_t *script, @@ -988,43 +1016,78 @@ int flecs_expr_identifier_visit_type( const ecs_expr_eval_desc_t *desc) { (void)desc; + + ecs_entity_t type = node->node.type; if (cur->valid) { - node->node.type = ecs_meta_get_type(cur); - } else { - node->node.type = ecs_id(ecs_entity_t); - *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); + type = ecs_meta_get_type(cur); } - ecs_entity_t type = node->node.type; + const EcsType *type_ptr = NULL; + if (type) { + type_ptr = ecs_get(script->world, type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + } - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); + if (type_ptr && + (type_ptr->kind == EcsEnumType || type_ptr->kind == EcsBitmaskType)) + { + /* If the requested type is an enum or bitmask, use cursor to resolve + * identifier to correct type constant. This lets us type 'Red' in places + * where we expect a value of type Color, instead of Color.Red. */ + node->node.type = type; + if (flecs_expr_constant_identifier_visit_type(script, node)) { + goto error; + } - if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { - result->storage.entity = desc->lookup_action( + return 0; + } else { + /* If not, try to resolve the identifier as entity */ + ecs_entity_t e = desc->lookup_action( script->world, node->value, desc->lookup_ctx); - result->ptr = &result->storage.entity; - if (!result->storage.entity) { - flecs_expr_visit_free(script, (ecs_expr_node_t*)result); - if (!desc->allow_unresolved_identifiers) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); + if (e) { + if (!type) { + type = ecs_id(ecs_entity_t); + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + result->storage.entity = e; + result->ptr = &result->storage.entity; + node->expr = (ecs_expr_node_t*)result; + node->node.type = type; + return 0; + } + + /* If identifier could not be resolved as entity, try as variable */ + int32_t var_sp = -1; + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->value, &var_sp); + if (var) { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = var->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { goto error; } - result = NULL; + return 0; } - } else { - ecs_meta_cursor_t expr_cur = ecs_meta_cursor( - script->world, type, &result->storage.u64); - if (ecs_meta_set_string(&expr_cur, node->value)) { - flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + + /* If unresolved identifiers aren't allowed here, throw error */ + if (!desc->allow_unresolved_identifiers) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); goto error; } - result->ptr = &result->storage.u64; - } - node->expr = (ecs_expr_node_t*)result; + /* Identifier will be resolved at eval time, default to entity */ + node->node.type = ecs_id(ecs_entity_t); + } return 0; error: @@ -1552,7 +1615,6 @@ int flecs_expr_match_visit_type( node->any.compare = elem->compare; node->any.expr = elem->expr; - elem = &node->any; ecs_vec_remove_last(&node->elements); } else { expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); diff --git a/test/script/project.json b/test/script/project.json index b93522fcf..ec2e076ef 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -317,7 +317,8 @@ "component_w_match", "component_w_match_invalid", "pair_component_w_match", - "component_assign_w_match" + "component_assign_w_match", + "const_w_match" ] }, { "id": "Template", @@ -746,7 +747,10 @@ "match_w_any_not_last", "match_w_any_first", "match_w_any_mismatching_type", - "match_i_w_any_f" + "match_i_w_any_f", + "identifier_as_var", + "expr_w_identifier_as_var", + "initializer_w_identifier_as_var" ] }, { "id": "ExprAst", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 29fd3057e..c2bc007f6 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10269,3 +10269,67 @@ void Eval_component_assign_w_match(void) { ecs_fini(world); } + +void Eval_const_w_match(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "const x = match $i {" + LINE " 1: 10" + LINE " 2: 20" + LINE " 3: 30" + LINE "}" + LINE "Foo {" + LINE " $x" + LINE "}" + ; + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define(vars, "i", ecs_i32_t); + + ecs_script_eval_desc_t desc = { .vars = vars }; + ecs_script_t *s = ecs_script_parse(world, NULL, expr, &desc); + test_assert(s != NULL); + + { + *(int32_t*)var->value.ptr = 1; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const int64_t *ptr = ecs_get(world, foo, ecs_i64_t); + test_assert(ptr != NULL); + test_int(*ptr, 10); + } + + { + *(int32_t*)var->value.ptr = 2; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const int64_t *ptr = ecs_get(world, foo, ecs_i64_t); + test_assert(ptr != NULL); + test_int(*ptr, 20); + } + + { + *(int32_t*)var->value.ptr = 3; + test_assert(ecs_script_eval(s, &desc) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const int64_t *ptr = ecs_get(world, foo, ecs_i64_t); + test_assert(ptr != NULL); + test_int(*ptr, 30); + } + + { + *(int32_t*)var->value.ptr = 4; + ecs_log_set_level(-4); + test_assert(ecs_script_eval(s, &desc) != 0); + ecs_log_set_level(-1); + } + + ecs_script_vars_fini(vars); + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 131ca82d8..608fd7a15 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -7785,3 +7785,84 @@ void Expr_match_i_w_any_f(void) { ecs_fini(world); } + +void Expr_identifier_as_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_expr_w_identifier_as_var(void) { + ecs_world_t *world = ecs_init(); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *var = ecs_script_vars_define( + vars, "foo", ecs_i64_t); + *(int32_t*)var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "10 + foo", &v, &desc) != NULL); + test_assert(v.type == ecs_id(ecs_i64_t)); + test_assert(v.ptr != NULL); + test_uint(*(int64_t*)v.ptr, 10 + 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} + +void Expr_initializer_w_identifier_as_var(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Position; + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_script_vars_t *vars = ecs_script_vars_init(world); + ecs_script_var_t *x_var = ecs_script_vars_define(vars, "x", ecs_i32_t); + *(int32_t*)x_var->value.ptr = 10; + ecs_script_var_t *y_var = ecs_script_vars_define(vars, "y", ecs_i32_t); + *(int32_t*)y_var->value.ptr = 20; + + ecs_expr_eval_desc_t desc = { .vars = vars, .disable_folding = disable_folding }; + + Position p; + ecs_value_t v = { .type = ecs_id(Position), .ptr = &p }; + test_assert(ecs_expr_run(world, "{x, y}", &v, &desc) != NULL); + test_assert(v.type == ecs_id(Position)); + test_assert(v.ptr != NULL); + test_int(p.x, 10); + test_int(p.y, 20); + ecs_value_free(world, v.type, v.ptr); + + ecs_script_vars_fini(vars); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 6aef7f463..fd538691a 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -314,6 +314,7 @@ void Eval_component_w_match(void); void Eval_component_w_match_invalid(void); void Eval_pair_component_w_match(void); void Eval_component_assign_w_match(void); +void Eval_const_w_match(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -734,6 +735,9 @@ void Expr_match_w_any_not_last(void); void Expr_match_w_any_first(void); void Expr_match_w_any_mismatching_type(void); void Expr_match_i_w_any_f(void); +void Expr_identifier_as_var(void); +void Expr_expr_w_identifier_as_var(void); +void Expr_initializer_w_identifier_as_var(void); // Testsuite 'ExprAst' void ExprAst_binary_f32_var_add_f32_var(void); @@ -2150,6 +2154,10 @@ bake_test_case Eval_testcases[] = { { "component_assign_w_match", Eval_component_assign_w_match + }, + { + "const_w_match", + Eval_const_w_match } }; @@ -3811,6 +3819,18 @@ bake_test_case Expr_testcases[] = { { "match_i_w_any_f", Expr_match_i_w_any_f + }, + { + "identifier_as_var", + Expr_identifier_as_var + }, + { + "expr_w_identifier_as_var", + Expr_expr_w_identifier_as_var + }, + { + "initializer_w_identifier_as_var", + Expr_initializer_w_identifier_as_var } }; @@ -4579,7 +4599,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 305, + 306, Eval_testcases }, { @@ -4600,7 +4620,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 266, + 269, Expr_testcases, 1, Expr_params From 7687b5ae9c4449e71978d20365b381e41df36293 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 Dec 2024 22:13:24 -0800 Subject: [PATCH 24/36] Document match expressions --- docs/FlecsScript.md | 61 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 11dbea4d5..a40e22a5b 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -398,6 +398,48 @@ Tree: {color: $color, height: $height} Tree: {color: $, height: $} ``` +### Match expressions +Match expressions can be used to conditionally assign a value. An example: + +```c +const x = 1 + +// y will be assigned with value 10 +const y = match x { + 1: 10 + 2: 20 + 3: 30 +} +``` + +The input to a match expression must be matched by one of its cases. If the input is not matched, script execution will fail. Match expressions can include an "any" case, which is selected when none of the other cases match: + +```c +const x = 4 + +// y will be assigned with value 100 +const y = match x { + 1: 10 + 2: 20 + 3: 30 + _: 100 +} +``` + +Match expressions can be used to assign components: + +```c +e { + Position: match i { + 1: {10, 20} + 2: {20, 30} + 3: {40, 50} + } +} +``` + +The type of a match expression is derived from the case values. When the case statements in a match contain values of multiple types, the most expressive type is selected. The algorithm for determining the most expressive type is the same as the one used to determine the type for binary expressions. When a match expression contains values with conflicting types, script execution will fail. + ### String interpolation Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: @@ -898,7 +940,7 @@ Scripts can contain variables, which are useful for often repeated values. Varia const pi = 3.1415926 my_entity { - Rotation: {angle: $pi} + Rotation: {angle: pi} } ``` @@ -909,7 +951,7 @@ const pi = 3.1415926 const pi_2 = $pi * 2 my_entity { - Rotation: {angle: $pi / 2} + Rotation: {angle: pi / 2} } ``` @@ -919,7 +961,18 @@ In the above examples, the type of the variable is inferred. Variables can also const wood = Color: {38, 25, 13} ``` -Variables can be used in component values as shown in the previous examples, or can be used directly as component. Example: +When the name of a variable clashes with an entity, it can be disambiguated by prefixing the variable name with a `$`: + +```c +const pi = 3.1415926 +const pi_2 = $pi * 2 + +pi { + Rotation: {angle: $pi / 2} +} +``` + +Variables can be used in component values as shown in the previous examples, or can be used directly as component. When used like this, the variable name must be prefixed with a `$`. Example: ```c const wood = Color: {38, 25, 13} @@ -935,7 +988,7 @@ my_entity { } ``` -Additionally, variables can also be used in combination with `with` statements: +Additionally, variables can also be used in combination with `with` statements. When used like this the variable name must also be prefixed with a `$`: ```c const wood = Color: {38, 25, 13} From b0ce3f02aa57f40e0523a88d5d2d51d862caf125 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 28 Dec 2024 13:39:03 -0800 Subject: [PATCH 25/36] Implement add assignment and multiply assignment operators --- distr/flecs.c | 139 ++++++++++++++++++++++------ docs/FlecsScript.md | 30 ++++++ src/addons/script/expr/ast.h | 1 + src/addons/script/expr/parser.c | 20 +++- src/addons/script/expr/util.c | 8 ++ src/addons/script/expr/visit_eval.c | 64 +++++++++---- src/addons/script/expr/visit_fold.c | 4 + src/addons/script/expr/visit_type.c | 16 ++++ src/addons/script/tokenizer.c | 6 ++ src/addons/script/tokenizer.h | 2 + src/addons/script/visit_eval.c | 18 ++-- test/script/project.json | 11 ++- test/script/src/Deserialize.c | 99 ++++++++++++++++++++ test/script/src/Eval.c | 68 ++++++++++++++ test/script/src/Expr.c | 1 + test/script/src/Template.c | 78 ++++++++++++++++ test/script/src/main.c | 41 +++++++- 17 files changed, 549 insertions(+), 57 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d2c4c9f08..9da0f89f0 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -4558,6 +4558,8 @@ typedef enum ecs_script_token_kind_t { EcsTokKeywordProp = 130, EcsTokKeywordConst = 131, EcsTokKeywordMatch = 132, + EcsTokAddAssign = 133, + EcsTokMulAssign = 134, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { @@ -5001,6 +5003,7 @@ typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *value; uintptr_t offset; + ecs_script_token_kind_t operator; } ecs_expr_initializer_element_t; typedef struct ecs_expr_initializer_t { @@ -59655,6 +59658,8 @@ const char* flecs_script_token_kind_str( case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: @@ -59722,6 +59727,8 @@ const char* flecs_script_token_str( case EcsTokRange: return ".."; case EcsTokShiftLeft: return "<<"; case EcsTokShiftRight: return ">>"; + case EcsTokAddAssign: return "+="; + case EcsTokMulAssign: return "*="; case EcsTokKeywordWith: return "with"; case EcsTokKeywordUsing: return "using"; case EcsTokKeywordProp: return "prop"; @@ -60123,6 +60130,8 @@ const char* flecs_script_token( } else if (flecs_script_is_number(pos)) { return flecs_script_number(parser, pos, out); + OperatorMultiChar ("+=", EcsTokAddAssign) + OperatorMultiChar ("*=", EcsTokMulAssign) Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) @@ -61991,6 +62000,10 @@ int flecs_script_eval_component( return -1; } } + + ecs_record_t *r = flecs_entities_get(v->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; ecs_value_t value = { .ptr = ecs_ensure_id(v->world, src, node->id.eval), @@ -61999,13 +62012,15 @@ int flecs_script_eval_component( /* Assign entire value, including members not set by expression. This * prevents uninitialized or unexpected values. */ - if (!ti->hooks.ctor) { - ecs_os_memset(value.ptr, 0, ti->size); - } else if (ti->hooks.ctor) { - if (ti->hooks.dtor) { - ti->hooks.dtor(value.ptr, 1, ti); + if (r->table != table) { + if (!ti->hooks.ctor) { + ecs_os_memset(value.ptr, 0, ti->size); + } else if (ti->hooks.ctor) { + if (ti->hooks.dtor) { + ti->hooks.dtor(value.ptr, 1, ti); + } + ti->hooks.ctor(value.ptr, 1, ti); } - ti->hooks.ctor(value.ptr, 1, ti); } if (flecs_script_eval_expr(v, &node->expr, &value)) { @@ -75963,8 +75978,8 @@ const char* flecs_script_parse_initializer( a, &node->elements, ecs_expr_initializer_element_t); ecs_os_zeromem(elem); + /* Parse member name */ { - /* Parse member name */ LookAhead_2(EcsTokIdentifier, ':', { elem->member = Token(0); LookAhead_Keep(); @@ -75972,6 +75987,24 @@ const char* flecs_script_parse_initializer( break; }) } + { + LookAhead_2(EcsTokIdentifier, EcsTokAddAssign, { + elem->member = Token(0); + elem->operator = EcsTokAddAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) + } + { + LookAhead_2(EcsTokIdentifier, EcsTokMulAssign, { + elem->member = Token(0); + elem->operator = EcsTokMulAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) + } pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); if (!pos) { @@ -76782,6 +76815,8 @@ int flecs_value_unary( case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -76973,6 +77008,12 @@ int flecs_value_binary( case EcsTokShiftRight: ECS_BINARY_INT_OP(left, right, out, >>); break; + case EcsTokAddAssign: + ECS_BINARY_OP(out, right, out, +=); + break; + case EcsTokMulAssign: + ECS_BINARY_OP(out, right, out, *=); + break; case EcsTokEnd: case EcsTokUnknown: case EcsTokScopeOpen: @@ -77236,15 +77277,28 @@ int flecs_expr_initializer_eval_static( * a cast to the type of the initializer element. */ ecs_entity_t type = elem->value->type; - if (expr->owned) { - if (ecs_value_move(ctx->world, type, - ECS_OFFSET(value, elem->offset), expr->value.ptr)) - { - goto error; + if (!elem->operator) { + if (expr->owned) { + if (ecs_value_move(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } else { + if (ecs_value_copy(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } } } else { - if (ecs_value_copy(ctx->world, type, - ECS_OFFSET(value, elem->offset), expr->value.ptr)) + ecs_value_t dst = { + .type = type, + .ptr = ECS_OFFSET(value, elem->offset) + }; + + if (flecs_value_binary( + ctx->script, NULL, &expr->value, &dst, elem->operator)) { goto error; } @@ -78070,7 +78124,18 @@ int flecs_expr_visit_eval( flecs_expr_stack_push(stack); - ecs_expr_value_t *val = flecs_expr_stack_result(stack, node); + ecs_expr_value_t val_tmp; + ecs_expr_value_t *val; + if (out->type && (out->type == node->type) && out->ptr) { + val_tmp = (ecs_expr_value_t){ + .value = *out, + .owned = false, + .type_info = ecs_get_type_info(script->world, out->type) + }; + val = &val_tmp; + } else { + val = flecs_expr_stack_result(stack, node); + } ecs_script_eval_ctx_t ctx = { .script = script, @@ -78095,17 +78160,19 @@ int flecs_expr_visit_eval( out->ptr = ecs_value_new(ctx.world, out->type); } - if (val->owned) { - /* Values owned by the runtime can be moved to output */ - if (flecs_value_move_to(ctx.world, out, &val->value)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; - } - } else { - /* Values not owned by runtime should be copied */ - if (flecs_value_copy_to(ctx.world, out, val)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; + if (val != &val_tmp || out->ptr != val->value.ptr) { + if (val->owned) { + /* Values owned by the runtime can be moved to output */ + if (flecs_value_move_to(ctx.world, out, &val->value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } else { + /* Values not owned by runtime should be copied */ + if (flecs_value_copy_to(ctx.world, out, val)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } } } @@ -78393,6 +78460,10 @@ int flecs_expr_initializer_pre_fold( if (elem->value->kind != EcsExprValue) { *can_fold = false; } + + if (elem->operator) { + *can_fold = false; + } } if (node->is_dynamic) { @@ -79621,6 +79692,8 @@ bool flecs_expr_oper_valid_for_type( case EcsTokMul: case EcsTokDiv: case EcsTokMod: + case EcsTokAddAssign: + case EcsTokMulAssign: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: @@ -79751,6 +79824,8 @@ int flecs_expr_type_for_operator( case EcsTokSub: case EcsTokMul: break; + case EcsTokAddAssign: + case EcsTokMulAssign: case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: @@ -80266,6 +80341,18 @@ int flecs_expr_initializer_visit_type( elem->value = cast; } + if (elem->operator) { + if (!flecs_expr_oper_valid_for_type( + script->world, elem_type, elem->operator)) + { + char *type_str = ecs_get_path(script->world, elem_type); + flecs_expr_visit_error(script, node, + "invalid operator for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } + if (!is_opaque) { elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); } diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index a40e22a5b..34f5da194 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -372,6 +372,7 @@ Initializers are values that are used to initialize composite and collection mem {start: {x: 10, y: 20}, stop: {x: 10, y: 20}} [10, 20, 30] [{10, 20}, {30, 40}, {50, 60}] +{x += 10, y *= 2} ``` Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: @@ -398,6 +399,35 @@ Tree: {color: $color, height: $height} Tree: {color: $, height: $} ``` +Initializer expressions may contain add assignment (`+=`) or multiply assignment (`*=`) operators. These operators allow an initializer to modify an existing value. An example: + +```c +e { + Position: {10, 20} + Position: {x += 1, y += 2} +} + +// e will have Position{11, 22} +``` + +This can be especially useful when used in combination with templates (see below): + +```cpp +template Tree { + prop height = f32: 4 + + // Make sure tree doesn't sink through the ground + Position: {y += height} +} + +e { + Position: {10, 0} + Tree: {height: 3} +} + +// e will have Position{10, 3} +``` + ### Match expressions Match expressions can be used to conditionally assign a value. An example: diff --git a/src/addons/script/expr/ast.h b/src/addons/script/expr/ast.h index ab020f692..b90a53be2 100644 --- a/src/addons/script/expr/ast.h +++ b/src/addons/script/expr/ast.h @@ -54,6 +54,7 @@ typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *value; uintptr_t offset; + ecs_script_token_kind_t operator; } ecs_expr_initializer_element_t; typedef struct ecs_expr_initializer_t { diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index db91b7741..f4278e2e3 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -165,8 +165,8 @@ const char* flecs_script_parse_initializer( a, &node->elements, ecs_expr_initializer_element_t); ecs_os_zeromem(elem); + /* Parse member name */ { - /* Parse member name */ LookAhead_2(EcsTokIdentifier, ':', { elem->member = Token(0); LookAhead_Keep(); @@ -174,6 +174,24 @@ const char* flecs_script_parse_initializer( break; }) } + { + LookAhead_2(EcsTokIdentifier, EcsTokAddAssign, { + elem->member = Token(0); + elem->operator = EcsTokAddAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) + } + { + LookAhead_2(EcsTokIdentifier, EcsTokMulAssign, { + elem->member = Token(0); + elem->operator = EcsTokMulAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) + } pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); if (!pos) { diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index 378d439cc..bc4354be0 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -113,6 +113,8 @@ int flecs_value_unary( case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: @@ -304,6 +306,12 @@ int flecs_value_binary( case EcsTokShiftRight: ECS_BINARY_INT_OP(left, right, out, >>); break; + case EcsTokAddAssign: + ECS_BINARY_OP(out, right, out, +=); + break; + case EcsTokMulAssign: + ECS_BINARY_OP(out, right, out, *=); + break; case EcsTokEnd: case EcsTokUnknown: case EcsTokScopeOpen: diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index 7f517a5ef..8176328cb 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -118,15 +118,28 @@ int flecs_expr_initializer_eval_static( * a cast to the type of the initializer element. */ ecs_entity_t type = elem->value->type; - if (expr->owned) { - if (ecs_value_move(ctx->world, type, - ECS_OFFSET(value, elem->offset), expr->value.ptr)) - { - goto error; + if (!elem->operator) { + if (expr->owned) { + if (ecs_value_move(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } else { + if (ecs_value_copy(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } } } else { - if (ecs_value_copy(ctx->world, type, - ECS_OFFSET(value, elem->offset), expr->value.ptr)) + ecs_value_t dst = { + .type = type, + .ptr = ECS_OFFSET(value, elem->offset) + }; + + if (flecs_value_binary( + ctx->script, NULL, &expr->value, &dst, elem->operator)) { goto error; } @@ -952,7 +965,18 @@ int flecs_expr_visit_eval( flecs_expr_stack_push(stack); - ecs_expr_value_t *val = flecs_expr_stack_result(stack, node); + ecs_expr_value_t val_tmp; + ecs_expr_value_t *val; + if (out->type && (out->type == node->type) && out->ptr) { + val_tmp = (ecs_expr_value_t){ + .value = *out, + .owned = false, + .type_info = ecs_get_type_info(script->world, out->type) + }; + val = &val_tmp; + } else { + val = flecs_expr_stack_result(stack, node); + } ecs_script_eval_ctx_t ctx = { .script = script, @@ -977,17 +1001,19 @@ int flecs_expr_visit_eval( out->ptr = ecs_value_new(ctx.world, out->type); } - if (val->owned) { - /* Values owned by the runtime can be moved to output */ - if (flecs_value_move_to(ctx.world, out, &val->value)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; - } - } else { - /* Values not owned by runtime should be copied */ - if (flecs_value_copy_to(ctx.world, out, val)) { - flecs_expr_visit_error(script, node, "failed to write to output"); - goto error; + if (val != &val_tmp || out->ptr != val->value.ptr) { + if (val->owned) { + /* Values owned by the runtime can be moved to output */ + if (flecs_value_move_to(ctx.world, out, &val->value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } else { + /* Values not owned by runtime should be copied */ + if (flecs_value_copy_to(ctx.world, out, val)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } } } diff --git a/src/addons/script/expr/visit_fold.c b/src/addons/script/expr/visit_fold.c index 546139194..2d065e284 100644 --- a/src/addons/script/expr/visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -269,6 +269,10 @@ int flecs_expr_initializer_pre_fold( if (elem->value->kind != EcsExprValue) { *can_fold = false; } + + if (elem->operator) { + *can_fold = false; + } } if (node->is_dynamic) { diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index e4160050f..22f4e4fba 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -219,6 +219,8 @@ bool flecs_expr_oper_valid_for_type( case EcsTokMul: case EcsTokDiv: case EcsTokMod: + case EcsTokAddAssign: + case EcsTokMulAssign: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: @@ -349,6 +351,8 @@ int flecs_expr_type_for_operator( case EcsTokSub: case EcsTokMul: break; + case EcsTokAddAssign: + case EcsTokMulAssign: case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: @@ -864,6 +868,18 @@ int flecs_expr_initializer_visit_type( elem->value = cast; } + if (elem->operator) { + if (!flecs_expr_oper_valid_for_type( + script->world, elem_type, elem->operator)) + { + char *type_str = ecs_get_path(script->world, elem_type); + flecs_expr_visit_error(script, node, + "invalid operator for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } + if (!is_opaque) { elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); } diff --git a/src/addons/script/tokenizer.c b/src/addons/script/tokenizer.c index 2976294fe..ee6c17808 100644 --- a/src/addons/script/tokenizer.c +++ b/src/addons/script/tokenizer.c @@ -68,6 +68,8 @@ const char* flecs_script_token_kind_str( case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: @@ -135,6 +137,8 @@ const char* flecs_script_token_str( case EcsTokRange: return ".."; case EcsTokShiftLeft: return "<<"; case EcsTokShiftRight: return ">>"; + case EcsTokAddAssign: return "+="; + case EcsTokMulAssign: return "*="; case EcsTokKeywordWith: return "with"; case EcsTokKeywordUsing: return "using"; case EcsTokKeywordProp: return "prop"; @@ -536,6 +540,8 @@ const char* flecs_script_token( } else if (flecs_script_is_number(pos)) { return flecs_script_number(parser, pos, out); + OperatorMultiChar ("+=", EcsTokAddAssign) + OperatorMultiChar ("*=", EcsTokMulAssign) Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) diff --git a/src/addons/script/tokenizer.h b/src/addons/script/tokenizer.h index bbc062f48..a07a290a6 100644 --- a/src/addons/script/tokenizer.h +++ b/src/addons/script/tokenizer.h @@ -58,6 +58,8 @@ typedef enum ecs_script_token_kind_t { EcsTokKeywordProp = 130, EcsTokKeywordConst = 131, EcsTokKeywordMatch = 132, + EcsTokAddAssign = 133, + EcsTokMulAssign = 134, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index 3568bb025..b6f47dcc9 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -776,6 +776,10 @@ int flecs_script_eval_component( return -1; } } + + ecs_record_t *r = flecs_entities_get(v->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; ecs_value_t value = { .ptr = ecs_ensure_id(v->world, src, node->id.eval), @@ -784,13 +788,15 @@ int flecs_script_eval_component( /* Assign entire value, including members not set by expression. This * prevents uninitialized or unexpected values. */ - if (!ti->hooks.ctor) { - ecs_os_memset(value.ptr, 0, ti->size); - } else if (ti->hooks.ctor) { - if (ti->hooks.dtor) { - ti->hooks.dtor(value.ptr, 1, ti); + if (r->table != table) { + if (!ti->hooks.ctor) { + ecs_os_memset(value.ptr, 0, ti->size); + } else if (ti->hooks.ctor) { + if (ti->hooks.dtor) { + ti->hooks.dtor(value.ptr, 1, ti); + } + ti->hooks.ctor(value.ptr, 1, ti); } - ti->hooks.ctor(value.ptr, 1, ti); } if (flecs_script_eval_expr(v, &node->expr, &value)) { diff --git a/test/script/project.json b/test/script/project.json index ec2e076ef..1bdf7dc1e 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -318,7 +318,9 @@ "component_w_match_invalid", "pair_component_w_match", "component_assign_w_match", - "const_w_match" + "const_w_match", + "component_w_assign_add", + "component_w_assign_mul" ] }, { "id": "Template", @@ -388,7 +390,9 @@ "template_w_child_component_w_undefined_identifier", "template_w_anonymous_child_component_w_undefined_identifier", "clear_script_w_template_w_on_remove_observer", - "clear_script_w_template_w_on_remove_observer_added_after" + "clear_script_w_template_w_on_remove_observer_added_after", + "component_w_assign_add", + "component_w_assign_mul" ] }, { "id": "Error", @@ -924,6 +928,9 @@ "struct_w_newline", "struct_w_members_newline", "struct_w_trailing_comma", + "struct_w_add_assign_expr", + "struct_w_mul_assign_expr", + "struct_w_add_assign_expr_invalid_type", "array_w_trailing_comma", "array_i32_2", "array_string_2", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index e70eb61c4..02f1b2aa8 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -1825,6 +1825,105 @@ void Deserialize_struct_member_2_nested_i32_i32_reverse(void) { ecs_fini(world); } +void Deserialize_struct_w_add_assign_expr(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + + Point value = {10, 20}; + + const char *ptr = ecs_expr_run(world, + "{x += 1, y += 2}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_int(value.x, 11); + test_int(value.y, 22); + + ecs_fini(world); +} + +void Deserialize_struct_w_mul_assign_expr(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + int32_t x; + int32_t y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + + Point value = {10, 20}; + + const char *ptr = ecs_expr_run(world, + "{x *= 2, y *= 4}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_int(value.x, 20); + test_int(value.y, 80); + + ecs_fini(world); +} + +void Deserialize_struct_w_add_assign_expr_invalid_type(void) { + ecs_world_t *world = ecs_init(); + + typedef struct { + char *x; + char *y; + } Point; + + ECS_COMPONENT(world, Point); + + ecs_entity_t t = ecs_struct(world, { + .entity = ecs_id(Point), + .members = { + {"x", ecs_id(ecs_string_t)}, + {"y", ecs_id(ecs_string_t)} + } + }); + + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + + Point value = {0}; + + ecs_log_set_level(-4); + const char *ptr = ecs_expr_run(world, + "{x += 1, y += 2}", + &(ecs_value_t){t, &value}, &desc); + test_assert(ptr == NULL); + + ecs_fini(world); +} + void Deserialize_struct_i32_array_3(void) { typedef struct { int32_t x[3]; diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index c2bc007f6..7c01f7f76 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10333,3 +10333,71 @@ void Eval_const_w_match(void) { ecs_fini(world); } + +void Eval_component_w_assign_add(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {" + LINE " Position: {10, 20}" + LINE " Position: {x += 1, y += 2}" + LINE "}" + ; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, NULL); + test_assert(s != NULL); + + test_assert(ecs_script_eval(s, NULL) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 11); + test_int(ptr->y, 22); + + ecs_script_free(s); + + ecs_fini(world); +} + +void Eval_component_w_assign_mul(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "Foo {" + LINE " Position: {10, 20}" + LINE " Position: {x *= 2, y *= 4}" + LINE "}" + ; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, NULL); + test_assert(s != NULL); + + test_assert(ecs_script_eval(s, NULL) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 20); + test_int(ptr->y, 80); + + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 608fd7a15..841038d47 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -988,6 +988,7 @@ void Expr_struct_result_implicit_members(void) { const char *ptr = ecs_expr_run(world, "{5 + 5, 10 + 10}", &(ecs_value_t){ .type = t, .ptr = &v }, &desc); + test_assert(ptr != NULL); test_assert(!ptr[0]); diff --git a/test/script/src/Template.c b/test/script/src/Template.c index d9b2c96fc..9ea68fff5 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -3102,3 +3102,81 @@ void Template_clear_script_w_template_w_on_remove_observer_added_after(void) { ecs_fini(world); } + +void Template_component_w_assign_add(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Tree {" + LINE " prop height = f32: 0" + LINE " Position: {y += height / 2}" + LINE "}" + LINE "" + HEAD "Foo {" + LINE " Position: {10, 20}" + LINE " Tree: {6}" + LINE "}" + ; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, NULL); + test_assert(s != NULL); + + test_assert(ecs_script_eval(s, NULL) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 23); + + ecs_script_free(s); + + ecs_fini(world); +} + +void Template_component_w_assign_mul(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Tree {" + LINE " prop height = f32: 0" + LINE " Position: {y *= height / 2}" + LINE "}" + LINE "" + HEAD "Foo {" + LINE " Position: {10, 20}" + LINE " Tree: {6}" + LINE "}" + ; + + ecs_script_t *s = ecs_script_parse(world, NULL, expr, NULL); + test_assert(s != NULL); + + test_assert(ecs_script_eval(s, NULL) == 0); + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + const Position *ptr = ecs_get(world, foo, Position); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 60); + + ecs_script_free(s); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index fd538691a..21ef2a199 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -315,6 +315,8 @@ void Eval_component_w_match_invalid(void); void Eval_pair_component_w_match(void); void Eval_component_assign_w_match(void); void Eval_const_w_match(void); +void Eval_component_w_assign_add(void); +void Eval_component_w_assign_mul(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -383,6 +385,8 @@ void Template_template_w_child_component_w_undefined_identifier(void); void Template_template_w_anonymous_child_component_w_undefined_identifier(void); void Template_clear_script_w_template_w_on_remove_observer(void); void Template_clear_script_w_template_w_on_remove_observer_added_after(void); +void Template_component_w_assign_add(void); +void Template_component_w_assign_mul(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -900,6 +904,9 @@ void Deserialize_struct_w_2_array_type_struct(void); void Deserialize_struct_w_newline(void); void Deserialize_struct_w_members_newline(void); void Deserialize_struct_w_trailing_comma(void); +void Deserialize_struct_w_add_assign_expr(void); +void Deserialize_struct_w_mul_assign_expr(void); +void Deserialize_struct_w_add_assign_expr_invalid_type(void); void Deserialize_array_w_trailing_comma(void); void Deserialize_array_i32_2(void); void Deserialize_array_string_2(void); @@ -2158,6 +2165,14 @@ bake_test_case Eval_testcases[] = { { "const_w_match", Eval_const_w_match + }, + { + "component_w_assign_add", + Eval_component_w_assign_add + }, + { + "component_w_assign_mul", + Eval_component_w_assign_mul } }; @@ -2425,6 +2440,14 @@ bake_test_case Template_testcases[] = { { "clear_script_w_template_w_on_remove_observer_added_after", Template_clear_script_w_template_w_on_remove_observer_added_after + }, + { + "component_w_assign_add", + Template_component_w_assign_add + }, + { + "component_w_assign_mul", + Template_component_w_assign_mul } }; @@ -4456,6 +4479,18 @@ bake_test_case Deserialize_testcases[] = { "struct_w_trailing_comma", Deserialize_struct_w_trailing_comma }, + { + "struct_w_add_assign_expr", + Deserialize_struct_w_add_assign_expr + }, + { + "struct_w_mul_assign_expr", + Deserialize_struct_w_mul_assign_expr + }, + { + "struct_w_add_assign_expr_invalid_type", + Deserialize_struct_w_add_assign_expr_invalid_type + }, { "array_w_trailing_comma", Deserialize_array_w_trailing_comma @@ -4599,14 +4634,14 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 306, + 308, Eval_testcases }, { "Template", NULL, NULL, - 66, + 68, Template_testcases }, { @@ -4650,7 +4685,7 @@ static bake_test_suite suites[] = { "Deserialize", Deserialize_setup, NULL, - 92, + 95, Deserialize_testcases, 1, Deserialize_params From 4422b0828135440998665518829f10d0db17b475 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 28 Dec 2024 14:16:35 -0800 Subject: [PATCH 26/36] Allow props to have implicit types --- distr/flecs.c | 157 ++++++++++++++-------------- docs/FlecsScript.md | 196 ++++++++++++++++++----------------- src/addons/script/parser.c | 114 ++++++++++---------- src/addons/script/template.c | 45 +++++--- test/script/project.json | 1 + test/script/src/Template.c | 49 +++++++++ test/script/src/main.c | 7 +- 7 files changed, 316 insertions(+), 253 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 9da0f89f0..54ae65179 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56670,6 +56670,57 @@ const char* flecs_script_if_stmt( ParserEnd; } +static +const char* flecs_script_parse_var( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + bool is_prop) +{ + Parse_1(EcsTokIdentifier, + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1)); + var->node.kind = is_prop ? EcsAstProp : EcsAstConst; + + Parse( + // const color = + case '=': { + // const color = Color : + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + var->type = Token(3); + + // const color = Color: { + LookAhead_1('{', + // const color = Color: {expr} + pos = lookahead; + Initializer('}', + var->expr = INITIALIZER; + EndOfRule; + ) + ) + + // const color = Color: expr\n + Initializer('\n', + var->expr = INITIALIZER; + EndOfRule; + ) + ) + + // const PI = expr\n + Expr('\n', + var->expr = EXPR; + EndOfRule; + ) + } + ) + ) + +error: + return NULL; +} + /* Parse a single statement */ static const char* flecs_script_stmt( @@ -56863,72 +56914,13 @@ template_stmt: { // prop prop_var: { // prop color = Color: - Parse_4(EcsTokIdentifier, '=', EcsTokIdentifier, ':', - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = EcsAstProp; - var->type = Token(3); - - // prop color = Color : { - LookAhead_1('{', - // prop color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // prop color = Color : expr\n - Initializer('\n', - var->expr = INITIALIZER; - EndOfRule; - ) - ) + return flecs_script_parse_var(parser, pos, tokenizer, true); } // const const_var: { // const color - Parse_1(EcsTokIdentifier, - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = EcsAstConst; - - Parse( - // const color = - case '=': { - // const color = Color : - LookAhead_2(EcsTokIdentifier, ':', - pos = lookahead; - - var->type = Token(3); - - // const color = Color: { - LookAhead_1('{', - // const color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // const color = Color: expr\n - Initializer('\n', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // const PI = expr\n - Expr('\n', - var->expr = EXPR; - EndOfRule; - ) - } - ) - ) + return flecs_script_parse_var(parser, pos, tokenizer, false); } // if @@ -59303,8 +59295,10 @@ int flecs_script_template_eval_prop( return -1; } + ecs_entity_t type; + const ecs_type_info_t *ti; + if (node->type) { - ecs_entity_t type; if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", @@ -59312,7 +59306,7 @@ int flecs_script_template_eval_prop( return -1; } - const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); + ti = flecs_script_get_type_info(v, node, type); if (!ti) { return -1; } @@ -59325,23 +59319,32 @@ int flecs_script_template_eval_prop( if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } + } else { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + return -1; + } - ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, - &template->prop_defaults, ecs_script_var_t); - value->value.ptr = flecs_calloc_w_dbg_info( - &v->base.script->allocator, ti->size, ti->name); - value->value.type = type; - value->type_info = ti; - ecs_value_copy_w_type_info( - v->world, ti, value->value.ptr, var->value.ptr); + type = var->value.type; + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + } - ecs_entity_t mbr = ecs_entity(v->world, { - .name = node->name, - .parent = template->entity - }); + ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, + &template->prop_defaults, ecs_script_var_t); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); + value->value.type = type; + value->type_info = ti; + ecs_value_copy_w_type_info( + v->world, ti, value->value.ptr, var->value.ptr); - ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); - } + ecs_entity_t mbr = ecs_entity(v->world, { + .name = node->name, + .parent = template->entity + }); + + ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); return 0; } diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 34f5da194..6b35bed6d 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -7,15 +7,15 @@ Some of the features of Flecs Script are: - Native support for named entities, hierarchies and inheritance - Assign component values -- Expressions and variables (`$var + 10`) -- Conditionals (`if $var > 10`) +- Expressions and variables (`var + 10`) +- Conditionals (`if var > 10`) - Native integration with templates (procedural assets) To learn Flecs Script, check out the [Tutorial](FlecsScriptTutorial.md)! ## Example -```c +```cpp using flecs.meta struct MaxSpeed { @@ -46,13 +46,13 @@ This section goes over the basic syntax over Flecs Script. ### Entities An entity is created by specifying an identifier followed by a scope. Example: -```c +```cpp my_entity {} ``` An entity scope can contain components and child entities. The following example shows how to add a child entity: -```c +```cpp my_parent { my_child {} } @@ -62,7 +62,7 @@ Note how a scope is also added to the child entity. To create anonymous entities, leave out the entity name: -```c +```cpp { my_child {} // named child with anonymous parent } @@ -70,7 +70,7 @@ To create anonymous entities, leave out the entity name: Alternatively, the `_` placeholder can be used to indicate an anomyous entity: -```c +```cpp _ { my_child {} // named child with anonymous parent } @@ -78,14 +78,14 @@ _ { The `_` placeholder can be useful in combination with syntax constructs that require an identifier token, such as inheritance: -```c +```cpp // anonymous entity that inherits from SpaceShip _ : SpaceShip { } ``` Entity names can be specified using a string. This allows for entities with names that contain special characters, like spaces: -```c +```cpp "my parent" { "my child" {} } @@ -93,14 +93,14 @@ Entity names can be specified using a string. This allows for entities with name String names can be combined with string interpolation (see below) to create names that are computed when the script is evaluated: -```c +```cpp "USS_$name" {} ``` ### Tags A tag can be added to an entity by simply specifying the tag's identifier in an entity scope. Example: -```c +```cpp SpaceShip {} // Define SpaceShip tag my_entity { @@ -111,7 +111,7 @@ my_entity { ### Pairs Pairs are added to entities by adding them to an entity scope, just like tags: -```c +```cpp Likes {} Pizza {} @@ -123,7 +123,7 @@ my_entity { ### Components Components are specified like tags, but with an additional value: -```c +```cpp my_entity { Position: {x: 10, y: 20} } @@ -133,7 +133,7 @@ For a component to be assignable with a value, it also needs to be described in A component can also be added without a value. This will create a default constructed component. Example: -```c +```cpp my_entity { Position } @@ -141,7 +141,7 @@ my_entity { Components can be defined in a script: -```c +```cpp using flecs.meta struct Position { @@ -156,7 +156,7 @@ my_entity { Components can be pairs: -```c +```cpp my_entity { (Start, Position): {x: 0, y: 0} (Stop, Position): {x: 10, y: 20} @@ -166,7 +166,7 @@ my_entity { ### Namespacing When referring to child entities or components, identifiers need to include the parent path as well as the entity name. Paths are provided as lists of identifiers separated by a dot (`.`): -```c +```cpp Sun.Earth { solarsystem.Planet } @@ -177,7 +177,7 @@ To avoid having to repeatedly type the same paths, use the `using` statement (se ### Singletons To create a singleton component, use `$` as the entity identifier: -```c +```cpp $ { TimeOfDay: { t: 0.5 } } @@ -185,7 +185,7 @@ $ { Multiple singleton components can be specified in the same scope: -```c +```cpp $ { TimeOfDay: { t: 0.5 } Player: { name: "bob" } @@ -196,13 +196,13 @@ $ { ### Entity kinds An entity can be created with a "kind", which is a component specified before the entity name. This is similar to adding a tag or component in a scope, but can provide a more natural way to describe things. For example: -```c +```cpp SpaceShip my_spaceship {} ``` This is equivalent to doing: -```c +```cpp my_spaceship { SpaceShip } @@ -210,19 +210,19 @@ my_spaceship { When using the entity kind syntax, the scope is optional: -```c +```cpp SpaceShip my_spaceship // no {} ``` If the specified kind is a component, a value can be specified between parentheses: -```c +```cpp CheckBox my_checkbox(checked: true) ``` When the entity kind is a component, a value will always be assigned even if none is specified. This is different from component assignments in a scope. Example: -```c +```cpp CheckBox my_checkbox(checked: true) // is equivalent to @@ -232,7 +232,7 @@ my_checkbox { } ``` -```c +```cpp CheckBox my_checkbox // is equivalent to @@ -245,7 +245,7 @@ my_checkbox { #### Builtin kinds Applications can specify the following builtin kinds which provide convenience shortcuts to commonly used features: -```c +```cpp prefab SpaceShip // is equivalent to @@ -253,7 +253,7 @@ prefab SpaceShip Prefab spaceship ``` -```c +```cpp prefab SpaceShip { slot CockPit } @@ -270,7 +270,7 @@ prefab SpaceShip { ### Inheritance Scripts can natively specify inheritance relationships between entities, which is useful in particular for prefabs. Example: -```c +```cpp prefab SpaceShip { MaxSpeed: {value: 100} } @@ -280,13 +280,13 @@ my_spaceship : SpaceShip {} When specifying inheritance, the scope is optional: -```c +```cpp my_spaceship : SpaceShip // no {} ``` This is equivalent to doing: -```c +```cpp my_spaceship { (IsA, SpaceShip) } @@ -295,7 +295,7 @@ my_spaceship { ### Relationship hierarchies By default entity hierarchies are created with the `ChildOf` relationship. Other relationships can also be used to create hierarchies by combining a pair with a scope. Example: -```c +```cpp (IsA, Thing) { (IsA, Organism) { (IsA, Plant) { @@ -311,7 +311,7 @@ By default entity hierarchies are created with the `ChildOf` relationship. Other ## Expressions Scripts can contain expressions, which allow for computing values from inputs such as component values, template properties and variables. Here are some examples of valid Flecs script expressions: -```c +```cpp const x = 10 + 20 * 30 const x = 10 * (20 + 30) const x = $var * 10 @@ -364,7 +364,7 @@ The following table lists the different kinds of values that are supported in ex #### Initializers Initializers are values that are used to initialize composite and collection members. Composite values are initialized by initializers that are delimited by `{}`, while collection initializers are delimited by `[]`. Furthermore, composite initializers can specify which member of the composite value should be initialized. Here are some examples of initializer expressions: -```c +```cpp {} {10, 20} {x: 10, y: 20} @@ -377,20 +377,20 @@ Initializers are values that are used to initialize composite and collection mem Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: -```c +```cpp const x = Position: {10, 20} ``` while this is an invalid usage of an initializer: -```c +```cpp // Invalid, unknown type for initializer const x = {10, 20} ``` When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element: -```c +```cpp // Normal notation Tree: {color: $color, height: $height} @@ -401,7 +401,7 @@ Tree: {color: $, height: $} Initializer expressions may contain add assignment (`+=`) or multiply assignment (`*=`) operators. These operators allow an initializer to modify an existing value. An example: -```c +```cpp e { Position: {10, 20} Position: {x += 1, y += 2} @@ -431,7 +431,7 @@ e { ### Match expressions Match expressions can be used to conditionally assign a value. An example: -```c +```cpp const x = 1 // y will be assigned with value 10 @@ -444,7 +444,7 @@ const y = match x { The input to a match expression must be matched by one of its cases. If the input is not matched, script execution will fail. Match expressions can include an "any" case, which is selected when none of the other cases match: -```c +```cpp const x = 4 // y will be assigned with value 100 @@ -458,7 +458,7 @@ const y = match x { Match expressions can be used to assign components: -```c +```cpp e { Position: match i { 1: {10, 20} @@ -473,19 +473,19 @@ The type of a match expression is derived from the case values. When the case st ### String interpolation Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: -```c +```cpp const x = "The value of PI is $PI" ``` The following example shows how to use an expression: -```c +```cpp const x = "The circumference of the circle is {2 * $PI * $r}" ``` To prevent evaluating expressions in an interpolated string, the `$` and `{` characters can be escaped: -```c +```cpp const x = "The value of variable \$x is $x" ``` @@ -582,7 +582,7 @@ Type expressiveness is determined by the kind of type and its storage size. The The function to determine whether a type is implicitly castable is: -```c +```cpp bool implicit_cast_allowed(from, to) { if (expressiveness(to) >= expressiveness(from)) { return storage(to) >= storage(from); @@ -601,19 +601,19 @@ Lvalues are the left side of assignments. There are two kinds of assignments pos The type of an expression can be influenced by the type of the lvalue it is assigned to. For example, if the lvalue is a variable of type `Position`, the assigned initializer will also be of type `Position`: -```c +```cpp const p = Position: {10, 20} ``` Similarly, when an initializer is used inside of an initializer, it obtains the type of the initializer element. In the following example the outer initializer is of type `Line`, while the inner initializers are of type `Point`: -```c +```cpp const l = Line: {{10, 20}, {30, 40}} ``` Another notable example where this matters is for enum and bitmask constants. Consider the following example: -```c +```cpp const c = Color: Red ``` @@ -622,7 +622,7 @@ Here, `Red` is a resolvable identifier, even though the fully qualified identifi ### Functions Expressions can call functions. Functions in Flecs script can have arguments of any type, and must return a value. The following snippet shows examples of function calls: -```c +```cpp const x = sqrt(100) const x = pow(100, 2) const x = add({10, 20}, {30, 40}) @@ -632,7 +632,7 @@ Currently functions can only be defined outside of scripts by the Flecs Script A A function can be created in code by doing: -```c +```cpp ecs_function(world, { .name = "sum", .return_type = ecs_id(ecs_i64_t), @@ -647,7 +647,7 @@ ecs_function(world, { ### Methods Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: -```c +```cpp const x = v.length() const x = v1.add(v2) ``` @@ -656,7 +656,7 @@ Just like functions, methods can currently only be defined outside of scripts by A method can be created in code by doing: -```c +```cpp ecs_method(world, { .name = "add", .parent = ecs_id(ecs_i64_t), // Add method to i64 @@ -751,14 +751,14 @@ const x = $rng.f(1.0) To use the math functions, make sure to use a Flecs build compiled with the `FLECS_SCRIPT_MATH` addon (disabled by default) and that the module is imported: -```c +```cpp ECS_IMPORT(world, FlecsScriptMath); ``` ## Templates Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: -```c +```cpp template Square { Color: {255, 0, 0} Rectangle: {width: 100, height: 100} @@ -767,7 +767,7 @@ template Square { The script contents of an template are not ran immediately. Instead they are ran whenever an template is _instantiated_. To instantiate an template, add it as a regular component to an entity: -```c +```cpp my_entity { Square } @@ -782,29 +782,31 @@ my_entity { Templates are commonly used in combination with the kind syntax: -```c +```cpp Square my_entity ``` Templates can be parameterized with properties. Properties are variables that are exposed as component members. To create a property, use the `prop` keyword. Example: -```c +```cpp template Square { - prop size = i32: 10 + prop size: 10 prop color = Color: {255, 0, 0} $color - Rectangle: {width: $size, height: $size} + Rectangle: {width: size, height: size} } Square my_entity(size: 20, color: {38, 25, 13}) ``` +Just like `const` variables, `prop` variables can explicitly specify a type or implicitly derive their type from the assigned (default) value. + Template scripts can do anything a regular script can do, including creating child entities. The following example shows how to create an template that uses a nested template to create children: -```c +```cpp template Tree { - prop height = f32: 10 + prop height: 10 const wood_color = Color: {38, 25, 13} const leaves_color = Color: {51, 76, 38} @@ -850,7 +852,7 @@ Forest my_forest ### Module statement The `module` statement puts all contents of a script in a module. Example: -```c +```cpp module components.transform // Creates components.transform.Position @@ -865,14 +867,14 @@ The `components.transform` entity will be created with the `Module` tag. ### Using statement The `using` keyword imports a namespace into the current namespace. Example: -```c +```cpp // Without using flecs.meta.struct Position { x = flecs.meta.f32 y = flecs.meta.f32 } ``` -```c +```cpp // With using using flecs.meta @@ -884,13 +886,13 @@ struct Position { The `using` keyword only applies to the scope in which it is specified. Example: -```c +```cpp // Scope without using my_engine { game.engines.FtlEngine: {active: true} } ``` -```c +```cpp // Scope with using my_spaceship { using game.engines @@ -901,7 +903,7 @@ my_spaceship { A `using` statement may end with a wildcard (`*`). This will import all namespaces matching the path. Example: -```c +```cpp using flecs.* struct Position { @@ -913,7 +915,7 @@ struct Position { ### With statement When you're building a scene or asset you may find yourself often repeating the same components for multiple entities. To avoid this, a `with` statement can be used. For example: -```c +```cpp with SpaceShip { MillenniumFalcon {} UssEnterprise {} @@ -924,7 +926,7 @@ with SpaceShip { This is equivalent to doing: -```c +```cpp MillenniumFalcon { SpaceShip } @@ -944,7 +946,7 @@ Rocinante { With statements can contain multiple tags: -```c +```cpp with SpaceShip, HasWeapons { MillenniumFalcon {} UssEnterprise {} @@ -955,7 +957,7 @@ with SpaceShip, HasWeapons { With statements can contain component values, specified between parentheses: -```c +```cpp with Color(38, 25, 13) { pillar_1 {} pillar_2 {} @@ -966,7 +968,7 @@ with Color(38, 25, 13) { ### Variables Scripts can contain variables, which are useful for often repeated values. Variables are created with the `const` keyword. Example: -```c +```cpp const pi = 3.1415926 my_entity { @@ -976,7 +978,7 @@ my_entity { Variables can be combined with expressions: -```c +```cpp const pi = 3.1415926 const pi_2 = $pi * 2 @@ -987,13 +989,13 @@ my_entity { In the above examples, the type of the variable is inferred. Variables can also be provided with an explicit type: -```c +```cpp const wood = Color: {38, 25, 13} ``` When the name of a variable clashes with an entity, it can be disambiguated by prefixing the variable name with a `$`: -```c +```cpp const pi = 3.1415926 const pi_2 = $pi * 2 @@ -1004,7 +1006,7 @@ pi { Variables can be used in component values as shown in the previous examples, or can be used directly as component. When used like this, the variable name must be prefixed with a `$`. Example: -```c +```cpp const wood = Color: {38, 25, 13} my_entity { @@ -1020,7 +1022,7 @@ my_entity { Additionally, variables can also be used in combination with `with` statements. When used like this the variable name must also be prefixed with a `$`: -```c +```cpp const wood = Color: {38, 25, 13} with $color { @@ -1033,7 +1035,7 @@ with $color { ### Component values A script can use the value of a component that is looked up on a specific entity. The following example fetches the `width` and `depth` members from the `Level` component, that is fetched from the `Game` entity: -```c +```cpp grid { Grid: { Game[Level].width, Game[Level].depth } } @@ -1041,7 +1043,7 @@ grid { To reduce the number of component lookups in a script, the component value can be stored in a variable: -```c +```cpp const level = Game[Level] tiles { @@ -1054,7 +1056,7 @@ The requested component is stored by value, not by reference. Adding or removing ### If statement Parts of a script can be conditionally executed with an if statement. Example: -```c +```cpp const daytime = bool: false lantern { @@ -1070,7 +1072,7 @@ lantern { If statements can be chained with `else if`: -```c +```cpp const state = 0 traffic_light { @@ -1087,7 +1089,7 @@ traffic_light { ### For statement Parts of a script can be repeated with a for loop. Example: -```c +```cpp for i in 0..10 { Lantern() { Position: {x: $i * 5} @@ -1097,7 +1099,7 @@ for i in 0..10 { The values specified in the range can be an expression: -```c +```cpp for i in 0..$count { // ... } @@ -1105,7 +1107,7 @@ for i in 0..$count { When creating entities in a for loop, ensure that they are unique or the for loop will overwrite the same entity: -```c +```cpp for i in 0..10 { // overwrites entity "e" 10 times e: { Position: {x: $i * 5} } @@ -1114,7 +1116,7 @@ for i in 0..10 { To avoid this, scripts can either create anonymous entities: -```c +```cpp for i in 0..10 { // creates 10 anonymous entities _ { Position: {x: $i * 5} } @@ -1123,7 +1125,7 @@ for i in 0..10 { Or use a unique string expression for the entity name: -```c +```cpp for i in 0..10 { // creates entities with names e_0, e_1, ... e_9 "e_$i" { Position: {x: $i * 5} } @@ -1135,7 +1137,7 @@ A scope can have a default component, which means entities in that scope can ass There are different ways to specify a default component. One way is to use a `with` statement. Default component values are assigned with the `=` operator, and don't need a `{}` surrounding the value. Example: -```c +```cpp with Position { ent_a = 10, 20 ent_b = 20, 30 @@ -1144,7 +1146,7 @@ with Position { Another way a default components are derived is from the entity kind. If an entity is specified with a kind, a `DefaultChildComponent` component will be looked up on the kind to find the default component for the scope, if any. For example: -```c +```cpp // Create a PositionList tag with a DefaultChildComponent PositionList { DefaultChildComponent: {Position} @@ -1160,7 +1162,7 @@ PositionList plist { A common use of default components is when creating structs. `struct` is a component with `member` as default child component. Example: -```c +```cpp struct Position { x = f32 y = f32 @@ -1176,7 +1178,7 @@ struct Position { Note how `member` is also used as kind for the children. This means that children of `x` and `y` derive their default child component from `member`, which is set to `member`. This makes it easy to create nested members: -```c +```cpp struct Line { start { x = f32 @@ -1205,7 +1207,7 @@ struct Line { ### Semicolon operator Multiple statements can be combined on a single line when using the semicolon operator. Example: -```c +```cpp my_spaceship { SpaceShip; HasFtl } @@ -1214,7 +1216,7 @@ my_spaceship { ### Comma operator The comma operator can be used as a shortcut to create multiple entities in a scope. Example: -```c +```cpp my_spaceship { pilot_a, pilot_b, @@ -1232,7 +1234,7 @@ my_spaceship { This allows for a more natural way to describe things like enum types: -```c +```cpp enum Color { Red, Green, @@ -1254,7 +1256,7 @@ This section goes over how to run scripts in an application. ### Run once To run a script once, use the `ecs_script_run` function. Example: -```c +```cpp const char *code = "my_spaceship {}"; if (ecs_script_run(world, "my_script_name", code)) { @@ -1264,7 +1266,7 @@ if (ecs_script_run(world, "my_script_name", code)) { Alternatively a script can be ran directly from a file: -```c +```cpp if (ecs_script_run_file(world, "my_script.flecs")) { // error } @@ -1275,7 +1277,7 @@ If a script fails, the entities created by the script will not be automatically ### Run multiple times A script can be ran multiple times by using the `ecs_script_parse` and `ecs_script_eval` functions. Example: -```c +```cpp const char *code = "my_spaceship {}"; ecs_script_t *script = ecs_script_parse( @@ -1303,7 +1305,7 @@ Managed scripts are scripts that are associated with an entity, and can be ran m To run a managed script, do: -```c +```cpp const char *code = "my_spaceship {}"; ecs_entity_t s = ecs_script(world, { @@ -1317,7 +1319,7 @@ if (!s) { To update the code of a managed script, use the `ecs_script_update` function: -```c +```cpp if (ecs_script_update(world, s, 0, new_code)) { // error } diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 9a87eb83c..898e6aeaa 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -284,6 +284,57 @@ const char* flecs_script_if_stmt( ParserEnd; } +static +const char* flecs_script_parse_var( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + bool is_prop) +{ + Parse_1(EcsTokIdentifier, + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1)); + var->node.kind = is_prop ? EcsAstProp : EcsAstConst; + + Parse( + // const color = + case '=': { + // const color = Color : + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + var->type = Token(3); + + // const color = Color: { + LookAhead_1('{', + // const color = Color: {expr} + pos = lookahead; + Initializer('}', + var->expr = INITIALIZER; + EndOfRule; + ) + ) + + // const color = Color: expr\n + Initializer('\n', + var->expr = INITIALIZER; + EndOfRule; + ) + ) + + // const PI = expr\n + Expr('\n', + var->expr = EXPR; + EndOfRule; + ) + } + ) + ) + +error: + return NULL; +} + /* Parse a single statement */ static const char* flecs_script_stmt( @@ -477,72 +528,13 @@ template_stmt: { // prop prop_var: { // prop color = Color: - Parse_4(EcsTokIdentifier, '=', EcsTokIdentifier, ':', - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = EcsAstProp; - var->type = Token(3); - - // prop color = Color : { - LookAhead_1('{', - // prop color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // prop color = Color : expr\n - Initializer('\n', - var->expr = INITIALIZER; - EndOfRule; - ) - ) + return flecs_script_parse_var(parser, pos, tokenizer, true); } // const const_var: { // const color - Parse_1(EcsTokIdentifier, - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = EcsAstConst; - - Parse( - // const color = - case '=': { - // const color = Color : - LookAhead_2(EcsTokIdentifier, ':', - pos = lookahead; - - var->type = Token(3); - - // const color = Color: { - LookAhead_1('{', - // const color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // const color = Color: expr\n - Initializer('\n', - var->expr = INITIALIZER; - EndOfRule; - ) - ) - - // const PI = expr\n - Expr('\n', - var->expr = EXPR; - EndOfRule; - ) - } - ) - ) + return flecs_script_parse_var(parser, pos, tokenizer, false); } // if diff --git a/src/addons/script/template.c b/src/addons/script/template.c index aeba9960c..f8d1ab697 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -311,8 +311,10 @@ int flecs_script_template_eval_prop( return -1; } + ecs_entity_t type; + const ecs_type_info_t *ti; + if (node->type) { - ecs_entity_t type; if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", @@ -320,7 +322,7 @@ int flecs_script_template_eval_prop( return -1; } - const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); + ti = flecs_script_get_type_info(v, node, type); if (!ti) { return -1; } @@ -333,24 +335,33 @@ int flecs_script_template_eval_prop( if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } + } else { + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + return -1; + } - ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, - &template->prop_defaults, ecs_script_var_t); - value->value.ptr = flecs_calloc_w_dbg_info( - &v->base.script->allocator, ti->size, ti->name); - value->value.type = type; - value->type_info = ti; - ecs_value_copy_w_type_info( - v->world, ti, value->value.ptr, var->value.ptr); - - ecs_entity_t mbr = ecs_entity(v->world, { - .name = node->name, - .parent = template->entity - }); - - ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); + type = var->value.type; + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); } + ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, + &template->prop_defaults, ecs_script_var_t); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); + value->value.type = type; + value->type_info = ti; + ecs_value_copy_w_type_info( + v->world, ti, value->value.ptr, var->value.ptr); + + ecs_entity_t mbr = ecs_entity(v->world, { + .name = node->name, + .parent = template->entity + }); + + ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); + return 0; } diff --git a/test/script/project.json b/test/script/project.json index 1bdf7dc1e..e2d3e2a49 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -334,6 +334,7 @@ "template_instance_w_default_values", "template_instance_w_assign_default_values", "template_instance_w_overridden_values", + "template_w_prop_implicit_type", "template_w_child", "template_w_child_parse_script", "template_w_child_parse_script_twice", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 9ea68fff5..d66d75f47 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -342,6 +342,55 @@ void Template_template_instance_w_overridden_values(void) { ecs_fini(world); } +void Template_template_w_prop_implicit_type(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "template Tree {" + LINE " prop width = 10" + LINE " prop height = 20" + LINE "}" + LINE "" + LINE "e { Tree }" + LINE ""; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t tree = ecs_lookup(world, "Tree"); + test_assert(tree != 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + const EcsStruct *st = ecs_get(world, tree, EcsStruct); + test_assert(st != NULL); + test_int(st->members.count, 2); + test_str(ecs_vec_get_t(&st->members, ecs_member_t, 0)->name, "width"); + test_uint(ecs_vec_get_t(&st->members, ecs_member_t, 0)->type, ecs_id(ecs_i64_t)); + test_str(ecs_vec_get_t(&st->members, ecs_member_t, 1)->name, "height"); + test_uint(ecs_vec_get_t(&st->members, ecs_member_t, 1)->type, ecs_id(ecs_i64_t)); + + test_assert(ecs_has_id(world, e, tree)); + const void *ptr = ecs_get_id(world, e, tree); + test_assert(ptr != NULL); + char *str = ecs_ptr_to_expr(world, tree, ptr); + test_assert(str != NULL); + test_str(str, "{width: 10, height: 20}"); + ecs_os_free(str); + + ecs_fini(world); +} + void Template_template_w_child(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 21ef2a199..fe0745ff5 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -328,6 +328,7 @@ void Template_template_w_using(void); void Template_template_instance_w_default_values(void); void Template_template_instance_w_assign_default_values(void); void Template_template_instance_w_overridden_values(void); +void Template_template_w_prop_implicit_type(void); void Template_template_w_child(void); void Template_template_w_child_parse_script(void); void Template_template_w_child_parse_script_twice(void); @@ -2213,6 +2214,10 @@ bake_test_case Template_testcases[] = { "template_instance_w_overridden_values", Template_template_instance_w_overridden_values }, + { + "template_w_prop_implicit_type", + Template_template_w_prop_implicit_type + }, { "template_w_child", Template_template_w_child @@ -4641,7 +4646,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 68, + 69, Template_testcases }, { From a400e8351010a05b121a47d36ef93ce846e69161 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 28 Dec 2024 14:37:01 -0800 Subject: [PATCH 27/36] Deprecate const var = ... notation in favor of const var: ... --- distr/flecs.c | 63 +++++++++++++++++- distr/flecs.h | 22 +++++++ include/flecs/addons/log.h | 22 +++++++ src/addons/log.c | 46 ++++++++++++- src/addons/script/parser.c | 12 ++++ src/addons/script/parser.h | 5 ++ test/script/project.json | 2 +- test/script/src/Error.c | 24 ------- test/script/src/Eval.c | 131 +++++++++++++++++++++++-------------- test/script/src/ExprAst.c | 4 +- test/script/src/Template.c | 22 +++---- test/script/src/main.c | 14 ++-- 12 files changed, 267 insertions(+), 100 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 54ae65179..3ef8ce1e7 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -24507,12 +24507,13 @@ void ecs_log_pop_( } } -void ecs_parser_errorv_( +void flecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, - va_list args) + va_list args, + bool is_warning) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the @@ -24588,11 +24589,35 @@ void ecs_parser_errorv_( } char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); + if (is_warning) { + ecs_os_warn(name, 0, msg); + } else { + ecs_os_err(name, 0, msg); + } ecs_os_free(msg); } } +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + flecs_parser_errorv(name, expr, column_arg, fmt, args, false); +} + +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + flecs_parser_errorv(name, expr, column_arg, fmt, args, true); +} + void ecs_parser_error_( const char *name, const char *expr, @@ -24608,6 +24633,21 @@ void ecs_parser_error_( } } +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + ecs_parser_warningv_(name, expr, column, fmt, args); + va_end(args); + } +} + void ecs_abort_( int32_t err, const char *file, @@ -56194,6 +56234,11 @@ void FlecsScriptMathImport( (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ goto error +/* Warning */ +#define Warning(...)\ + ecs_parser_warning(parser->script->pub.name, parser->script->pub.code,\ + (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ + /* Parse expression */ #define Expr(until, ...)\ {\ @@ -56709,6 +56754,18 @@ const char* flecs_script_parse_var( ) // const PI = expr\n + Expr('\n', + Warning("'%s var = expr' syntax is deprecated" + ", use '%s var: expr' instead", + is_prop ? "prop" : "const", + is_prop ? "prop" : "const"); + var->expr = EXPR; + EndOfRule; + ) + } + + case ':': { + // const PI: expr\n Expr('\n', var->expr = EXPR; EndOfRule; diff --git a/distr/flecs.h b/distr/flecs.h index 6d15de6a8..585a353e5 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -10727,6 +10727,22 @@ void ecs_parser_errorv_( const char *fmt, va_list args); +FLECS_API +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +FLECS_API +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args); + //////////////////////////////////////////////////////////////////////////////// //// Logging macros @@ -10972,6 +10988,12 @@ void ecs_parser_errorv_( #define ecs_parser_errorv(name, expr, column, fmt, args)\ ecs_parser_errorv_(name, expr, column, fmt, args) +#define ecs_parser_warning(name, expr, column, ...)\ + ecs_parser_warning_(name, expr, column, __VA_ARGS__) + +#define ecs_parser_warningv(name, expr, column, fmt, args)\ + ecs_parser_warningv_(name, expr, column, fmt, args) + #endif // FLECS_LEGACY diff --git a/include/flecs/addons/log.h b/include/flecs/addons/log.h index 523097556..d63221b41 100644 --- a/include/flecs/addons/log.h +++ b/include/flecs/addons/log.h @@ -180,6 +180,22 @@ void ecs_parser_errorv_( const char *fmt, va_list args); +FLECS_API +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +FLECS_API +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args); + //////////////////////////////////////////////////////////////////////////////// //// Logging macros @@ -425,6 +441,12 @@ void ecs_parser_errorv_( #define ecs_parser_errorv(name, expr, column, fmt, args)\ ecs_parser_errorv_(name, expr, column, fmt, args) +#define ecs_parser_warning(name, expr, column, ...)\ + ecs_parser_warning_(name, expr, column, __VA_ARGS__) + +#define ecs_parser_warningv(name, expr, column, fmt, args)\ + ecs_parser_warningv_(name, expr, column, fmt, args) + #endif // FLECS_LEGACY diff --git a/src/addons/log.c b/src/addons/log.c index 713a3083e..758aedac2 100644 --- a/src/addons/log.c +++ b/src/addons/log.c @@ -226,12 +226,13 @@ void ecs_log_pop_( } } -void ecs_parser_errorv_( +void flecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, - va_list args) + va_list args, + bool is_warning) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the @@ -307,11 +308,35 @@ void ecs_parser_errorv_( } char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); + if (is_warning) { + ecs_os_warn(name, 0, msg); + } else { + ecs_os_err(name, 0, msg); + } ecs_os_free(msg); } } +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + flecs_parser_errorv(name, expr, column_arg, fmt, args, false); +} + +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + flecs_parser_errorv(name, expr, column_arg, fmt, args, true); +} + void ecs_parser_error_( const char *name, const char *expr, @@ -327,6 +352,21 @@ void ecs_parser_error_( } } +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + ecs_parser_warningv_(name, expr, column, fmt, args); + va_end(args); + } +} + void ecs_abort_( int32_t err, const char *file, diff --git a/src/addons/script/parser.c b/src/addons/script/parser.c index 898e6aeaa..e614fb10e 100644 --- a/src/addons/script/parser.c +++ b/src/addons/script/parser.c @@ -323,6 +323,18 @@ const char* flecs_script_parse_var( ) // const PI = expr\n + Expr('\n', + Warning("'%s var = expr' syntax is deprecated" + ", use '%s var: expr' instead", + is_prop ? "prop" : "const", + is_prop ? "prop" : "const"); + var->expr = EXPR; + EndOfRule; + ) + } + + case ':': { + // const PI: expr\n Expr('\n', var->expr = EXPR; EndOfRule; diff --git a/src/addons/script/parser.h b/src/addons/script/parser.h index 4bd25c3d3..47e86a654 100644 --- a/src/addons/script/parser.h +++ b/src/addons/script/parser.h @@ -63,6 +63,11 @@ (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ goto error +/* Warning */ +#define Warning(...)\ + ecs_parser_warning(parser->script->pub.name, parser->script->pub.code,\ + (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ + /* Parse expression */ #define Expr(until, ...)\ {\ diff --git a/test/script/project.json b/test/script/project.json index e2d3e2a49..9d922f0b0 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -167,6 +167,7 @@ "const_var_string", "const_var_struct", "const_var_scoped", + "const_var_int_deprecated_notation", "assign_component_from_var", "assign_component_from_var_in_scope", "scope_w_component_after_const_var", @@ -453,7 +454,6 @@ "template_unresolved_with_pair_target", "template_unresolved_tag_in_child", "template_prop_no_type", - "template_prop_no_default", "template_w_composite_prop_invalid_assignment", "template_redeclare_prop_as_const", "template_redeclare_prop_as_prop", diff --git a/test/script/src/Error.c b/test/script/src/Error.c index ff2dca566..b918a26f8 100644 --- a/test/script/src/Error.c +++ b/test/script/src/Error.c @@ -1305,30 +1305,6 @@ void Error_template_prop_no_type(void) { ecs_fini(world); } -void Error_template_prop_no_default(void) { - ecs_world_t *world = ecs_init(); - - ECS_COMPONENT(world, Position); - - ecs_struct(world, { - .entity = ecs_id(Position), - .members = { - {"x", ecs_id(ecs_f32_t)}, - {"y", ecs_id(ecs_f32_t)} - } - }); - - const char *expr = - LINE "template Tree {" - LINE " prop height: flecs.meta.f32" - LINE "}"; - - ecs_log_set_level(-4); - test_assert(ecs_script_run(world, NULL, expr) != 0); - - ecs_fini(world); -} - void Error_template_w_composite_prop_invalid_assignment(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index 7c01f7f76..f09480279 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -4615,13 +4615,46 @@ void Eval_const_var_int(void) { } }); + const char *expr = + HEAD "const var_x: 10" + LINE "const var_y: 20" + LINE "" + LINE "e { Position: {$var_x, $var_y} }"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Eval_const_var_int_deprecated_notation(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct(world, { + .entity = ecs_entity(world, {.name = "Position"}), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + const char *expr = HEAD "const var_x = 10" LINE "const var_y = 20" LINE "" LINE "e { Position: {$var_x, $var_y} }"; + ecs_log_set_level(-3); test_assert(ecs_script_run(world, NULL, expr) == 0); + ecs_log_set_level(-1); ecs_entity_t e = ecs_lookup(world, "e"); test_assert(e != 0); @@ -4647,8 +4680,8 @@ void Eval_const_var_float(void) { }); const char *expr = - HEAD "const var_x = 10.5" - LINE "const var_y = 20.5" + HEAD "const var_x: 10.5" + LINE "const var_y: 20.5" LINE "" LINE "e { Position: {$var_x, $var_y} }"; @@ -4683,8 +4716,8 @@ void Eval_const_var_bool(void) { }); const char *expr = - HEAD "const var_x = true" - LINE "const var_y = false" + HEAD "const var_x: true" + LINE "const var_y: false" LINE "" LINE "e { Bools: {$var_x, $var_y} }"; @@ -4714,8 +4747,8 @@ void Eval_const_var_string(void) { }); const char *expr = - HEAD "const var_x = \"10.5\"" - LINE "const var_y = \"20.5\"" + HEAD "const var_x: \"10.5\"" + LINE "const var_y: \"20.5\"" LINE "" LINE "e { Position: {$var_x, $var_y} }"; @@ -4786,15 +4819,15 @@ void Eval_const_var_scoped(void) { }); const char *expr = - HEAD "const var_x = 10" + HEAD "const var_x: 10" LINE "a { Position: {$var_x, $var_x} }" LINE "a {" - LINE " const var_x = 20" - LINE " const var_y = 30" + LINE " const var_x: 20" + LINE " const var_y: 30" LINE " b { Position: {$var_x, $var_y} }" LINE "}" LINE "a {" - LINE " const var_y = 20" + LINE " const var_y: 20" LINE " c { Position: {$var_x, $var_y} }" LINE "}"; @@ -4909,7 +4942,7 @@ void Eval_scope_w_component_after_const_var(void) { const char *expr = HEAD "Foo {" - LINE " const var = 5" + LINE " const var: 5" LINE " Position: {x: 10, y: $var}" LINE "}"; @@ -4940,7 +4973,7 @@ void Eval_component_after_const_add_expr(void) { const char *expr = HEAD "Foo {" - LINE " const var = 5 + 15" + LINE " const var: 5 + 15" LINE " Position: {x: 10, y: $var}" LINE "}"; @@ -4971,7 +5004,7 @@ void Eval_component_after_const_sub_expr(void) { const char *expr = HEAD "Foo {" - LINE " const var = 25 - 5" + LINE " const var: 25 - 5" LINE " Position: {x: 10, y: $var}" LINE "}"; @@ -5002,7 +5035,7 @@ void Eval_component_after_const_mul_expr(void) { const char *expr = HEAD "Foo {" - LINE " const var = 2 * 10" + LINE " const var: 2 * 10" LINE " Position: {x: 10, y: $var}" LINE "}"; @@ -5033,7 +5066,7 @@ void Eval_component_after_const_div_expr(void) { const char *expr = HEAD "Foo {" - LINE " const var = 40 / 2" + LINE " const var: 40 / 2" LINE " Position: {x: 10, y: $var}" LINE "}"; @@ -5066,7 +5099,7 @@ void Eval_component_after_const_paren_expr(void) { const char *expr = LINE "e {" - LINE " const val = (10 + 20)" + LINE " const val: (10 + 20)" LINE " Position: {$val, $val * 2}" LINE "}"; @@ -5609,7 +5642,7 @@ void Eval_assign_const_w_expr(void) { }); const char *expr = - LINE "const var = 5 + 1" + LINE "const var: 5 + 1" LINE "e { Position: {x: $var, y: $var * 2} }"; test_assert(ecs_script_run(world, NULL, expr) == 0); @@ -5700,7 +5733,7 @@ void Eval_assign_var_to_typed_const_w_composite_type(void) { const char *expr = HEAD "const var_pos_a = Position: {10, 20}" - HEAD "const var_pos_b = $var_pos_a" + HEAD "const var_pos_b: $var_pos_a" LINE "a { $var_pos_b }" LINE ""; @@ -6379,7 +6412,7 @@ void Eval_pair_w_rel_var(void) { ECS_TAG(world, Tgt); const char *expr = - LINE "const rel = Rel\n" + LINE "const rel: Rel\n" LINE "ent {\n" LINE " ($rel, Tgt)\n" LINE "}\n" @@ -6401,7 +6434,7 @@ void Eval_pair_w_tgt_var(void) { ECS_TAG(world, Tgt); const char *expr = - LINE "const tgt = Tgt\n" + LINE "const tgt: Tgt\n" LINE "ent {\n" LINE " (Rel, $tgt)\n" LINE "}\n" @@ -6780,7 +6813,7 @@ void Eval_if_true_var(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = true" + HEAD "const v: true" LINE "if $v {" LINE " a{}" LINE "} else {" @@ -6798,7 +6831,7 @@ void Eval_if_false_var(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = false" + HEAD "const v: false" LINE "if $v {" LINE " a{}" LINE "} else {" @@ -6897,7 +6930,7 @@ void Eval_if_lt_const(void) { { const char *expr = - HEAD "const v = 2.0" + HEAD "const v: 2.0" LINE "if $v > 3.0 {" LINE " a{}" LINE "} else {" @@ -6911,7 +6944,7 @@ void Eval_if_lt_const(void) { { const char *expr = - HEAD "const v = 3.0" + HEAD "const v: 3.0" LINE "if $v > 2.0 {" LINE " c{}" LINE "} else {" @@ -6930,7 +6963,7 @@ void Eval_if_else_if(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = 1" + HEAD "const v: 1" LINE "if $v == 1 {" LINE " a{}" LINE "} else if $v == 0 {" @@ -6948,7 +6981,7 @@ void Eval_if_else_if_else(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = 1" + HEAD "const v: 1" LINE "if $v == 1 {" LINE " a{}" LINE "} else if $v == 0 {" @@ -6970,7 +7003,7 @@ void Eval_if_else_if_else_if(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = 1" + HEAD "const v: 1" LINE "if $v == 1 {" LINE " a{}" LINE "} else if $v == 0 {" @@ -6992,7 +7025,7 @@ void Eval_if_else_newline_if(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = 1" + HEAD "const v: 1" LINE "if $v == 1 {" LINE " a{}" LINE "} else" @@ -7011,7 +7044,7 @@ void Eval_if_else_space_newline_if(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const v = 1" + HEAD "const v: 1" LINE "if $v == 1 {" LINE " a{}" LINE "} else " @@ -7577,7 +7610,7 @@ void Eval_assign_component_to_const(void) { ecs_set(world, e, Position, {10, 20}); const char *expr = - HEAD "const pos = e[Position]" + HEAD "const pos: e[Position]" LINE "foo {" LINE " Position: {$pos.y, $pos.x}" LINE "}"; @@ -7610,8 +7643,8 @@ void Eval_assign_component_member_to_const(void) { ecs_set(world, e, Position, {10, 20}); const char *expr = - HEAD "const px = e[Position].x" - LINE "const py = e[Position].y" + HEAD "const px: e[Position].x" + LINE "const py: e[Position].y" LINE "" LINE "foo {" LINE " Position: {$py, $px}" @@ -7730,7 +7763,7 @@ void Eval_const_w_component_expr(void) { ecs_set(world, e, Position, {10, 20}); const char *expr = - HEAD "const pos = e[Position]" + HEAD "const pos: e[Position]" LINE "foo {" LINE " $pos" LINE "}"; @@ -7765,7 +7798,7 @@ void Eval_const_w_component_expr_in_scope(void) { const char *expr = HEAD "parent {" - LINE " const pos = e[Position]" + LINE " const pos: e[Position]" LINE " foo {" LINE " $pos" LINE " }" @@ -7804,7 +7837,7 @@ void Eval_const_w_component_expr_in_module(void) { const char *expr = HEAD "module parent" - LINE "const pos = e[Position]" + LINE "const pos: e[Position]" LINE "foo {" LINE " $pos" LINE "}"; @@ -7845,7 +7878,7 @@ void Eval_const_w_component_in_scope_expr_in_scope(void) { const char *expr = HEAD "parent {" - LINE " const pos = e[Position]" + LINE " const pos: e[Position]" LINE " foo {" LINE " $pos" LINE " }" @@ -7884,7 +7917,7 @@ void Eval_const_w_component_in_scope_expr_in_module(void) { const char *expr = HEAD "module parent" - LINE "const pos = e[Position]" + LINE "const pos: e[Position]" LINE "foo {" LINE " $pos" LINE "}"; @@ -7922,7 +7955,7 @@ void Eval_const_w_component_and_entity_in_scope_expr_in_scope(void) { const char *expr = HEAD "parent {" - LINE " const pos = e[Position]" + LINE " const pos: e[Position]" LINE " foo {" LINE " $pos" LINE " }" @@ -7961,7 +7994,7 @@ void Eval_const_w_component_and_entity_in_scope_expr_in_module(void) { const char *expr = HEAD "module parent" - LINE "const pos = e[Position]" + LINE "const pos: e[Position]" LINE "foo {" LINE " $pos" LINE "}"; @@ -9402,7 +9435,7 @@ void Eval_entity_w_interpolated_name_w_var(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const i = 10" + HEAD "const i: 10" LINE "\"e_{$i + 20}\" { }"; test_assert(ecs_script_run(world, NULL, expr) == 0); @@ -9554,7 +9587,7 @@ void Eval_entity_w_interpolated_name_w_var_in_scope(void) { const char *expr = HEAD "parent {" - LINE " const i = 10" + LINE " const i: 10" LINE " \"e_{$i + 20}\" { }" LINE "}"; @@ -9632,8 +9665,8 @@ void Eval_for_range_vars(void) { }); const char *expr = - HEAD "const x = 0" - LINE "const y = 3" + HEAD "const x: 0" + LINE "const y: 3" LINE "for i in $x..$y {" LINE " \"e_{$i}\" {" LINE " Position: {$i, $i * 2}" @@ -9790,7 +9823,7 @@ void Eval_variable_assign_self(void) { ecs_world_t *world = ecs_init(); const char *expr = - LINE "const v = $v" + LINE "const v: $v" LINE "" ; @@ -10023,7 +10056,7 @@ void Eval_const_assign_empty_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = {}" + HEAD "const x: {}" LINE; ecs_log_set_level(-4); @@ -10036,7 +10069,7 @@ void Eval_const_assign_empty_collection_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = []" + HEAD "const x: []" LINE; ecs_log_set_level(-4); @@ -10049,7 +10082,7 @@ void Eval_const_i32_assign_empty_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = i32: {}" + HEAD "const x: i32: {}" LINE; ecs_log_set_level(-4); @@ -10062,7 +10095,7 @@ void Eval_const_i32_assign_empty_collection_initializer(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = i32: []" + HEAD "const x: i32: []" LINE; ecs_log_set_level(-4); @@ -10274,7 +10307,7 @@ void Eval_const_w_match(void) { ecs_world_t *world = ecs_init(); const char *expr = - HEAD "const x = match $i {" + HEAD "const x: match $i {" LINE " 1: 10" LINE " 2: 20" LINE " 3: 30" diff --git a/test/script/src/ExprAst.c b/test/script/src/ExprAst.c index 59646973e..78e75a7c3 100644 --- a/test/script/src/ExprAst.c +++ b/test/script/src/ExprAst.c @@ -262,8 +262,8 @@ void ExprAst_template_w_foldable_const(void) { HEAD "Foo {}" LINE LINE "template Bar {" - LINE " const a = 10" - LINE " const b = $a + 10" + LINE " const a: 10" + LINE " const b: $a + 10" LINE " Position: {$a, $b}" LINE "}" LINE diff --git a/test/script/src/Template.c b/test/script/src/Template.c index d66d75f47..f5133a6be 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -357,8 +357,8 @@ void Template_template_w_prop_implicit_type(void) { const char *expr = LINE "template Tree {" - LINE " prop width = 10" - LINE " prop height = 20" + LINE " prop width: 10" + LINE " prop height: 20" LINE "}" LINE "" LINE "e { Tree }" @@ -1971,7 +1971,7 @@ void Template_hoist_var(void) { const char *expr = HEAD "using flecs.meta" - LINE "const v = 10" + LINE "const v: 10" LINE "template Tree {" LINE " prop height = f32: 0" LINE " Position: {$v, $height}" @@ -2008,9 +2008,9 @@ void Template_hoist_vars_nested(void) { }); const char *expr = - HEAD "const x = 10" + HEAD "const x: 10" LINE "parent {" - LINE " const y = 20" + LINE " const y: 20" LINE " template Tree {" LINE " Position: {$x, $y}" LINE " }" @@ -2047,10 +2047,10 @@ void Template_hoist_vars_nested_w_masked(void) { }); const char *expr = - HEAD "const x = 10" + HEAD "const x: 10" LINE "parent {" - HEAD " const x = 30" - LINE " const y = 20" + HEAD " const x: 30" + LINE " const y: 20" LINE " template Tree {" LINE " Position: {$x, $y}" LINE " }" @@ -2684,8 +2684,8 @@ void Template_fold_const(void) { const char *expr = HEAD "template Foo {" LINE " prop size = i32: 10" - LINE " const size_h = $size / 2" - LINE " const size_h_2 = $size_h + 2" + LINE " const size_h: $size / 2" + LINE " const size_h_2: $size_h + 2" LINE " Position: {$size_h, $size_h_2}" LINE "}" LINE "" @@ -2914,7 +2914,7 @@ void Template_template_w_for(void) { const char *expr = HEAD "template Foo {" LINE " for i in 0..2 {" - LINE" const t = $i" + LINE" const t: $i" LINE " \"child_$i\" = Position: {$t, $t + 2}" LINE " }" LINE "}" diff --git a/test/script/src/main.c b/test/script/src/main.c index fe0745ff5..3ce74d72c 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -163,6 +163,7 @@ void Eval_const_var_bool(void); void Eval_const_var_string(void); void Eval_const_var_struct(void); void Eval_const_var_scoped(void); +void Eval_const_var_int_deprecated_notation(void); void Eval_assign_component_from_var(void); void Eval_assign_component_from_var_in_scope(void); void Eval_scope_w_component_after_const_var(void); @@ -445,7 +446,6 @@ void Error_template_unresolved_with_pair_relationship(void); void Error_template_unresolved_with_pair_target(void); void Error_template_unresolved_tag_in_child(void); void Error_template_prop_no_type(void); -void Error_template_prop_no_default(void); void Error_template_w_composite_prop_invalid_assignment(void); void Error_template_redeclare_prop_as_const(void); void Error_template_redeclare_prop_as_prop(void); @@ -1559,6 +1559,10 @@ bake_test_case Eval_testcases[] = { "const_var_scoped", Eval_const_var_scoped }, + { + "const_var_int_deprecated_notation", + Eval_const_var_int_deprecated_notation + }, { "assign_component_from_var", Eval_assign_component_from_var @@ -2677,10 +2681,6 @@ bake_test_case Error_testcases[] = { "template_prop_no_type", Error_template_prop_no_type }, - { - "template_prop_no_default", - Error_template_prop_no_default - }, { "template_w_composite_prop_invalid_assignment", Error_template_w_composite_prop_invalid_assignment @@ -4639,7 +4639,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 308, + 309, Eval_testcases }, { @@ -4653,7 +4653,7 @@ static bake_test_suite suites[] = { "Error", NULL, NULL, - 81, + 80, Error_testcases }, { From a16dc084b6af648bbf1761c5d118a25be1fcc645 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 28 Dec 2024 16:08:40 -0800 Subject: [PATCH 28/36] Allow referring to global variables without $ in script --- distr/flecs.c | 42 ++++++++++++++++++----------- src/addons/rest.c | 4 +-- src/addons/script/expr/visit_type.c | 38 +++++++++++++++++--------- test/script/project.json | 1 + test/script/src/Expr.c | 21 +++++++++++++++ test/script/src/main.c | 7 ++++- 6 files changed, 82 insertions(+), 31 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 3ef8ce1e7..ed28a732d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -26255,7 +26255,7 @@ void flecs_rest_capture_log( } #ifdef FLECS_DEBUG - /* In debug mode, log unexpected errors and warnings to the console */ + /* In debug mode, log unexpected errors to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { @@ -26266,7 +26266,7 @@ void flecs_rest_capture_log( } #endif - if (!rest_last_err && level < 0) { + if (!rest_last_err && level <= -3) { rest_last_err = ecs_os_strdup(msg); } } @@ -80529,11 +80529,6 @@ int flecs_expr_binary_visit_type( return -1; } -// else { -// type = ecs_id(ecs_entity_t); -// *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); -// } - static int flecs_expr_constant_identifier_visit_type( ecs_script_t *script, @@ -80594,16 +80589,33 @@ int flecs_expr_identifier_visit_type( ecs_entity_t e = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (e) { - if (!type) { - type = ecs_id(ecs_entity_t); + const EcsScriptConstVar *global = ecs_get( + script->world, e, EcsScriptConstVar); + if (!global) { + if (!type) { + type = ecs_id(ecs_entity_t); + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + result->storage.entity = e; + result->ptr = &result->storage.entity; + node->expr = (ecs_expr_node_t*)result; + node->node.type = type; + } else { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = global->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { + goto error; + } } - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); - result->storage.entity = e; - result->ptr = &result->storage.entity; - node->expr = (ecs_expr_node_t*)result; - node->node.type = type; return 0; } diff --git a/src/addons/rest.c b/src/addons/rest.c index 940a29292..47585808a 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -96,7 +96,7 @@ void flecs_rest_capture_log( } #ifdef FLECS_DEBUG - /* In debug mode, log unexpected errors and warnings to the console */ + /* In debug mode, log unexpected errors to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { @@ -107,7 +107,7 @@ void flecs_rest_capture_log( } #endif - if (!rest_last_err && level < 0) { + if (!rest_last_err && level <= -3) { rest_last_err = ecs_os_strdup(msg); } } diff --git a/src/addons/script/expr/visit_type.c b/src/addons/script/expr/visit_type.c index 22f4e4fba..6515da4f2 100644 --- a/src/addons/script/expr/visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -996,11 +996,6 @@ int flecs_expr_binary_visit_type( return -1; } -// else { -// type = ecs_id(ecs_entity_t); -// *cur = ecs_meta_cursor(script->world, ecs_id(ecs_entity_t), NULL); -// } - static int flecs_expr_constant_identifier_visit_type( ecs_script_t *script, @@ -1061,16 +1056,33 @@ int flecs_expr_identifier_visit_type( ecs_entity_t e = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (e) { - if (!type) { - type = ecs_id(ecs_entity_t); + const EcsScriptConstVar *global = ecs_get( + script->world, e, EcsScriptConstVar); + if (!global) { + if (!type) { + type = ecs_id(ecs_entity_t); + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + result->storage.entity = e; + result->ptr = &result->storage.entity; + node->expr = (ecs_expr_node_t*)result; + node->node.type = type; + } else { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = global->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { + goto error; + } } - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); - result->storage.entity = e; - result->ptr = &result->storage.entity; - node->expr = (ecs_expr_node_t*)result; - node->node.type = type; return 0; } diff --git a/test/script/project.json b/test/script/project.json index 9d922f0b0..b26563839 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -754,6 +754,7 @@ "match_w_any_mismatching_type", "match_i_w_any_f", "identifier_as_var", + "identifier_as_const_var", "expr_w_identifier_as_var", "initializer_w_identifier_as_var" ] diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 841038d47..6d7b940cf 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -7831,6 +7831,27 @@ void Expr_expr_w_identifier_as_var(void) { ecs_fini(world); } +void Expr_identifier_as_const_var(void) { + ecs_world_t *world = ecs_init(); + + int32_t value = 10; + + test_assert(0 != ecs_const_var(world, { + .name = "FOO", + .type = ecs_id(ecs_i32_t), + .value = &value + })); + + ecs_value_t v = {0}; + test_assert(ecs_expr_run(world, "FOO", &v, NULL) != NULL); + test_assert(v.type == ecs_id(ecs_i32_t)); + test_assert(v.ptr != NULL); + test_uint(*(int32_t*)v.ptr, 10); + ecs_value_free(world, v.type, v.ptr); + + ecs_fini(world); +} + void Expr_initializer_w_identifier_as_var(void) { ecs_world_t *world = ecs_init(); diff --git a/test/script/src/main.c b/test/script/src/main.c index 3ce74d72c..0eb3a888c 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -741,6 +741,7 @@ void Expr_match_w_any_first(void); void Expr_match_w_any_mismatching_type(void); void Expr_match_i_w_any_f(void); void Expr_identifier_as_var(void); +void Expr_identifier_as_const_var(void); void Expr_expr_w_identifier_as_var(void); void Expr_initializer_w_identifier_as_var(void); @@ -3852,6 +3853,10 @@ bake_test_case Expr_testcases[] = { "identifier_as_var", Expr_identifier_as_var }, + { + "identifier_as_const_var", + Expr_identifier_as_const_var + }, { "expr_w_identifier_as_var", Expr_expr_w_identifier_as_var @@ -4660,7 +4665,7 @@ static bake_test_suite suites[] = { "Expr", Expr_setup, NULL, - 269, + 270, Expr_testcases, 1, Expr_params From 92d0e13f0c71e4c1db9d2bbf8e221c64a74582c5 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 28 Dec 2024 21:10:57 -0800 Subject: [PATCH 29/36] Allow script code to be provided in body of script HTTP request --- distr/flecs.c | 6 +++++- src/addons/rest.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index ed28a732d..1cc41477c 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -26688,7 +26688,11 @@ bool flecs_rest_script( const char *code = ecs_http_get_param(req, "code"); if (!code) { - flecs_reply_error(reply, "missing data parameter"); + code = req->body; + } + + if (!code) { + flecs_reply_error(reply, "missing code parameter"); return true; } diff --git a/src/addons/rest.c b/src/addons/rest.c index 47585808a..6d4c238c2 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -529,7 +529,11 @@ bool flecs_rest_script( const char *code = ecs_http_get_param(req, "code"); if (!code) { - flecs_reply_error(reply, "missing data parameter"); + code = req->body; + } + + if (!code) { + flecs_reply_error(reply, "missing code parameter"); return true; } From c633f9941be5faa3c09818e21c795ab9d8160eee Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 29 Dec 2024 14:44:27 -0800 Subject: [PATCH 30/36] Detect issue where props are defined before const variables --- distr/flecs.c | 15 ++++++++++++++- src/addons/script/template.c | 11 ++++++++++- src/addons/script/visit_eval.c | 4 ++++ test/script/project.json | 3 ++- test/script/src/Template.c | 27 +++++++++++++++++++++++++++ test/script/src/main.c | 7 ++++++- 6 files changed, 63 insertions(+), 4 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 1cc41477c..dda130bc0 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -59349,6 +59349,15 @@ int flecs_script_template_eval_prop( ecs_script_var_node_t *node) { ecs_script_template_t *template = v->template; + if (ecs_vec_count(&v->vars->vars) > + ecs_vec_count(&template->prop_defaults)) + { + flecs_script_eval_error(v, node, + "const variables declared before prop '%s' (props must come first)", + node->name); + return -1; + } + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, @@ -59362,7 +59371,7 @@ int flecs_script_template_eval_prop( if (node->type) { if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, - "unresolved type '%s' for const variable '%s'", + "unresolved type '%s' for prop '%s'", node->type, node->name); return -1; } @@ -62765,6 +62774,10 @@ int ecs_script_eval( flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v, &priv_desc); + + ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); + ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); + return result; } diff --git a/src/addons/script/template.c b/src/addons/script/template.c index f8d1ab697..b88bc47d0 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -304,6 +304,15 @@ int flecs_script_template_eval_prop( ecs_script_var_node_t *node) { ecs_script_template_t *template = v->template; + if (ecs_vec_count(&v->vars->vars) > + ecs_vec_count(&template->prop_defaults)) + { + flecs_script_eval_error(v, node, + "const variables declared before prop '%s' (props must come first)", + node->name); + return -1; + } + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, @@ -317,7 +326,7 @@ int flecs_script_template_eval_prop( if (node->type) { if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, - "unresolved type '%s' for const variable '%s'", + "unresolved type '%s' for prop '%s'", node->type, node->name); return -1; } diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index b6f47dcc9..b0dd3d2bc 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1477,6 +1477,10 @@ int ecs_script_eval( flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v, &priv_desc); + + ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); + ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); + return result; } diff --git a/test/script/project.json b/test/script/project.json index b26563839..db039b2c7 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -394,7 +394,8 @@ "clear_script_w_template_w_on_remove_observer", "clear_script_w_template_w_on_remove_observer_added_after", "component_w_assign_add", - "component_w_assign_mul" + "component_w_assign_mul", + "prop_after_const" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index f5133a6be..61e847543 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -3229,3 +3229,30 @@ void Template_component_w_assign_mul(void) { ecs_fini(world); } + +void Template_prop_after_const(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + LINE "template Tree {" + LINE " const x = i32: 10" + LINE " prop y = f32: 20" + LINE " Position: {x, y}" + LINE "}" + LINE "Tree e(30)"; + + ecs_log_set_level(-4); + test_assert(ecs_script_run(world, NULL, expr) != 0); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 0eb3a888c..7aa1f734e 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -389,6 +389,7 @@ void Template_clear_script_w_template_w_on_remove_observer(void); void Template_clear_script_w_template_w_on_remove_observer_added_after(void); void Template_component_w_assign_add(void); void Template_component_w_assign_mul(void); +void Template_prop_after_const(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2458,6 +2459,10 @@ bake_test_case Template_testcases[] = { { "component_w_assign_mul", Template_component_w_assign_mul + }, + { + "prop_after_const", + Template_prop_after_const } }; @@ -4651,7 +4656,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 69, + 70, Template_testcases }, { From 5d2489f569c29b5e7609bc322f604ca73c4c9663 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 29 Dec 2024 16:43:04 -0800 Subject: [PATCH 31/36] Increase size of HTTP buffer, throw warning if request exceeds size --- distr/flecs.c | 14 +++++++++++--- src/addons/http.c | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index dda130bc0..1cc70ecbb 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -22486,7 +22486,7 @@ typedef int ecs_http_socket_t; #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (64 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) @@ -23449,13 +23449,15 @@ void http_recv_connection( ecs_http_socket_t sock) { ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + char *recv_buf = ecs_os_malloc(ECS_HTTP_SEND_RECV_BUFFER_SIZE); ecs_http_fragment_t frag = {0}; int32_t retries = 0; + ecs_os_sleep(0, 10 * 1000 * 1000); + do { if ((bytes_read = http_recv( - sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) + sock, recv_buf, ECS_HTTP_SEND_RECV_BUFFER_SIZE, 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { @@ -23500,11 +23502,17 @@ void http_recv_connection( ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); + if (bytes_read == ECS_HTTP_SEND_RECV_BUFFER_SIZE) { + ecs_warn("request exceeded receive buffer size (%d)", + ECS_HTTP_SEND_RECV_BUFFER_SIZE); + } + if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: + ecs_os_free(recv_buf); ecs_strbuf_reset(&frag.buf); } diff --git a/src/addons/http.c b/src/addons/http.c index 7ad0aa783..84276be27 100644 --- a/src/addons/http.c +++ b/src/addons/http.c @@ -92,7 +92,7 @@ typedef int ecs_http_socket_t; #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (64 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) @@ -1055,13 +1055,15 @@ void http_recv_connection( ecs_http_socket_t sock) { ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + char *recv_buf = ecs_os_malloc(ECS_HTTP_SEND_RECV_BUFFER_SIZE); ecs_http_fragment_t frag = {0}; int32_t retries = 0; + ecs_os_sleep(0, 10 * 1000 * 1000); + do { if ((bytes_read = http_recv( - sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) + sock, recv_buf, ECS_HTTP_SEND_RECV_BUFFER_SIZE, 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { @@ -1106,11 +1108,17 @@ void http_recv_connection( ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); + if (bytes_read == ECS_HTTP_SEND_RECV_BUFFER_SIZE) { + ecs_warn("request exceeded receive buffer size (%d)", + ECS_HTTP_SEND_RECV_BUFFER_SIZE); + } + if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: + ecs_os_free(recv_buf); ecs_strbuf_reset(&frag.buf); } From b2a2e5cb6e1e10e99bcb039b40796c1a40711526 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 30 Dec 2024 00:50:56 -0800 Subject: [PATCH 32/36] Update documentation to new variable assignment syntax --- docs/FlecsScript.md | 64 +++++++++++++------------- examples/script/anonymous_entity.flecs | 8 ++-- examples/script/expressions.flecs | 8 ++-- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index 6b35bed6d..bbe8e1afd 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -312,13 +312,13 @@ By default entity hierarchies are created with the `ChildOf` relationship. Other Scripts can contain expressions, which allow for computing values from inputs such as component values, template properties and variables. Here are some examples of valid Flecs script expressions: ```cpp -const x = 10 + 20 * 30 -const x = 10 * (20 + 30) -const x = $var * 10 -const x = pow($var, 2) -const x = e.parent().name() -const x = Position: {10, 20} -const x = Position: {x: 10, y: 20} +const x: 10 + 20 * 30 +const x: 10 * (20 + 30) +const x: $var * 10 +const x: pow($var, 2) +const x: e.parent().name() +const x: Position: {10, 20} +const x: Position: {x: 10, y: 20} ``` The following sections describe the different features of expressions. @@ -385,7 +385,7 @@ while this is an invalid usage of an initializer: ```cpp // Invalid, unknown type for initializer -const x = {10, 20} +const x: {10, 20} ``` When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element: @@ -432,10 +432,10 @@ e { Match expressions can be used to conditionally assign a value. An example: ```cpp -const x = 1 +const: = 1 // y will be assigned with value 10 -const y = match x { +const y: match x { 1: 10 2: 20 3: 30 @@ -445,10 +445,10 @@ const y = match x { The input to a match expression must be matched by one of its cases. If the input is not matched, script execution will fail. Match expressions can include an "any" case, which is selected when none of the other cases match: ```cpp -const x = 4 +const x: 4 // y will be assigned with value 100 -const y = match x { +const y: match x { 1: 10 2: 20 3: 30 @@ -474,19 +474,19 @@ The type of a match expression is derived from the case values. When the case st Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: ```cpp -const x = "The value of PI is $PI" +const x: "The value of PI is $PI" ``` The following example shows how to use an expression: ```cpp -const x = "The circumference of the circle is {2 * $PI * $r}" +const x: "The circumference of the circle is {2 * $PI * $r}" ``` To prevent evaluating expressions in an interpolated string, the `$` and `{` characters can be escaped: ```cpp -const x = "The value of variable \$x is $x" +const x: "The value of variable \$x is $x" ``` ### Types @@ -623,9 +623,9 @@ Here, `Red` is a resolvable identifier, even though the fully qualified identifi Expressions can call functions. Functions in Flecs script can have arguments of any type, and must return a value. The following snippet shows examples of function calls: ```cpp -const x = sqrt(100) -const x = pow(100, 2) -const x = add({10, 20}, {30, 40}) +const x: sqrt(100) +const x: pow(100, 2) +const x: add({10, 20}, {30, 40}) ``` Currently functions can only be defined outside of scripts by the Flecs Script API. Flecs comes with a set of builtin and math functions. Math functions are defined by the script math addon, which must be explicitly enabled by defining `FLECS_SCRIPT_MATH`. @@ -648,8 +648,8 @@ ecs_function(world, { Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: ```cpp -const x = v.length() -const x = v1.add(v2) +const x: v.length() +const x: v1.add(v2) ``` Just like functions, methods can currently only be defined outside of scripts by using the Flecs Script API. @@ -746,7 +746,7 @@ The random number generator can be used like this: ```cpp const rng = flecs.script.math.Rng: {} -const x = $rng.f(1.0) +const x: $rng.f(1.0) ``` To use the math functions, make sure to use a Flecs build compiled with the `FLECS_SCRIPT_MATH` addon (disabled by default) and that the module is imported: @@ -811,9 +811,9 @@ template Tree { const wood_color = Color: {38, 25, 13} const leaves_color = Color: {51, 76, 38} - const canopy_height = 2 - const trunk_height = $height - $canopy_height - const trunk_width = 2 + const canopy_height: 2 + const trunk_height: $height - $canopy_height + const trunk_width: 2 Trunk { Position: {0, ($height / 2), 0} @@ -822,7 +822,7 @@ template Tree { } Canopy { - const canopy_y = $trunk_height + ($canopy_height / 2) + const canopy_y: $trunk_height + ($canopy_height / 2) Position3: {0, $canopy_y, 0} Box: {$canopy_width, $canopy_height} @@ -969,7 +969,7 @@ with Color(38, 25, 13) { Scripts can contain variables, which are useful for often repeated values. Variables are created with the `const` keyword. Example: ```cpp -const pi = 3.1415926 +const pi: 3.1415926 my_entity { Rotation: {angle: pi} @@ -979,8 +979,8 @@ my_entity { Variables can be combined with expressions: ```cpp -const pi = 3.1415926 -const pi_2 = $pi * 2 +const pi: 3.1415926 +const pi_2: $pi * 2 my_entity { Rotation: {angle: pi / 2} @@ -996,8 +996,8 @@ const wood = Color: {38, 25, 13} When the name of a variable clashes with an entity, it can be disambiguated by prefixing the variable name with a `$`: ```cpp -const pi = 3.1415926 -const pi_2 = $pi * 2 +const pi: 3.1415926 +const pi_2: $pi * 2 pi { Rotation: {angle: $pi / 2} @@ -1044,7 +1044,7 @@ grid { To reduce the number of component lookups in a script, the component value can be stored in a variable: ```cpp -const level = Game[Level] +const level: Game[Level] tiles { Grid: { width: $level.width, $level.depth, prefab: Tile } @@ -1073,7 +1073,7 @@ lantern { If statements can be chained with `else if`: ```cpp -const state = 0 +const state: 0 traffic_light { if $state == 0 { diff --git a/examples/script/anonymous_entity.flecs b/examples/script/anonymous_entity.flecs index b34e79ceb..dd4c44ca6 100644 --- a/examples/script/anonymous_entity.flecs +++ b/examples/script/anonymous_entity.flecs @@ -3,12 +3,12 @@ // // To load this file yourself, call ecs_script_run_file("anonymous_entity.flecs"); -// To create an entity without a name, use the _ character -_ { +// To create an entity without a name just open a scope +{ SpaceShip } // Anonymous entities can be used as parents and children -SpaceShip _ { - _ { Engine } +SpaceShip() { + { Engine } } diff --git a/examples/script/expressions.flecs b/examples/script/expressions.flecs index 8bbf0273e..7a0598ed0 100644 --- a/examples/script/expressions.flecs +++ b/examples/script/expressions.flecs @@ -18,13 +18,13 @@ struct Rectangle { // Flecs script files can contain variables that can be referenced later on when // assigning values to components -const width = 5 +const width: 5 // Variables and components can be assigned using expressions. Most arithmetic // and conditional operators are supported. -const height = $width * 2 +const height: width * 2 e { - Position: {0, -($height / 2)} - Rectangle: {$width, $height} + Position: {0, -(height / 2)} + Rectangle: {width, height} } From 8ba771e850ea3a119824dcdcff79057d32b6689b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 30 Dec 2024 13:54:04 -0800 Subject: [PATCH 33/36] Fix warnings --- distr/flecs.c | 85 ++++++++++++++++++++++++--------- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/addons/log.c | 29 +++++++++++ src/addons/script/expr/parser.c | 4 +- src/addons/script/expr/util.c | 48 +++++++++++-------- src/addons/script/template.c | 4 ++ test/script/src/Deserialize.c | 30 ++++++------ test/script/src/Expr.c | 34 +++++++------ 9 files changed, 161 insertions(+), 77 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 1cc70ecbb..962ad8bf8 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -24515,6 +24515,7 @@ void ecs_log_pop_( } } +static void flecs_parser_errorv( const char *name, const char *expr, @@ -24816,6 +24817,34 @@ void ecs_parser_errorv_( (void)args; } + +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; +} + +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} + void ecs_abort_( int32_t error_code, const char *file, @@ -59417,6 +59446,10 @@ int flecs_script_template_eval_prop( ecs_value_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); + if (!node->type) { + ecs_value_free(v->world, type, var->value.ptr); + } + ecs_entity_t mbr = ecs_entity(v->world, { .name = node->name, .parent = template->entity @@ -76553,7 +76586,7 @@ ecs_script_t* ecs_expr_parse( goto error; } - // printf("%s\n", ecs_script_ast_to_str(script, true)); + //printf("%s\n", ecs_script_ast_to_str(script, true)); if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { @@ -76561,7 +76594,7 @@ ecs_script_t* ecs_expr_parse( } } - // printf("%s\n", ecs_script_ast_to_str(script, true)); + //printf("%s\n", ecs_script_ast_to_str(script, true)); return script; error: @@ -76931,37 +76964,40 @@ int flecs_value_unary( #define ECS_BOP_COND(left, right, result, op, R, T)\ ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) +#define ECS_BOP_ASSIGN(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) op (R)(ECS_VALUE_GET(right, T)) + /* Unsigned operations */ #define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ + if ((right)->type == ecs_id(ecs_u64_t)) { \ OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + } else if ((right)->type == ecs_id(ecs_u32_t)) { \ OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ - } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + } else if ((right)->type == ecs_id(ecs_u16_t)) { \ OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ } /* Unsigned + signed operations */ #define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_i64_t)) { \ + else if ((right)->type == ecs_id(ecs_i64_t)) { \ OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + } else if ((right)->type == ecs_id(ecs_i32_t)) { \ OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ - } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + } else if ((right)->type == ecs_id(ecs_i16_t)) { \ OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ - } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + } else if ((right)->type == ecs_id(ecs_i8_t)) { \ OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ } /* Unsigned + signed + floating point operations */ #define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_f64_t)) { \ + else if ((right)->type == ecs_id(ecs_f64_t)) { \ OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + } else if ((right)->type == ecs_id(ecs_f32_t)) { \ OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ } @@ -76985,15 +77021,15 @@ int flecs_value_unary( #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ + else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else if ((left)->type == ecs_id(ecs_entity_t)) { \ + } else if ((right)->type == ecs_id(ecs_entity_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ - } else if ((left)->type == ecs_id(ecs_string_t)) { \ + } else if ((right)->type == ecs_id(ecs_string_t)) { \ char *lstr = *(char**)(left)->ptr;\ char *rstr = *(char**)(right)->ptr;\ if (lstr && rstr) {\ @@ -77007,18 +77043,21 @@ int flecs_value_unary( #define ECS_BINARY_COND_OP(left, right, result, op)\ ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ + else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } +#define ECS_BINARY_ASSIGN_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_ASSIGN)\ + #define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_bool_t)) { \ + if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ @@ -77094,10 +77133,10 @@ int flecs_value_binary( ECS_BINARY_INT_OP(left, right, out, >>); break; case EcsTokAddAssign: - ECS_BINARY_OP(out, right, out, +=); + ECS_BINARY_ASSIGN_OP(left, right, out, +=); break; case EcsTokMulAssign: - ECS_BINARY_OP(out, right, out, *=); + ECS_BINARY_ASSIGN_OP(left, right, out, *=); break; case EcsTokEnd: case EcsTokUnknown: diff --git a/distr/flecs.h b/distr/flecs.h index 585a353e5..446b0fb28 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -255,7 +255,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index 18da32b8a..2a8d14b53 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -253,7 +253,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -// #define FLECS_USE_OS_ALLOC +#define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/addons/log.c b/src/addons/log.c index 758aedac2..fa281341a 100644 --- a/src/addons/log.c +++ b/src/addons/log.c @@ -226,6 +226,7 @@ void ecs_log_pop_( } } +static void flecs_parser_errorv( const char *name, const char *expr, @@ -527,6 +528,34 @@ void ecs_parser_errorv_( (void)args; } + +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; +} + +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} + void ecs_abort_( int32_t error_code, const char *file, diff --git a/src/addons/script/expr/parser.c b/src/addons/script/expr/parser.c index f4278e2e3..c617bc29c 100644 --- a/src/addons/script/expr/parser.c +++ b/src/addons/script/expr/parser.c @@ -655,7 +655,7 @@ ecs_script_t* ecs_expr_parse( goto error; } - // printf("%s\n", ecs_script_ast_to_str(script, true)); + //printf("%s\n", ecs_script_ast_to_str(script, true)); if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { @@ -663,7 +663,7 @@ ecs_script_t* ecs_expr_parse( } } - // printf("%s\n", ecs_script_ast_to_str(script, true)); + //printf("%s\n", ecs_script_ast_to_str(script, true)); return script; error: diff --git a/src/addons/script/expr/util.c b/src/addons/script/expr/util.c index bc4354be0..44df5ae30 100644 --- a/src/addons/script/expr/util.c +++ b/src/addons/script/expr/util.c @@ -144,37 +144,40 @@ int flecs_value_unary( #define ECS_BOP_COND(left, right, result, op, R, T)\ ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) +#define ECS_BOP_ASSIGN(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) op (R)(ECS_VALUE_GET(right, T)) + /* Unsigned operations */ #define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - if ((left)->type == ecs_id(ecs_u64_t)) { \ + if ((right)->type == ecs_id(ecs_u64_t)) { \ OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if ((left)->type == ecs_id(ecs_u32_t)) { \ + } else if ((right)->type == ecs_id(ecs_u32_t)) { \ OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ - } else if ((left)->type == ecs_id(ecs_u16_t)) { \ + } else if ((right)->type == ecs_id(ecs_u16_t)) { \ OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ } /* Unsigned + signed operations */ #define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_i64_t)) { \ + else if ((right)->type == ecs_id(ecs_i64_t)) { \ OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if ((left)->type == ecs_id(ecs_i32_t)) { \ + } else if ((right)->type == ecs_id(ecs_i32_t)) { \ OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ - } else if ((left)->type == ecs_id(ecs_i16_t)) { \ + } else if ((right)->type == ecs_id(ecs_i16_t)) { \ OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ - } else if ((left)->type == ecs_id(ecs_i8_t)) { \ + } else if ((right)->type == ecs_id(ecs_i8_t)) { \ OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ } /* Unsigned + signed + floating point operations */ #define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ ECS_BINARY_INT_OPS(left, right, result, op, OP)\ - else if ((left)->type == ecs_id(ecs_f64_t)) { \ + else if ((right)->type == ecs_id(ecs_f64_t)) { \ OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else if ((left)->type == ecs_id(ecs_f32_t)) { \ + } else if ((right)->type == ecs_id(ecs_f32_t)) { \ OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ } @@ -198,15 +201,15 @@ int flecs_value_unary( #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ + else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else if ((left)->type == ecs_id(ecs_entity_t)) { \ + } else if ((right)->type == ecs_id(ecs_entity_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ - } else if ((left)->type == ecs_id(ecs_string_t)) { \ + } else if ((right)->type == ecs_id(ecs_string_t)) { \ char *lstr = *(char**)(left)->ptr;\ char *rstr = *(char**)(right)->ptr;\ if (lstr && rstr) {\ @@ -220,18 +223,21 @@ int flecs_value_unary( #define ECS_BINARY_COND_OP(left, right, result, op)\ ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ - else if ((left)->type == ecs_id(ecs_char_t)) { \ + else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if ((left)->type == ecs_id(ecs_u8_t)) { \ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if ((left)->type == ecs_id(ecs_bool_t)) { \ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } +#define ECS_BINARY_ASSIGN_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_ASSIGN)\ + #define ECS_BINARY_BOOL_OP(left, right, result, op)\ - if ((left)->type == ecs_id(ecs_bool_t)) { \ + if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ @@ -307,10 +313,10 @@ int flecs_value_binary( ECS_BINARY_INT_OP(left, right, out, >>); break; case EcsTokAddAssign: - ECS_BINARY_OP(out, right, out, +=); + ECS_BINARY_ASSIGN_OP(left, right, out, +=); break; case EcsTokMulAssign: - ECS_BINARY_OP(out, right, out, *=); + ECS_BINARY_ASSIGN_OP(left, right, out, *=); break; case EcsTokEnd: case EcsTokUnknown: diff --git a/src/addons/script/template.c b/src/addons/script/template.c index b88bc47d0..f2fd2e94b 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -364,6 +364,10 @@ int flecs_script_template_eval_prop( ecs_value_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); + if (!node->type) { + ecs_value_free(v->world, type, var->value.ptr); + } + ecs_entity_t mbr = ecs_entity(v->world, { .name = node->name, .parent = template->entity diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 02f1b2aa8..3391a9239 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -569,10 +569,10 @@ void Deserialize_enum(void) { } void Deserialize_bitmask(void) { - uint32_t Lettuce = 0x1; - uint32_t Bacon = 0x1 << 1; - uint32_t Tomato = 0x1 << 2; - uint32_t Cheese = 0x1 << 3; + uint64_t Lettuce = 0x1; + uint64_t Bacon = 0x1 << 1; + uint64_t Tomato = 0x1 << 2; + uint64_t Cheese = 0x1 << 3; ecs_world_t *world = ecs_init(); @@ -584,7 +584,7 @@ void Deserialize_bitmask(void) { }); { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "Lettuce", @@ -594,7 +594,7 @@ void Deserialize_bitmask(void) { } { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "Lettuce|Bacon", @@ -604,7 +604,7 @@ void Deserialize_bitmask(void) { } { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "Lettuce|Bacon|Tomato|Cheese", @@ -614,7 +614,7 @@ void Deserialize_bitmask(void) { } { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "Lettuce | Bacon | Tomato | Cheese", @@ -624,7 +624,7 @@ void Deserialize_bitmask(void) { } { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "BLT", @@ -634,7 +634,7 @@ void Deserialize_bitmask(void) { } { - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "0", @@ -645,7 +645,7 @@ void Deserialize_bitmask(void) { { ecs_log_set_level(-4); - uint32_t value = 0; + uint64_t value = 0; ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; const char *ptr = ecs_expr_run(world, "Foo", @@ -728,10 +728,10 @@ void Deserialize_struct_enum(void) { } void Deserialize_struct_bitmask(void) { - uint32_t Lettuce = 0x1; - uint32_t Bacon = 0x1 << 1; - uint32_t Tomato = 0x1 << 2; - uint32_t Cheese = 0x1 << 3; + uint64_t Lettuce = 0x1; + uint64_t Bacon = 0x1 << 1; + uint64_t Tomato = 0x1 << 2; + uint64_t Cheese = 0x1 << 3; typedef struct { ecs_u32_t v; diff --git a/test/script/src/Expr.c b/test/script/src/Expr.c index 6d7b940cf..8cd40efa6 100644 --- a/test/script/src/Expr.c +++ b/test/script/src/Expr.c @@ -2234,6 +2234,8 @@ void Expr_cond_eq_enum(void) { test_bool(*(bool*)v.ptr, true); } + ecs_value_free(world, v.type, v.ptr); + ecs_script_vars_fini(vars); ecs_fini(world); @@ -2253,6 +2255,7 @@ void Expr_cond_eq_string(void) { test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } @@ -2276,6 +2279,7 @@ void Expr_cond_eq_entity(void) { test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, false); + ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } @@ -2470,6 +2474,7 @@ void Expr_cond_neq_enum(void) { } ecs_script_vars_fini(vars); + ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } @@ -2488,6 +2493,7 @@ void Expr_cond_neq_string(void) { test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } @@ -2511,6 +2517,7 @@ void Expr_cond_neq_entity(void) { test_assert(v.type == ecs_id(ecs_bool_t)); test_assert(v.ptr != NULL); test_bool(*(bool*)v.ptr, true); + ecs_value_free(world, v.type, v.ptr); ecs_fini(world); } @@ -7075,7 +7082,7 @@ void Expr_match_i32_1_collection_case(void) { { *(int32_t*)var->value.ptr = 0; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; ecs_log_set_level(-4); test_assert(0 != ecs_expr_eval(s, &result, &desc)); @@ -7084,7 +7091,7 @@ void Expr_match_i32_1_collection_case(void) { { *(int32_t*)var->value.ptr = 1; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7125,7 +7132,7 @@ void Expr_match_i32_2_collection_cases(void) { { *(int32_t*)var->value.ptr = 0; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; ecs_log_set_level(-4); test_assert(0 != ecs_expr_eval(s, &result, &desc)); @@ -7134,7 +7141,7 @@ void Expr_match_i32_2_collection_cases(void) { { *(int32_t*)var->value.ptr = 1; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7144,7 +7151,7 @@ void Expr_match_i32_2_collection_cases(void) { { *(int32_t*)var->value.ptr = 2; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7186,7 +7193,7 @@ void Expr_match_i32_3_collection_cases(void) { { *(int32_t*)var->value.ptr = 0; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; ecs_log_set_level(-4); test_assert(0 != ecs_expr_eval(s, &result, &desc)); @@ -7195,7 +7202,7 @@ void Expr_match_i32_3_collection_cases(void) { { *(int32_t*)var->value.ptr = 1; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7205,7 +7212,7 @@ void Expr_match_i32_3_collection_cases(void) { { *(int32_t*)var->value.ptr = 2; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7215,7 +7222,7 @@ void Expr_match_i32_3_collection_cases(void) { { *(int32_t*)var->value.ptr = 3; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7258,7 +7265,7 @@ void Expr_match_i32_empty_collection_case(void) { { *(int32_t*)var->value.ptr = 0; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; ecs_log_set_level(-4); test_assert(0 != ecs_expr_eval(s, &result, &desc)); @@ -7267,7 +7274,7 @@ void Expr_match_i32_empty_collection_case(void) { { *(int32_t*)var->value.ptr = 1; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7277,7 +7284,7 @@ void Expr_match_i32_empty_collection_case(void) { { *(int32_t*)var->value.ptr = 2; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7287,7 +7294,7 @@ void Expr_match_i32_empty_collection_case(void) { { *(int32_t*)var->value.ptr = 3; - Ints p = {}; + Ints p = {0}; ecs_value_t result = { .type = ecs_id(Ints), .ptr = &p }; test_assert(0 == ecs_expr_eval(s, &result, &desc)); test_assert(result.type == ecs_id(Ints)); @@ -7882,7 +7889,6 @@ void Expr_initializer_w_identifier_as_var(void) { test_assert(v.ptr != NULL); test_int(p.x, 10); test_int(p.y, 20); - ecs_value_free(world, v.type, v.ptr); ecs_script_vars_fini(vars); From fdd8231de3d9ad94ac337b17ebf744f8306c3c8b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 2 Jan 2025 15:59:12 -0800 Subject: [PATCH 34/36] Add body parameter to ecs_http_server_request --- distr/flecs.c | 60 ++++++++++++++----- distr/flecs.h | 1 + include/flecs/addons/http.h | 1 + src/addons/app.c | 5 +- src/addons/http.c | 42 ++++++++++--- src/addons/rest.c | 9 ++- src/addons/script/template.c | 4 -- test/addons/project.json | 2 + test/addons/src/Rest.c | 110 ++++++++++++++++++++++++++--------- test/addons/src/main.c | 12 +++- test/script/project.json | 3 +- test/script/src/Template.c | 25 ++++++++ test/script/src/main.c | 7 ++- 13 files changed, 219 insertions(+), 62 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 962ad8bf8..e2a44e9c2 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -21539,12 +21539,13 @@ static ecs_app_desc_t ecs_app_desc; ecs_http_server_t *flecs_wasm_rest_server = NULL; EMSCRIPTEN_KEEPALIVE -char* flecs_explorer_request(const char *method, char *request) { +char* flecs_explorer_request(const char *method, char *request, char *body) { ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, "wasm REST server is not initialized yet"); ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); + ecs_http_server_request( + flecs_wasm_rest_server, method, request, body, &reply); if (reply.code == 200) { return ecs_strbuf_get(&reply.body); } else { @@ -23206,7 +23207,7 @@ bool http_parse_request( frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; - } + } break; case HttpFragStateCRLF: if (c == '\r') { @@ -23739,7 +23740,6 @@ void http_do_request( if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { - reply->code = 404; reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { @@ -24119,25 +24119,52 @@ int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, + const char *body, ecs_http_reply_t *reply_out) { - const char *http_ver = " HTTP/1.1\r\n\r\n"; + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(method != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(req != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(reply_out != NULL, ECS_INVALID_PARAMETER, NULL); + + const char *http_ver = " HTTP/1.1\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); + int32_t body_len = body ? ecs_os_strlen(body) : 0; int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; + char content_length[32] = {0}; - int32_t len = method_len + req_len + http_ver_len + 1; - if (method_len + req_len + http_ver_len >= 1024) { - reqstr = ecs_os_malloc(len + 1); + if (body_len) { + ecs_os_snprintf(content_length, 32, + "Content-Length: %d\r\n\r\n", body_len); } + int32_t content_length_len = ecs_os_strlen(content_length); + + int32_t len = method_len + req_len + content_length_len + body_len + + http_ver_len; + if (len >= 1024) { + reqstr = ecs_os_malloc(len); + } + + len += 3; + char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; - ptr[0] = '\n'; + + if (body) { + ecs_os_memcpy(ptr, content_length, content_length_len); + ptr += content_length_len; + ecs_os_memcpy(ptr, body, body_len); + ptr += body_len; + } + + ptr[0] = '\r'; + ptr[1] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { @@ -24145,6 +24172,8 @@ int ecs_http_server_request( } return result; +error: + return -1; } void* ecs_http_server_ctx( @@ -26727,15 +26756,18 @@ bool flecs_rest_script( if (!code) { code = req->body; } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); if (!code) { flecs_reply_error(reply, "missing code parameter"); + if (!try) { + reply->code = 400; + } return true; } - bool try = false; - flecs_rest_bool_param(req, "try", &try); - bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; flecs_set_prev_log(ecs_os_api.log_, try); @@ -59446,10 +59478,6 @@ int flecs_script_template_eval_prop( ecs_value_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); - if (!node->type) { - ecs_value_free(v->world, type, var->value.ptr); - } - ecs_entity_t mbr = ecs_entity(v->world, { .name = node->name, .parent = template->entity diff --git a/distr/flecs.h b/distr/flecs.h index 446b0fb28..ca831e155 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -11493,6 +11493,7 @@ int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, + const char *body, ecs_http_reply_t *reply_out); /** Get context provided in ecs_http_server_desc_t */ diff --git a/include/flecs/addons/http.h b/include/flecs/addons/http.h index b45f3adfc..2d05d54a5 100644 --- a/include/flecs/addons/http.h +++ b/include/flecs/addons/http.h @@ -202,6 +202,7 @@ int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, + const char *body, ecs_http_reply_t *reply_out); /** Get context provided in ecs_http_server_desc_t */ diff --git a/src/addons/app.c b/src/addons/app.c index e25e8aec5..40cfd06cb 100644 --- a/src/addons/app.c +++ b/src/addons/app.c @@ -60,12 +60,13 @@ static ecs_app_desc_t ecs_app_desc; ecs_http_server_t *flecs_wasm_rest_server = NULL; EMSCRIPTEN_KEEPALIVE -char* flecs_explorer_request(const char *method, char *request) { +char* flecs_explorer_request(const char *method, char *request, char *body) { ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, "wasm REST server is not initialized yet"); ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); + ecs_http_server_request( + flecs_wasm_rest_server, method, request, body, &reply); if (reply.code == 200) { return ecs_strbuf_get(&reply.body); } else { diff --git a/src/addons/http.c b/src/addons/http.c index 84276be27..5c9176ade 100644 --- a/src/addons/http.c +++ b/src/addons/http.c @@ -812,7 +812,7 @@ bool http_parse_request( frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; - } + } break; case HttpFragStateCRLF: if (c == '\r') { @@ -1345,7 +1345,6 @@ void http_do_request( if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { - reply->code = 404; reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { @@ -1725,25 +1724,52 @@ int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, + const char *body, ecs_http_reply_t *reply_out) { - const char *http_ver = " HTTP/1.1\r\n\r\n"; + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(method != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(req != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(reply_out != NULL, ECS_INVALID_PARAMETER, NULL); + + const char *http_ver = " HTTP/1.1\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); + int32_t body_len = body ? ecs_os_strlen(body) : 0; int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; + char content_length[32] = {0}; + + if (body_len) { + ecs_os_snprintf(content_length, 32, + "Content-Length: %d\r\n\r\n", body_len); + } + + int32_t content_length_len = ecs_os_strlen(content_length); - int32_t len = method_len + req_len + http_ver_len + 1; - if (method_len + req_len + http_ver_len >= 1024) { - reqstr = ecs_os_malloc(len + 1); + int32_t len = method_len + req_len + content_length_len + body_len + + http_ver_len; + if (len >= 1024) { + reqstr = ecs_os_malloc(len); } + len += 3; + char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; - ptr[0] = '\n'; + + if (body) { + ecs_os_memcpy(ptr, content_length, content_length_len); + ptr += content_length_len; + ecs_os_memcpy(ptr, body, body_len); + ptr += body_len; + } + + ptr[0] = '\r'; + ptr[1] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { @@ -1751,6 +1777,8 @@ int ecs_http_server_request( } return result; +error: + return -1; } void* ecs_http_server_ctx( diff --git a/src/addons/rest.c b/src/addons/rest.c index 6d4c238c2..7a731adfe 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -531,15 +531,18 @@ bool flecs_rest_script( if (!code) { code = req->body; } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); if (!code) { flecs_reply_error(reply, "missing code parameter"); + if (!try) { + reply->code = 400; + } return true; } - bool try = false; - flecs_rest_bool_param(req, "try", &try); - bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; flecs_set_prev_log(ecs_os_api.log_, try); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index f2fd2e94b..b88bc47d0 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -364,10 +364,6 @@ int flecs_script_template_eval_prop( ecs_value_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); - if (!node->type) { - ecs_value_free(v->world, type, var->value.ptr); - } - ecs_entity_t mbr = ecs_entity(v->world, { .name = node->name, .parent = template->entity diff --git a/test/addons/project.json b/test/addons/project.json index 6aa90ce4e..14272976b 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -505,6 +505,8 @@ "request_commands_no_commands", "request_commands_garbage_collect", "script_error", + "script_update", + "script_update_w_body", "import_rest_after_mini", "get_pipeline_stats_after_delete_system", "request_world_summary_before_monitor_sys_run", diff --git a/test/addons/src/Rest.c b/test/addons/src/Rest.c index b527316b6..bb8b27243 100644 --- a/test/addons/src/Rest.c +++ b/test/addons/src/Rest.c @@ -18,7 +18,7 @@ void Rest_get(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/World?label=true", &reply)); + "/entity/flecs/core/World?label=true", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -45,7 +45,7 @@ void Rest_get_cached(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/World", &reply)); + "/entity/flecs/core/World", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -59,7 +59,7 @@ void Rest_get_cached(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/World", &reply)); + "/entity/flecs/core/World", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -87,7 +87,7 @@ void Rest_get_cached_invalid(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(-1, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/Wor", &reply)); + "/entity/flecs/core/Wor", NULL, &reply)); test_int(reply.code, 404); char *reply_str = ecs_strbuf_get(&reply.body); @@ -100,7 +100,7 @@ void Rest_get_cached_invalid(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(-1, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/Wor", &reply)); + "/entity/flecs/core/Wor", NULL, &reply)); test_int(reply.code, 404); char *reply_str = ecs_strbuf_get(&reply.body); @@ -126,7 +126,7 @@ void Rest_try_query(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(-1, ecs_http_server_request(srv, "GET", - "/query?expr=Foo", &reply)); + "/query?expr=Foo", NULL, &reply)); test_int(reply.code, 400); // No try, should error ecs_strbuf_reset(&reply.body); } @@ -134,7 +134,7 @@ void Rest_try_query(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/query?expr=Foo&try=true", &reply)); + "/query?expr=Foo&try=true", NULL, &reply)); test_int(reply.code, 200); // With try, should not error ecs_strbuf_reset(&reply.body); } @@ -157,7 +157,7 @@ void Rest_query(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/query?expr=Position", &reply)); + "/query?expr=Position", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -191,7 +191,7 @@ void Rest_named_query(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/query?name=position_query", &reply)); + "/query?name=position_query", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -213,7 +213,7 @@ void Rest_tables(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/tables", &reply)); + "/tables", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -236,7 +236,7 @@ void Rest_request_commands(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/capture", &reply)); + "/commands/capture", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -258,7 +258,7 @@ void Rest_request_commands(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -282,7 +282,7 @@ void Rest_request_commands_2_syncs(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/capture", &reply)); + "/commands/capture", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -306,7 +306,7 @@ void Rest_request_commands_2_syncs(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -330,7 +330,7 @@ void Rest_request_commands_no_frames(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(-1, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 404); char *reply_str = ecs_strbuf_get(&reply.body); @@ -354,7 +354,7 @@ void Rest_request_commands_no_commands(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/capture", &reply)); + "/commands/capture", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -371,7 +371,7 @@ void Rest_request_commands_no_commands(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -395,7 +395,7 @@ void Rest_request_commands_garbage_collect(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/capture", &reply)); + "/commands/capture", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -418,7 +418,7 @@ void Rest_request_commands_garbage_collect(void) { for (int i = 0; i < 60 * 60; i ++) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -432,7 +432,7 @@ void Rest_request_commands_garbage_collect(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/capture", &reply)); + "/commands/capture", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); test_assert(reply_str != NULL); @@ -444,7 +444,7 @@ void Rest_request_commands_garbage_collect(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(-1, ecs_http_server_request(srv, "GET", - "/commands/frame/0", &reply)); + "/commands/frame/0", NULL, &reply)); test_int(reply.code, 404); char *reply_str = ecs_strbuf_get(&reply.body); test_assert(reply_str != NULL); @@ -455,7 +455,7 @@ void Rest_request_commands_garbage_collect(void) { { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/commands/frame/3601", &reply)); + "/commands/frame/3601", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); test_assert(reply_str != NULL); @@ -473,7 +473,7 @@ void Rest_script_error(void) { ECS_COMPONENT(world, Position); ecs_script(world, { - .entity = ecs_entity(world, { .name = "main.flecs" }), + .entity = ecs_entity(world, { .name = "main.flecs", .sep = "/" }), .code = "" }); @@ -485,7 +485,7 @@ void Rest_script_error(void) { ecs_log_set_level(-4); test_int(-1, ecs_http_server_request(srv, "PUT", "/script/main.flecs?code=struct%20Position%20%7B%0A%20%20x%20%3A%0A%7D", - &reply)); + NULL, &reply)); test_int(reply.code, 400); char *reply_str = ecs_strbuf_get(&reply.body); test_assert(reply_str != NULL); @@ -497,6 +497,62 @@ void Rest_script_error(void) { ecs_fini(world); } +void Rest_script_update(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_script(world, { + .entity = ecs_entity(world, { .name = "main.flecs", .sep = "/" }), + .code = "" + }); + + ecs_http_server_t *srv = ecs_rest_server_init(world, NULL); + test_assert(srv != NULL); + + { + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + test_int(0, ecs_http_server_request(srv, "PUT", + "/script/main.flecs?code=e%20%7B%7D", + NULL, &reply)); + test_int(reply.code, 200); + char *reply_str = ecs_strbuf_get(&reply.body); + test_assert(reply_str == NULL); + test_assert(ecs_lookup(world, "e") != 0); + } + + ecs_rest_server_fini(srv); + + ecs_fini(world); +} + +void Rest_script_update_w_body(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_script(world, { + .entity = ecs_entity(world, { .name = "main.flecs", .sep = "/" }), + .code = "" + }); + + ecs_http_server_t *srv = ecs_rest_server_init(world, NULL); + test_assert(srv != NULL); + + { + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + test_int(0, ecs_http_server_request(srv, "PUT", + "/script/main.flecs", "e {}", &reply)); + test_int(reply.code, 200); + char *reply_str = ecs_strbuf_get(&reply.body); + test_assert(reply_str == NULL); + } + + ecs_rest_server_fini(srv); + + ecs_fini(world); +} + void Rest_import_rest_after_mini(void) { ecs_world_t *world = ecs_mini(); @@ -531,7 +587,7 @@ void Rest_get_pipeline_stats_after_delete_system(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/stats/pipeline?name=all&period=1m", &reply)); + "/stats/pipeline?name=all&period=1m", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -553,7 +609,7 @@ void Rest_request_world_summary_before_monitor_sys_run(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/entity/flecs/core/World?values=true", &reply)); + "/entity/flecs/core/World?values=NULL, true", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); @@ -577,7 +633,7 @@ void Rest_escape_backslash(void) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; test_int(0, ecs_http_server_request(srv, "GET", - "/entity/foo%5C%2Fbar", &reply)); + "/entity/foo%5C%2Fbar", NULL, &reply)); test_int(reply.code, 200); char *reply_str = ecs_strbuf_get(&reply.body); diff --git a/test/addons/src/main.c b/test/addons/src/main.c index e8cd20ec5..9bcd7bb23 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -457,6 +457,8 @@ void Rest_request_commands_no_frames(void); void Rest_request_commands_no_commands(void); void Rest_request_commands_garbage_collect(void); void Rest_script_error(void); +void Rest_script_update(void); +void Rest_script_update_w_body(void); void Rest_import_rest_after_mini(void); void Rest_get_pipeline_stats_after_delete_system(void); void Rest_request_world_summary_before_monitor_sys_run(void); @@ -2213,6 +2215,14 @@ bake_test_case Rest_testcases[] = { "script_error", Rest_script_error }, + { + "script_update", + Rest_script_update + }, + { + "script_update_w_body", + Rest_script_update_w_body + }, { "import_rest_after_mini", Rest_import_rest_after_mini @@ -2680,7 +2690,7 @@ static bake_test_suite suites[] = { "Rest", NULL, NULL, - 18, + 20, Rest_testcases }, { diff --git a/test/script/project.json b/test/script/project.json index db039b2c7..89ae21daa 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -395,7 +395,8 @@ "clear_script_w_template_w_on_remove_observer_added_after", "component_w_assign_add", "component_w_assign_mul", - "prop_after_const" + "prop_after_const", + "const_from_prop" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index 61e847543..f3d4317a1 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -3256,3 +3256,28 @@ void Template_prop_after_const(void) { ecs_fini(world); } + +void Template_const_from_prop(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const char *expr = + LINE "template Tree {" + LINE " prop x: 10" + LINE " const y: x" + LINE " i32: {y}" + LINE "}" + LINE "Tree e(30)"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + const int32_t *ptr = ecs_get(world, e, ecs_i32_t); + test_assert(ptr != NULL); + test_int(*ptr, 30); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 7aa1f734e..d44a8aeee 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -390,6 +390,7 @@ void Template_clear_script_w_template_w_on_remove_observer_added_after(void); void Template_component_w_assign_add(void); void Template_component_w_assign_mul(void); void Template_prop_after_const(void); +void Template_const_from_prop(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2463,6 +2464,10 @@ bake_test_case Template_testcases[] = { { "prop_after_const", Template_prop_after_const + }, + { + "const_from_prop", + Template_const_from_prop } }; @@ -4656,7 +4661,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 70, + 71, Template_testcases }, { From fbf6ed297707d61c2533843961c652c8a93e71af Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 2 Jan 2025 17:50:49 -0800 Subject: [PATCH 35/36] #1488 Fix issue with parsing opaque primitive components --- distr/flecs.c | 38 ++++++++++++-- distr/flecs.h | 2 +- include/flecs.h | 2 +- src/addons/meta/cursor.c | 12 ++++- src/addons/script/template.c | 26 ++++++++-- test/script/project.json | 7 ++- test/script/src/Deserialize.c | 24 +++++++++ test/script/src/Eval.c | 95 +++++++++++++++++++++++++++++++++++ test/script/src/main.c | 19 ++++++- 9 files changed, 209 insertions(+), 16 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index e2a44e9c2..7c78d4103 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -48477,8 +48477,18 @@ int ecs_meta_push( if (cursor->depth == 0) { if (!cursor->is_primitive_scope) { + bool is_primitive = false; if ((op->kind > EcsOpScope) && (op->count <= 1)) { - cursor->is_primitive_scope = true; + is_primitive = true; + } else if (op->kind == EcsOpOpaque) { + const EcsOpaque *t = ecs_get(world, op->type, EcsOpaque); + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_has(world, t->as_type, EcsPrimitive)) { + is_primitive = true; + } + } + + if ((cursor->is_primitive_scope = is_primitive)) { return 0; } } @@ -59459,14 +59469,32 @@ int flecs_script_template_eval_prop( return -1; } } else { - if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, &node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); return -1; } - type = var->value.type; - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ti = ecs_get_type_info(v->world, type); + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); + type = var->value.type = value.type; + var->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); } ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, diff --git a/distr/flecs.h b/distr/flecs.h index ca831e155..ac32715b8 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -255,7 +255,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/include/flecs.h b/include/flecs.h index 2a8d14b53..18da32b8a 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -253,7 +253,7 @@ * When enabled, Flecs will use the OS allocator provided in the OS API directly * instead of the builtin block allocator. This can decrease memory utilization * as memory will be freed more often, at the cost of decreased performance. */ -#define FLECS_USE_OS_ALLOC +// #define FLECS_USE_OS_ALLOC /** @def FLECS_ID_DESC_MAX * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 069300f51..cfc294c0c 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -391,8 +391,18 @@ int ecs_meta_push( if (cursor->depth == 0) { if (!cursor->is_primitive_scope) { + bool is_primitive = false; if ((op->kind > EcsOpScope) && (op->count <= 1)) { - cursor->is_primitive_scope = true; + is_primitive = true; + } else if (op->kind == EcsOpOpaque) { + const EcsOpaque *t = ecs_get(world, op->type, EcsOpaque); + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_has(world, t->as_type, EcsPrimitive)) { + is_primitive = true; + } + } + + if ((cursor->is_primitive_scope = is_primitive)) { return 0; } } diff --git a/src/addons/script/template.c b/src/addons/script/template.c index b88bc47d0..48dd0b7ff 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -345,14 +345,32 @@ int flecs_script_template_eval_prop( return -1; } } else { - if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, &node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); return -1; } - type = var->value.type; - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ti = ecs_get_type_info(v->world, type); + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); + type = var->value.type = value.type; + var->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); } ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, diff --git a/test/script/project.json b/test/script/project.json index 89ae21daa..f70014b2d 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -321,7 +321,9 @@ "component_assign_w_match", "const_w_match", "component_w_assign_add", - "component_w_assign_mul" + "component_w_assign_mul", + "opaque_struct_component", + "opaque_string_component" ] }, { "id": "Template", @@ -951,7 +953,8 @@ "opaque_struct", "opaque_struct_w_member", "opaque_struct_w_member_reverse", - "struct_w_opaque_member" + "struct_w_opaque_member", + "opaque_string" ] }, { "id": "Fuzzing", diff --git a/test/script/src/Deserialize.c b/test/script/src/Deserialize.c index 3391a9239..50d569b2a 100644 --- a/test/script/src/Deserialize.c +++ b/test/script/src/Deserialize.c @@ -2778,3 +2778,27 @@ void Deserialize_struct_w_opaque_member(void) { ecs_fini(world); } + +void Deserialize_opaque_string(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Opaque_string); + + ecs_opaque(world, { + .entity = ecs_id(Opaque_string), + .type.as_type = ecs_id(ecs_string_t), + .type.assign_string = Opaque_string_set + }); + + Opaque_string v = {0, NULL}; + ecs_expr_eval_desc_t desc = { .disable_folding = disable_folding }; + const char *ptr = ecs_expr_run(world, + "\"foobar\"", &ecs_value_ptr(Opaque_string, &v), &desc); + test_assert(ptr != NULL); + test_assert(ptr[0] == '\0'); + + test_str(v.value, "foobar"); + ecs_os_free(v.value); + + ecs_fini(world); +} diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index f09480279..8bf7a956a 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -10434,3 +10434,98 @@ void Eval_component_w_assign_mul(void) { ecs_fini(world); } + +typedef struct { + int32_t _dummy_1; + int32_t x; + int32_t _dummy_2; + int32_t y; +} OpaqueStruct; + +static void* OpaqueStruct_member(void *ptr, const char *member) { + OpaqueStruct *data = ptr; + if (!strcmp(member, "x")) { + return &data->x; + } else if (!strcmp(member, "y")) { + return &data->y; + } else { + return NULL; + } +} + +void Eval_opaque_struct_component(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, OpaqueStruct); + + ecs_entity_t s = ecs_struct(world, { + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)}, + } + }); + + ecs_opaque(world, { + .entity = ecs_id(OpaqueStruct), + .type.as_type = s, + .type.ensure_member = OpaqueStruct_member + }); + + const char *expr = + HEAD "e {" + LINE " OpaqueStruct: {10, 20}" + LINE "}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + const OpaqueStruct *p = ecs_get(world, e, OpaqueStruct); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +typedef struct Opaque_string { + int32_t len; + char *value; +} Opaque_string; + +static void Opaque_string_set(void *ptr, const char *value) { + ((Opaque_string*)ptr)->value = ecs_os_strdup(value); +} + +void Eval_opaque_string_component(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Opaque_string); + + ecs_opaque(world, { + .entity = ecs_id(Opaque_string), + .type.as_type = ecs_id(ecs_string_t), + .type.assign_string = Opaque_string_set + }); + + const char *expr = + HEAD "e {" + LINE " Opaque_string: {\"Hello World\"}" + LINE "}" + ; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(e != 0); + + const Opaque_string *p = ecs_get(world, e, Opaque_string); + test_assert(p != NULL); + test_str(p->value, "Hello World"); + + ecs_os_free(p->value); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index d44a8aeee..3407b4504 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -318,6 +318,8 @@ void Eval_component_assign_w_match(void); void Eval_const_w_match(void); void Eval_component_w_assign_add(void); void Eval_component_w_assign_mul(void); +void Eval_opaque_struct_component(void); +void Eval_opaque_string_component(void); // Testsuite 'Template' void Template_template_no_scope(void); @@ -928,6 +930,7 @@ void Deserialize_opaque_struct(void); void Deserialize_opaque_struct_w_member(void); void Deserialize_opaque_struct_w_member_reverse(void); void Deserialize_struct_w_opaque_member(void); +void Deserialize_opaque_string(void); // Testsuite 'Fuzzing' void Fuzzing_1(void); @@ -2181,6 +2184,14 @@ bake_test_case Eval_testcases[] = { { "component_w_assign_mul", Eval_component_w_assign_mul + }, + { + "opaque_struct_component", + Eval_opaque_struct_component + }, + { + "opaque_string_component", + Eval_opaque_string_component } }; @@ -4578,6 +4589,10 @@ bake_test_case Deserialize_testcases[] = { { "struct_w_opaque_member", Deserialize_struct_w_opaque_member + }, + { + "opaque_string", + Deserialize_opaque_string } }; @@ -4654,7 +4669,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 309, + 311, Eval_testcases }, { @@ -4705,7 +4720,7 @@ static bake_test_suite suites[] = { "Deserialize", Deserialize_setup, NULL, - 95, + 96, Deserialize_testcases, 1, Deserialize_params From f1d52fcb4060a67cb177cc6d49be79524513a556 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Thu, 2 Jan 2025 21:05:58 -0800 Subject: [PATCH 36/36] Improve ecs_delete_empty_tables API by introducing desc struct as parameter --- distr/flecs.c | 15 ++++----- distr/flecs.h | 30 +++++++++++------ include/flecs.h | 30 +++++++++++------ src/addons/script/visit_eval.c | 3 -- src/world.c | 12 ++++--- test/core/src/World.c | 60 ++++++++++++++++++++++------------ 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 7c78d4103..51e64fe57 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -20540,11 +20540,7 @@ void ecs_run_aperiodic( int32_t ecs_delete_empty_tables( ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds) + const ecs_delete_empty_tables_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); @@ -20554,6 +20550,12 @@ int32_t ecs_delete_empty_tables( int32_t delete_count = 0; bool time_budget = false; + ecs_id_t id = desc->id; + uint16_t clear_generation = desc->clear_generation; + uint16_t delete_generation = desc->delete_generation; + int32_t min_id_count = desc->min_id_count; + double time_budget_seconds = desc->time_budget_seconds; + if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } @@ -62872,9 +62874,6 @@ int ecs_script_eval( int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v, &priv_desc); - ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); - ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); - return result; } diff --git a/distr/flecs.h b/distr/flecs.h index ac32715b8..2295d336c 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -5821,6 +5821,24 @@ void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags); +/** Used with ecs_delete_empty_tables(). */ +typedef struct ecs_delete_empty_tables_desc_t { + /** Optional component filter for the tables to evaluate. */ + ecs_id_t id; + + /** Free table data when generation > clear_generation. */ + uint16_t clear_generation; + + /** Delete table when generation > delete_generation. */ + uint16_t delete_generation; + + /** Minimum number of component ids the table should have. */ + int32_t min_id_count; + + /** Amount of time operation is allowed to spend. */ + double time_budget_seconds; +} ecs_delete_empty_tables_desc_t; + /** Cleanup empty tables. * This operation cleans up empty tables that meet certain conditions. Having * large amounts of empty tables does not negatively impact performance of the @@ -5847,21 +5865,13 @@ void ecs_run_aperiodic( * The time budget specifies how long the operation should take at most. * * @param world The world. - * @param id Optional component filter for the tables to evaluate. - * @param clear_generation Free table data when generation > clear_generation. - * @param delete_generation Delete table when generation > delete_generation. - * @param min_id_count Minimum number of component ids the table should have. - * @param time_budget_seconds Amount of time operation is allowed to spend. + * @param desc Configuration parameters. * @return Number of deleted tables. */ FLECS_API int32_t ecs_delete_empty_tables( ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds); + const ecs_delete_empty_tables_desc_t *desc); /** Get world from poly. * diff --git a/include/flecs.h b/include/flecs.h index 18da32b8a..f011bf060 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -2561,6 +2561,24 @@ void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags); +/** Used with ecs_delete_empty_tables(). */ +typedef struct ecs_delete_empty_tables_desc_t { + /** Optional component filter for the tables to evaluate. */ + ecs_id_t id; + + /** Free table data when generation > clear_generation. */ + uint16_t clear_generation; + + /** Delete table when generation > delete_generation. */ + uint16_t delete_generation; + + /** Minimum number of component ids the table should have. */ + int32_t min_id_count; + + /** Amount of time operation is allowed to spend. */ + double time_budget_seconds; +} ecs_delete_empty_tables_desc_t; + /** Cleanup empty tables. * This operation cleans up empty tables that meet certain conditions. Having * large amounts of empty tables does not negatively impact performance of the @@ -2587,21 +2605,13 @@ void ecs_run_aperiodic( * The time budget specifies how long the operation should take at most. * * @param world The world. - * @param id Optional component filter for the tables to evaluate. - * @param clear_generation Free table data when generation > clear_generation. - * @param delete_generation Delete table when generation > delete_generation. - * @param min_id_count Minimum number of component ids the table should have. - * @param time_budget_seconds Amount of time operation is allowed to spend. + * @param desc Configuration parameters. * @return Number of deleted tables. */ FLECS_API int32_t ecs_delete_empty_tables( ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds); + const ecs_delete_empty_tables_desc_t *desc); /** Get world from poly. * diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index b0dd3d2bc..e075fbb25 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1478,9 +1478,6 @@ int ecs_script_eval( int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v, &priv_desc); - ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); - ecs_delete_empty_tables(script->world, 0, 0, 1, 0, 0); - return result; } diff --git a/src/world.c b/src/world.c index 7dfa9754a..5119b58ff 100644 --- a/src/world.c +++ b/src/world.c @@ -2205,11 +2205,7 @@ void ecs_run_aperiodic( int32_t ecs_delete_empty_tables( ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds) + const ecs_delete_empty_tables_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); @@ -2219,6 +2215,12 @@ int32_t ecs_delete_empty_tables( int32_t delete_count = 0; bool time_budget = false; + ecs_id_t id = desc->id; + uint16_t clear_generation = desc->clear_generation; + uint16_t delete_generation = desc->delete_generation; + int32_t min_id_count = desc->min_id_count; + double time_budget_seconds = desc->time_budget_seconds; + if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } diff --git a/test/core/src/World.c b/test/core/src/World.c index ffa5b2bbe..858eac7cc 100644 --- a/test/core/src/World.c +++ b/test/core/src/World.c @@ -1137,10 +1137,12 @@ void World_delete_empty_tables_after_mini(void) { int32_t empty_table_count = info->table_count; int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Increase to 1 */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1}); /* Increase to 1 */ test_int(deleted, 0); - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Delete */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1}); /* Delete */ test_assert(deleted != 0); test_int(info->table_count + deleted, empty_table_count); @@ -1151,10 +1153,12 @@ void World_delete_empty_tables_after_init(void) { ecs_world_t *world = ecs_init(); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Increase to 1 */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); /* Increase to 1 */ test_int(deleted, 0); - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Delete */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); /* Delete */ test_assert(deleted != 0); ecs_fini(world); @@ -1178,10 +1182,12 @@ void World_delete_1000_empty_tables(void) { test_int(info->table_count, old_table_count + 1000 + 1); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Increase to 1 */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); /* Increase to 1 */ test_int(deleted, 0); - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); /* Delete */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); /* Delete */ test_assert(deleted != 0); test_assert(deleted >= 1000); @@ -1213,10 +1219,12 @@ void World_delete_empty_tables_for_id(void) { test_int(info->table_count, old_table_count + 1000 + 2); int32_t deleted; - deleted = ecs_delete_empty_tables(world, TagA, 0, 1, 0, 0); /* Increase to 1 */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .id = TagA, .delete_generation = 1 }); test_int(deleted, 0); - deleted = ecs_delete_empty_tables(world, TagA, 0, 1, 0, 0); /* Delete */ + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .id = TagA, .delete_generation = 1 }); test_assert(deleted != 0); test_assert(deleted >= 500); test_assert(deleted < 1000); @@ -1236,9 +1244,11 @@ void World_use_after_delete_empty(void) { ecs_remove(world, e, TagA); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); test_assert(deleted == 0); - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); test_assert(deleted != 0); ecs_add(world, e, TagA); @@ -1260,9 +1270,11 @@ void World_use_after_clear_empty(void) { ecs_remove(world, e, TagA); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); ecs_add(world, e, TagA); @@ -1289,13 +1301,15 @@ void World_use_after_delete_empty_w_component(void) { test_assert( !ecs_has(world, e, Velocity)); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); test_assert(deleted == 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); test_assert( ecs_has(world, e, Position)); - deleted = ecs_delete_empty_tables(world, 0, 0, 1, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .delete_generation = 1 }); test_assert(deleted != 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); @@ -1326,13 +1340,15 @@ void World_use_after_clear_empty_w_component(void) { test_assert( !ecs_has(world, e, Velocity)); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); test_assert( ecs_has(world, e, Position)); - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); @@ -1370,13 +1386,15 @@ void World_use_after_clear_empty_w_component_w_lifecycle(void) { test_assert( !ecs_has(world, e, Velocity)); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); test_assert( ecs_has(world, e, Position)); - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); test_bool(true, ecs_is_alive(world, ecs_id(Position))); test_bool(true, ecs_is_alive(world, ecs_id(Velocity))); @@ -1397,9 +1415,11 @@ void World_use_after_clear_unused(void) { ECS_TAG(world, TagB); int32_t deleted; - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); - deleted = ecs_delete_empty_tables(world, 0, 1, 0, 0, 0); + deleted = ecs_delete_empty_tables(world, + &(ecs_delete_empty_tables_desc_t){ .clear_generation = 1 }); test_assert(deleted == 0); ecs_entity_t e = ecs_new(world);