diff --git a/distr/flecs.c b/distr/flecs.c index c69d71412..eb15b1a30 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -56283,1885 +56283,418 @@ const char* ecs_script_expr_run( #endif /** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. + * @file addons/script/interpolate.c + * @brief String interpolation. */ #ifdef FLECS_SCRIPT - -#define flecs_expr_ast_new(parser, T, kind)\ - (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) +#include static -void* flecs_expr_ast_new_( - ecs_script_parser_t *parser, - ecs_size_t size, - ecs_expr_node_kind_t kind) +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) { - ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_allocator_t *a = &parser->script->allocator; - ecs_expr_node_t *result = flecs_calloc(a, size); - result->kind = kind; - result->pos = parser->pos; - return result; -} + char ch, *bptr = token_out; -ecs_expr_val_t* flecs_expr_bool( - ecs_script_parser_t *parser, - bool value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.bool_ = value; - result->ptr = &result->storage.bool_; - result->node.type = ecs_id(ecs_bool_t); - return result; -} + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } -ecs_expr_val_t* flecs_expr_int( - ecs_script_parser_t *parser, - int64_t value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.i64 = value; - result->ptr = &result->storage.i64; - result->node.type = ecs_id(ecs_i64_t); - return result; -} + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; + } + } -ecs_expr_val_t* flecs_expr_uint( - ecs_script_parser_t *parser, - uint64_t value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.u64 = value; - result->ptr = &result->storage.u64; - result->node.type = ecs_id(ecs_i64_t); - return result; -} + if (bptr == token_out) { + goto error; + } -ecs_expr_val_t* flecs_expr_float( - ecs_script_parser_t *parser, - double value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.f64 = value; - result->ptr = &result->storage.f64; - result->node.type = ecs_id(ecs_f64_t); - return result; -} + *bptr = '\0'; -ecs_expr_val_t* flecs_expr_string( - ecs_script_parser_t *parser, - const char *value) -{ - ecs_expr_val_t *result = flecs_expr_ast_new( - parser, ecs_expr_val_t, EcsExprValue); - result->storage.string = value; - result->ptr = &result->storage.string; - result->node.type = ecs_id(ecs_string_t); - return result; + return ptr; +error: + return NULL; } -ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_parser_t *parser, - const char *value) +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) { - ecs_expr_identifier_t *result = flecs_expr_ast_new( - parser, ecs_expr_identifier_t, EcsExprIdentifier); - result->value = value; - return result; -} + char ch, *bptr = token_out; -ecs_expr_variable_t* flecs_expr_variable( - ecs_script_parser_t *parser, - const char *value) -{ - ecs_expr_variable_t *result = flecs_expr_ast_new( - parser, ecs_expr_variable_t, EcsExprVariable); - result->value = value; - return result; -} + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } -ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser) -{ - ecs_expr_unary_t *result = flecs_expr_ast_new( - parser, ecs_expr_unary_t, EcsExprUnary); - return result; -} + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } -ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser) -{ - ecs_expr_binary_t *result = flecs_expr_ast_new( - parser, ecs_expr_binary_t, EcsExprBinary); - return result; -} + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } + } -ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser) -{ - ecs_expr_member_t *result = flecs_expr_ast_new( - parser, ecs_expr_member_t, EcsExprMember); - return result; -} + if (bptr == token_out) { + goto error; + } -ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser) -{ - ecs_expr_element_t *result = flecs_expr_ast_new( - parser, ecs_expr_element_t, EcsExprElement); - return result; + *bptr = '\0'; + + return ptr; +error: + return NULL; } -ecs_expr_cast_t* flecs_expr_cast( - ecs_script_t *script, - ecs_expr_node_t *expr, - ecs_entity_t type) +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) { - 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; - result->node.pos = expr->pos; - result->node.type = type; - result->expr = expr; - return result; -} + char token[ECS_MAX_TOKEN_SIZE]; + ecs_strbuf_t result = ECS_STRBUF_INIT; + const char *ptr; + char ch; -#endif + for(ptr = str; (ch = *ptr); ptr++) { + if (ch == '\\') { + ptr ++; + if (ptr[0] == '$') { + ecs_strbuf_appendch(&result, '$'); + continue; + } + if (ptr[0] == '\\') { + ecs_strbuf_appendch(&result, '\\'); + continue; + } + if (ptr[0] == '{') { + ecs_strbuf_appendch(&result, '{'); + continue; + } + if (ptr[0] == '}') { + ecs_strbuf_appendch(&result, '}'); + continue; + } + ptr --; + } -/** - * @file addons/script/expr_to_str.c - * @brief Script expression AST to string visitor. - */ + if (ch == '$') { + ptr = flecs_parse_var_name(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid variable name '%s'", ptr); + goto error; + } + ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); + if (!var) { + ecs_parser_error(NULL, str, ptr - str, + "unresolved variable '%s'", token); + goto error; + } -#ifdef FLECS_SCRIPT + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; + } -typedef struct ecs_expr_str_visitor_t { - const ecs_world_t *world; - ecs_strbuf_t *buf; - int32_t depth; - bool newline; -} ecs_expr_str_visitor_t; + ptr --; + } else if (ch == '{') { + ptr = flecs_parse_interpolated_str(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid interpolated expression"); + goto error; + } -int flecs_expr_node_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_node_t *node); + ecs_script_expr_run_desc_t expr_desc = { + .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) + }; -int flecs_expr_value_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_val_t *node) -{ - return ecs_ptr_to_str_buf( - v->world, node->node.type, node->ptr, v->buf); -} + ecs_value_t expr_result = {0}; + if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { + goto error; + } -int flecs_expr_unary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_unary_t *node) -{ - switch(node->operator) { - case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; - default: - ecs_err("invalid operator for unary expression"); - return -1; - }; + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } - if (flecs_expr_node_to_str(v, node->expr)) { - goto error; + ecs_value_free(world, expr_result.type, expr_result.ptr); + + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } } - return 0; + return ecs_strbuf_get(&result); error: - return -1; + return NULL; } -int flecs_expr_binary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_binary_t *node) -{ - ecs_strbuf_appendlit(v->buf, "("); +#endif - if (flecs_expr_node_to_str(v, node->left)) { - goto error; - } +/** + * @file addons/script/parser.c + * @brief Script grammar parser. + */ - ecs_strbuf_appendlit(v->buf, " "); - switch(node->operator) { - case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; - case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; - case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; - case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; - case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; - case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; - case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; - case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; - case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; - case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; - case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; - case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; - case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; - case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; - case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; - case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; - case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; - default: - ecs_err("invalid operator for binary expression"); - return -1; - }; +#ifdef FLECS_SCRIPT +/** + * @file addons/script/parser.h + * @brief Script grammar parser. + * + * Macro utilities that facilitate a simple recursive descent parser. + */ - ecs_strbuf_appendlit(v->buf, " "); +#ifndef FLECS_SCRIPT_PARSER_H +#define FLECS_SCRIPT_PARSER_H - if (flecs_expr_node_to_str(v, node->right)) { - goto error; +#if defined(ECS_TARGET_CLANG) +/* Ignore unused enum constants in switch as it would blow up the parser code */ +#pragma clang diagnostic ignored "-Wswitch-enum" +/* To allow for nested Parse statements */ +#pragma clang diagnostic ignored "-Wshadow" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wshadow" +#elif defined(ECS_TARGET_MSVC) +/* Allow for variable shadowing */ +#pragma warning(disable : 4456) +#endif + +/* Create script & parser structs with static token buffer */ +#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ + ecs_script_impl_t script = {\ + .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ + .pub.name = script_name,\ + .pub.code = expr\ + };\ + ecs_script_parser_t parser = {\ + .script = flecs_script_impl(&script),\ + .pos = expr,\ + .token_cur = tokens\ } - ecs_strbuf_appendlit(v->buf, ")"); +/* Definitions for parser functions */ +#define ParserBegin\ + ecs_script_tokenizer_t _tokenizer = {{0}};\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_script_tokenizer_t *tokenizer = &_tokenizer; - return 0; -error: - return -1; -} +#define ParserEnd\ + Error("unexpected end of rule (parser error)");\ + error:\ + return NULL -int flecs_expr_identifier_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_identifier_t *node) -{ - ecs_strbuf_appendstr(v->buf, node->value); - return 0; -} +/* Get token */ +#define Token(n) (tokenizer->tokens[n].value) -int flecs_expr_variable_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_variable_t *node) -{ - ecs_strbuf_appendlit(v->buf, "$"); - ecs_strbuf_appendstr(v->buf, node->value); - return 0; -} +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; -int flecs_expr_member_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_member_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; - ecs_strbuf_appendlit(v->buf, "."); - ecs_strbuf_appendstr(v->buf, node->member_name); - return 0; -} +/* Error */ +#define Error(...)\ + ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ + (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ + goto error -int flecs_expr_element_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_element_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } +/* Parse expression */ +#define Expr(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ + goto error;\ + }\ + if (!t->value[0] && (until == '\n' || until == '{')) {\ + pos ++;\ + Error("empty expression");\ + }\ + }\ + Parse_1(until, __VA_ARGS__) - ecs_strbuf_appendlit(v->buf, "["); - if (flecs_expr_node_to_str(v, node->index)) { - return -1; - } - ecs_strbuf_appendlit(v->buf, "]"); - return 0; -} +/* Parse token until character */ +#define Until(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_until(parser, pos, t, until))) {\ + goto error;\ + }\ + }\ + Parse_1(until, __VA_ARGS__) -int flecs_expr_cast_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_cast_t *node) -{ - return flecs_expr_node_to_str(v, node->expr); -} +/* Parse next token */ +#define Parse(...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_script_token(parser, pos, t, false))) {\ + goto error;\ + }\ + switch(t->kind) {\ + __VA_ARGS__\ + default:\ + if (t->value) {\ + Error("unexpected %s'%s'", \ + flecs_script_token_kind_str(t->kind), t->value);\ + } else {\ + Error("unexpected %s", \ + flecs_script_token_kind_str(t->kind));\ + }\ + }\ + } -int flecs_expr_node_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_node_t *node) -{ - ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); +/* Parse N consecutive tokens */ +#define Parse_1(tok, ...)\ + Parse(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) - if (node->type) { - ecs_strbuf_append(v->buf, "%s", ECS_BLUE); - ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); - ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); - } +#define Parse_2(tok1, tok2, ...)\ + Parse_1(tok1, \ + Parse(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + ) - switch(node->kind) { - case EcsExprValue: - if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { - goto error; - } - break; - case EcsExprUnary: - if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { - goto error; - } - break; - case EcsExprIdentifier: - if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { - goto error; - } - break; - case EcsExprVariable: - if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { - goto error; - } - break; - case EcsExprFunction: - break; - case EcsExprMember: - if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { - goto error; - } - break; - case EcsExprElement: - if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { - goto error; - } - break; - case EcsExprCast: - if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { - goto error; - } - break; - } - - if (node->type) { - ecs_strbuf_append(v->buf, ")"); - } - - return 0; -error: - return -1; -} - -FLECS_API -char* ecs_script_expr_to_str( - const ecs_world_t *world, - const ecs_expr_node_t *expr) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; - if (flecs_expr_node_to_str(&v, expr)) { - ecs_strbuf_reset(&buf); - return NULL; - } - - return ecs_strbuf_get(&buf); -} - -#endif - -/** - * @file addons/script/expr_fold.c - * @brief Script expression constant folding. - */ - - -#ifdef FLECS_SCRIPT - -#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) - -#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ - ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) - -#define ECS_BINARY_INT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ - return -1;\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(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)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ - } else if (left->type == ecs_id(ecs_i64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ - } else if (left->type == ecs_id(ecs_f64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ - } else if (left->type == ecs_id(ecs_u8_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ - } else if (left->type == ecs_id(ecs_char_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ - } else if (left->type == ecs_id(ecs_bool_t)) { \ - ECS_BINARY_OP_T(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_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -#define ECS_BINARY_UINT_OP(left, right, result, op)\ - if (left->type == ecs_id(ecs_u64_t)) { \ - ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ - } else {\ - ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ - } - -int flecs_expr_unary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; - - if (node->operator != EcsTokNot) { - flecs_expr_visit_error(script, node, - "operator invalid for unary expression"); - goto error; - } - - if (flecs_script_expr_visit_fold(script, &node->expr)) { - goto error; - } - - if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - if (node->node.type != ecs_id(ecs_bool_t)) { - char *type_str = ecs_get_path(script->world, node->node.type); - flecs_expr_visit_error(script, node, - "! operator cannot be applied to value of type '%s' (must be bool)"); - ecs_os_free(type_str); - goto error; - } - - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = ecs_id(ecs_bool_t); - result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); - - return 0; -error: - return -1; -} - -int flecs_expr_binary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - - if (flecs_script_expr_visit_fold(script, &node->left)) { - goto error; - } - - if (flecs_script_expr_visit_fold(script, &node->right)) { - goto error; - } - - if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - ecs_expr_val_t *result = flecs_calloc_t( - &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); - result->ptr = &result->storage.u64; - result->node.kind = EcsExprValue; - result->node.pos = node->node.pos; - result->node.type = node->node.type; - - switch(node->operator) { - case EcsTokAdd: - ECS_BINARY_OP(node->left, node->right, result, +); - break; - case EcsTokSub: - ECS_BINARY_OP(node->left, node->right, result, -); - break; - case EcsTokMul: - ECS_BINARY_OP(node->left, node->right, result, *); - break; - case EcsTokDiv: - ECS_BINARY_OP(node->left, node->right, result, /); - break; - case EcsTokMod: - ECS_BINARY_INT_OP(node->left, node->right, result, %); - break; - case EcsTokEq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); - break; - case EcsTokNeq: - ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); - break; - case EcsTokGt: - ECS_BINARY_COND_OP(node->left, node->right, result, >); - break; - case EcsTokGtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, >=); - break; - case EcsTokLt: - ECS_BINARY_COND_OP(node->left, node->right, result, <); - break; - case EcsTokLtEq: - ECS_BINARY_COND_OP(node->left, node->right, result, <=); - break; - case EcsTokAnd: - ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); - break; - case EcsTokOr: - ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); - break; - case EcsTokShiftLeft: - ECS_BINARY_UINT_OP(node->left, node->right, result, <<); - break; - case EcsTokShiftRight: - ECS_BINARY_UINT_OP(node->left, node->right, result, >>); - break; - default: - flecs_expr_visit_error(script, node->left, "unsupported operator"); - goto error; - } - - *node_ptr = (ecs_expr_node_t*)result; - - return 0; -error: - return -1; -} - -int flecs_expr_cast_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - - if (flecs_script_expr_visit_fold(script, &node->expr)) { - goto error; - } - - if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; - - /* Reuse existing node to hold casted value */ - *node_ptr = (ecs_expr_node_t*)expr; - - ecs_entity_t dst_type = node->node.type; - ecs_entity_t src_type = expr->node.type; - - if (dst_type == src_type) { - /* No cast necessary if types are equal */ - return 0; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); - ecs_value_t value = { - .type = src_type, - .ptr = expr->ptr - }; - - ecs_meta_set_value(&cur, &value); - - expr->node.type = dst_type; - - return 0; -error: - return -1; -} - -int flecs_script_expr_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr) -{ - ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_expr_node_t *node = *node_ptr; - - switch(node->kind) { - case EcsExprValue: - break; - case EcsExprUnary: - if (flecs_expr_unary_visit_fold(script, node_ptr)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_visit_fold(script, node_ptr)) { - goto error; - } - break; - case EcsExprIdentifier: - break; - case EcsExprVariable: - break; - case EcsExprFunction: - break; - case EcsExprMember: - break; - case EcsExprElement: - break; - case EcsExprCast: - if (flecs_expr_cast_visit_fold(script, node_ptr)) { - goto error; - } - break; - } - - return 0; -error: - return -1; -} - -#endif - -/** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -static -bool flecs_expr_operator_is_equality( - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return true; - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - return false; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } -error: - return false; -} - -static -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 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 true; - else if (type == ecs_id(ecs_string_t)) return false; - else if (type == ecs_id(ecs_entity_t)) return false; - else return false; -} - -static -ecs_entity_t flecs_expr_largest_type( - const EcsPrimitive *type) -{ - switch(type->kind) { - case EcsBool: return ecs_id(ecs_bool_t); - case EcsChar: return ecs_id(ecs_char_t); - case EcsByte: return ecs_id(ecs_u8_t); - case EcsU8: return ecs_id(ecs_u64_t); - case EcsU16: return ecs_id(ecs_u64_t); - case EcsU32: return ecs_id(ecs_u64_t); - case EcsU64: return ecs_id(ecs_u64_t); - case EcsI8: return ecs_id(ecs_i64_t); - case EcsI16: return ecs_id(ecs_i64_t); - case EcsI32: return ecs_id(ecs_i64_t); - case EcsI64: return ecs_id(ecs_i64_t); - case EcsF32: return ecs_id(ecs_f64_t); - case EcsF64: return ecs_id(ecs_f64_t); - case EcsUPtr: return ecs_id(ecs_u64_t); - case EcsIPtr: return ecs_id(ecs_i64_t); - case EcsString: return ecs_id(ecs_string_t); - case EcsEntity: return ecs_id(ecs_entity_t); - case EcsId: return ecs_id(ecs_id_t); - default: ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -/** Promote type to most expressive (f64 > i64 > u64) */ -static -ecs_entity_t flecs_expr_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) -{ - if (type == ecs_id(ecs_u64_t)) { - return promote_to; - } - if (promote_to == ecs_id(ecs_u64_t)) { - return type; - } - if (type == ecs_id(ecs_f64_t)) { - return type; - } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; - } - return ecs_id(ecs_i64_t); -} - -static -bool flecs_expr_oper_valid_for_type( - ecs_entity_t type, - ecs_script_token_kind_t op) -{ - switch(op) { - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - return flecs_expr_is_type_number(type); - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - return type == ecs_id(ecs_u64_t) || - type == ecs_id(ecs_u32_t) || - type == ecs_id(ecs_u16_t) || - type == ecs_id(ecs_u8_t); - case EcsTokEq: - case EcsTokNeq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - return flecs_expr_is_type_number(type) || - (type == ecs_id(ecs_bool_t)) || - (type == ecs_id(ecs_char_t)) || - (type == ecs_id(ecs_entity_t)); - default: - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } -} - -static -int flecs_expr_type_for_oper( - ecs_script_t *script, - ecs_expr_binary_t *node, - 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; - - switch(node->operator) { - case EcsTokDiv: - /* Result type of a division is always a float */ - *operand_type = ecs_id(ecs_f64_t); - *result_type = ecs_id(ecs_f64_t); - return 0; - case EcsTokAnd: - case EcsTokOr: - /* Result type of a condition operator is always a bool */ - *operand_type = ecs_id(ecs_bool_t); - *result_type = ecs_id(ecs_bool_t); - return 0; - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - /* Result type of equality operator is always bool, but operand types - * should not be casted to bool */ - *result_type = ecs_id(ecs_bool_t); - break; - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBitwiseAnd: - case EcsTokBitwiseOr: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - break; - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } - - 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) { - 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)", - lname, rname); - ecs_os_free(lname); - ecs_os_free(rname); - return 0; - } - - ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); - ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); - if (ltype == rtype) { - *operand_type = ltype; - goto done; - } - - if (flecs_expr_operator_is_equality(node->operator)) { - char *lname = ecs_id_str(world, ltype); - char *rname = ecs_id_str(world, rtype); - flecs_expr_visit_error(script, node, - "mismatching types in equality expression (%s vs %s)", - lname, rname); - ecs_os_free(rname); - ecs_os_free(lname); - return 0; - } - - if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { - flecs_expr_visit_error(script, node, - "incompatible types in binary expression"); - return 0; - } - - *operand_type = flecs_expr_promote_type(ltype, rtype); - -done: - if (node->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); - } - - if (!*result_type) { - *result_type = *operand_type; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_unary_visit_type( - ecs_script_t *script, - ecs_expr_unary_t *node) -{ - if (flecs_script_expr_visit_type(script, node->expr)) { - goto error; - } - - /* The only supported unary expression is not (!) which returns a bool */ - node->node.type = ecs_id(ecs_bool_t); - - return 0; -error: - return -1; -} - -static -int flecs_expr_binary_visit_type( - ecs_script_t *script, - ecs_expr_binary_t *node) -{ - /* Operands must be of this type or casted to it */ - ecs_entity_t operand_type = 0; - - /* Resulting type of binary expression */ - ecs_entity_t result_type = 0; - - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - if (flecs_script_expr_visit_type(script, node->right)) { - goto error; - } - - if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { - goto error; - } - - if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { - flecs_expr_visit_error(script, node, "invalid operator for type"); - goto error; - } - - if (operand_type != node->left->type) { - node->left = (ecs_expr_node_t*)flecs_expr_cast( - script, node->left, operand_type); - } - - if (operand_type != node->right->type) { - node->right = (ecs_expr_node_t*)flecs_expr_cast( - script, node->right, operand_type); - } - - node->node.type = result_type; - - return 0; -error: - return -1; -} - -static -int flecs_expr_identifier_visit_type( - ecs_script_t *script, - ecs_expr_identifier_t *node) -{ - node->node.type = ecs_id(ecs_entity_t); - node->id = ecs_lookup(script->world, node->value); - if (!node->id) { - flecs_expr_visit_error(script, node, - "unresolved identifier '%s'", node->value); - goto error; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_variable_visit_type( - ecs_script_t *script, - ecs_expr_variable_t *node) -{ - node->node.type = ecs_id(ecs_entity_t); - return 0; -} - -static -int flecs_expr_member_visit_type( - ecs_script_t *script, - ecs_expr_member_t *node) -{ - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - - const EcsType *type = ecs_get(world, left_type, EcsType); - if (!type) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot resolve member on value of type '%s' (missing reflection data)", - type_str); - ecs_os_free(type_str); - goto error; - } - - if (type->kind != EcsStructType) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot resolve member on non-struct type '%s'", - type_str); - ecs_os_free(type_str); - goto error; - } - - ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); - ecs_meta_push(&cur); /* { */ - int prev_log = ecs_log_set_level(-4); - if (ecs_meta_dotmember(&cur, node->member_name)) { - ecs_log_set_level(prev_log); - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "unresolved member '%s' for type '%s'", - node->member_name, type_str); - ecs_os_free(type_str); - goto error; - } - ecs_log_set_level(prev_log); - - node->node.type = ecs_meta_get_type(&cur); - ecs_meta_pop(&cur); /* } */ - - return 0; -error: - return -1; -} - -static -int flecs_expr_element_visit_type( - ecs_script_t *script, - ecs_expr_element_t *node) -{ - if (flecs_script_expr_visit_type(script, node->left)) { - goto error; - } - - if (flecs_script_expr_visit_type(script, node->index)) { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - const EcsType *type = ecs_get(world, left_type, EcsType); - if (!type) { - char *type_str = ecs_get_path(world, left_type); - flecs_expr_visit_error(script, node, - "cannot use [] on value of type '%s' (missing reflection data)", - type_str); - ecs_os_free(type_str); - goto error; - } - - bool is_entity_type = false; - - if (type->kind == EcsPrimitiveType) { - const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); - if (ptype->kind == EcsEntity) { - is_entity_type = true; - } - } - - if (is_entity_type) { - if (node->index->kind == EcsExprIdentifier) { - node->node.type = ((ecs_expr_identifier_t*)node->index)->id; - } else { - flecs_expr_visit_error(script, node, - "invalid component expression"); - goto error; - } - } else if (type->kind == EcsArrayType) { - const EcsArray *type_array = ecs_get(world, left_type, EcsArray); - ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_array->type; - } else if (type->kind == EcsVectorType) { - const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); - ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); - node->node.type = type_vector->type; - } else { - char *type_str = ecs_get_path(script->world, node->left->type); - flecs_expr_visit_error(script, node, - "invalid usage of [] on non collection/entity type '%s'", type_str); - ecs_os_free(type_str); - goto error; - } - - return 0; -error: - return -1; -} - -int flecs_script_expr_visit_type( - ecs_script_t *script, - ecs_expr_node_t *node) -{ - ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - - switch(node->kind) { - case EcsExprValue: - /* Value types are assigned by the AST */ - break; - case EcsExprUnary: - if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { - goto error; - } - break; - case EcsExprIdentifier: - if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { - goto error; - } - break; - case EcsExprVariable: - if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { - goto error; - } - break; - case EcsExprFunction: - break; - case EcsExprMember: - if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { - goto error; - } - break; - case EcsExprElement: - if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { - goto error; - } - break; - case EcsExprCast: - break; - } - - return 0; -error: - return -1; -} - -#endif - -/** - * @file addons/script/interpolate.c - * @brief String interpolation. - */ - - -#ifdef FLECS_SCRIPT -#include - -static -const char* flecs_parse_var_name( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (isalpha(ch) || isdigit(ch) || ch == '_') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -static -const char* flecs_parse_interpolated_str( - const char *ptr, - char *token_out) -{ - char ch, *bptr = token_out; - - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } - - if (ch == '\\') { - if (ptr[1] == '}') { - *bptr = '}'; - bptr ++; - ptr += 2; - continue; - } - } - - if (ch != '}') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - ptr ++; - break; - } - } - - if (bptr == token_out) { - goto error; - } - - *bptr = '\0'; - - return ptr; -error: - return NULL; -} - -char* ecs_script_string_interpolate( - ecs_world_t *world, - const char *str, - const ecs_script_vars_t *vars) -{ - char token[ECS_MAX_TOKEN_SIZE]; - ecs_strbuf_t result = ECS_STRBUF_INIT; - const char *ptr; - char ch; - - for(ptr = str; (ch = *ptr); ptr++) { - if (ch == '\\') { - ptr ++; - if (ptr[0] == '$') { - ecs_strbuf_appendch(&result, '$'); - continue; - } - if (ptr[0] == '\\') { - ecs_strbuf_appendch(&result, '\\'); - continue; - } - if (ptr[0] == '{') { - ecs_strbuf_appendch(&result, '{'); - continue; - } - if (ptr[0] == '}') { - ecs_strbuf_appendch(&result, '}'); - continue; - } - ptr --; - } - - if (ch == '$') { - ptr = flecs_parse_var_name(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid variable name '%s'", ptr); - goto error; - } - - ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); - if (!var) { - ecs_parser_error(NULL, str, ptr - str, - "unresolved variable '%s'", token); - goto error; - } - - if (ecs_ptr_to_str_buf( - world, var->value.type, var->value.ptr, &result)) - { - goto error; - } - - ptr --; - } else if (ch == '{') { - ptr = flecs_parse_interpolated_str(ptr + 1, token); - if (!ptr) { - ecs_parser_error(NULL, str, ptr - str, - "invalid interpolated expression"); - goto error; - } - - ecs_script_expr_run_desc_t expr_desc = { - .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) - }; - - ecs_value_t expr_result = {0}; - if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { - goto error; - } - - if (ecs_ptr_to_str_buf( - world, expr_result.type, expr_result.ptr, &result)) - { - goto error; - } - - ecs_value_free(world, expr_result.type, expr_result.ptr); - - ptr --; - } else { - ecs_strbuf_appendch(&result, ch); - } - } - - return ecs_strbuf_get(&result); -error: - return NULL; -} - -#endif - -/** - * @file addons/script/expr.c - * @brief Script expression parser. - */ - - -#ifdef FLECS_SCRIPT -/** - * @file addons/script/parser.h - * @brief Script grammar parser. - * - * Macro utilities that facilitate a simple recursive descent parser. - */ - -#ifndef FLECS_SCRIPT_PARSER_H -#define FLECS_SCRIPT_PARSER_H - -#if defined(ECS_TARGET_CLANG) -/* Ignore unused enum constants in switch as it would blow up the parser code */ -#pragma clang diagnostic ignored "-Wswitch-enum" -/* To allow for nested Parse statements */ -#pragma clang diagnostic ignored "-Wshadow" -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#pragma GCC diagnostic ignored "-Wswitch-enum" -#pragma GCC diagnostic ignored "-Wshadow" -#elif defined(ECS_TARGET_MSVC) -/* Allow for variable shadowing */ -#pragma warning(disable : 4456) -#endif - -/* Create script & parser structs with static token buffer */ -#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ - ecs_script_impl_t script = {\ - .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ - .pub.name = script_name,\ - .pub.code = expr\ - };\ - ecs_script_parser_t parser = {\ - .script = flecs_script_impl(&script),\ - .pos = expr,\ - .token_cur = tokens\ - } - -/* Definitions for parser functions */ -#define ParserBegin\ - ecs_script_tokenizer_t _tokenizer = {{0}};\ - _tokenizer.tokens = _tokenizer.stack.tokens;\ - ecs_script_tokenizer_t *tokenizer = &_tokenizer; - -#define ParserEnd\ - Error("unexpected end of rule (parser error)");\ - error:\ - return NULL - -/* Get token */ -#define Token(n) (tokenizer->tokens[n].value) - -/* Push/pop token frame (allows token stack reuse in recursive functions) */ -#define TokenFramePush() \ - tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; - -#define TokenFramePop() \ - tokenizer->tokens = tokenizer->stack.tokens; - -/* Error */ -#define Error(...)\ - ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ - goto error - -/* Parse expression */ -#define Expr(until, ...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ - goto error;\ - }\ - if (!t->value[0] && (until == '\n' || until == '{')) {\ - pos ++;\ - Error("empty expression");\ - }\ - }\ - Parse_1(until, __VA_ARGS__) - -/* Parse token until character */ -#define Until(until, ...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_until(parser, pos, t, until))) {\ - goto error;\ - }\ - }\ - Parse_1(until, __VA_ARGS__) - -/* Parse next token */ -#define Parse(...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_token(parser, pos, t, false))) {\ - goto error;\ - }\ - switch(t->kind) {\ - __VA_ARGS__\ - default:\ - if (t->value) {\ - Error("unexpected %s'%s'", \ - flecs_script_token_kind_str(t->kind), t->value);\ - } else {\ - Error("unexpected %s", \ - flecs_script_token_kind_str(t->kind));\ - }\ - }\ - } - -/* Parse N consecutive tokens */ -#define Parse_1(tok, ...)\ - Parse(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) - -#define Parse_2(tok1, tok2, ...)\ - Parse_1(tok1, \ - Parse(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_3(tok1, tok2, tok3, ...)\ - Parse_2(tok1, tok2, \ - Parse(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_4(tok1, tok2, tok3, tok4, ...)\ - Parse_3(tok1, tok2, tok3, \ - Parse(\ - case tok4: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ - Parse_4(tok1, tok2, tok3, tok4, \ - Parse(\ - case tok5: {\ - __VA_ARGS__\ - }\ - )\ - ) - -#define LookAhead_Keep() \ - pos = lookahead;\ - parser->token_keep = parser->token_cur - -/* Same as Parse, but doesn't error out if token is not in handled cases */ -#define LookAhead(...)\ - const char *lookahead;\ - ecs_script_token_t lookahead_token;\ - const char *old_lh_token_cur = parser->token_cur;\ - if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ - tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ - switch(lookahead_token.kind) {\ - __VA_ARGS__\ - default:\ - tokenizer->stack.count --;\ - break;\ - }\ - if (old_lh_token_cur > parser->token_keep) {\ - parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ - } else {\ - parser->token_cur = parser->token_keep;\ - }\ - } - -/* Lookahead N consecutive tokens */ -#define LookAhead_1(tok, ...)\ - LookAhead(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) - -#define LookAhead_2(tok1, tok2, ...)\ - LookAhead_1(tok1, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - pos = old_ptr;\ - ) - -#define LookAhead_3(tok1, tok2, tok3, ...)\ - LookAhead_2(tok1, tok2, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - pos = old_ptr;\ - ) - -/* Open scope */ -#define Scope(s, ...) {\ - ecs_script_scope_t *old_scope = parser->scope;\ - parser->scope = s;\ - __VA_ARGS__\ - parser->scope = old_scope;\ - } - -/* Parser loop */ -#define Loop(...)\ - int32_t token_stack_count = tokenizer->stack.count;\ - do {\ - tokenizer->stack.count = token_stack_count;\ - __VA_ARGS__\ - } while (true); - -#define EndOfRule return pos - -#endif - - -/* From https://en.cppreference.com/w/c/language/operator_precedence */ - -static int flecs_expr_precedence[] = { - [EcsTokParenOpen] = 1, - [EcsTokMember] = 1, - [EcsTokBracketOpen] = 1, - [EcsTokNot] = 2, - [EcsTokMul] = 3, - [EcsTokDiv] = 3, - [EcsTokMod] = 3, - [EcsTokAdd] = 4, - [EcsTokSub] = 4, - [EcsTokShiftLeft] = 5, - [EcsTokShiftRight] = 5, - [EcsTokGt] = 6, - [EcsTokGtEq] = 6, - [EcsTokLt] = 6, - [EcsTokLtEq] = 6, - [EcsTokEq] = 7, - [EcsTokNeq] = 7, - [EcsTokBitwiseAnd] = 8, - [EcsTokBitwiseOr] = 10, - [EcsTokAnd] = 11, - [EcsTokOr] = 12, -}; - -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -static -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -static -bool flecs_has_precedence( - ecs_script_token_kind_t first, - ecs_script_token_kind_t second) -{ - if (!flecs_expr_precedence[first]) { - return false; - } - return flecs_expr_precedence[first] < flecs_expr_precedence[second]; -} - -static -const char* flecs_script_parse_rhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_expr_node_t *left, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - const char *last_pos = pos; - - TokenFramePush(); - - LookAhead( - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokBracketOpen: - case EcsTokMember: - { - ecs_script_token_kind_t oper = lookahead_token.kind; - - /* Only consume more tokens if operator has precedence */ - if (flecs_has_precedence(left_oper, oper)) { - break; - } - - /* Consume lookahead token */ - pos = lookahead; - - switch(oper) { - case EcsTokBracketOpen: { - ecs_expr_element_t *result = flecs_expr_element(parser); - result->left = *out; - - pos = flecs_script_parse_lhs( - parser, pos, tokenizer, 0, &result->index); - if (!pos) { - goto error; - } - - Parse_1(']', { - *out = (ecs_expr_node_t*)result; - break; - }); - - break; - } - - case EcsTokMember: { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; - - Parse_1(EcsTokIdentifier, { - result->member_name = Token(1); - *out = (ecs_expr_node_t*)result; - break; - }); - - break; - } - - default: { - ecs_expr_binary_t *result = flecs_expr_binary(parser); - result->left = *out; - result->operator = oper; - - pos = flecs_script_parse_lhs(parser, pos, tokenizer, - result->operator, &result->right); - if (!pos) { - goto error; - } - *out = (ecs_expr_node_t*)result; - break; - } - }; - - /* Ensures lookahead tokens in token buffer don't get overwritten */ - parser->token_keep = parser->token_cur; - - break; - } +#define Parse_3(tok1, tok2, tok3, ...)\ + Parse_2(tok1, tok2, \ + Parse(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ ) - if (pos[0] && (pos != last_pos)) { - pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); - } - - TokenFramePop(); - - return pos; -error: - return NULL; -} - -static -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - TokenFramePush(); - - Parse( - case EcsTokNumber: { - const char *expr = Token(0); - if (strchr(expr, '.') || strchr(expr, 'e')) { - *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); - } else if (expr[0] == '-') { - *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); - } else { - *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); - } - break; - } - - case EcsTokString: { - *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); - break; - } - - case EcsTokIdentifier: { - const char *expr = Token(0); - if (expr[0] == '$') { - *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); - } else if (!ecs_os_strcmp(expr, "true")) { - *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); - } else if (!ecs_os_strcmp(expr, "false")) { - *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); - } else { - *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); - } - break; - } - - case EcsTokParenOpen: { - pos = flecs_script_parse_expr(parser, pos, 0, out); - Parse_1(EcsTokParenClose, { - break; - }) - break; - } - - case EcsTokNot: { - ecs_expr_unary_t *unary = flecs_expr_unary(parser); - pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); - unary->operator = EcsTokNot; - *out = (ecs_expr_node_t*)unary; - break; - } +#define Parse_4(tok1, tok2, tok3, tok4, ...)\ + Parse_3(tok1, tok2, tok3, \ + Parse(\ + case tok4: {\ + __VA_ARGS__\ + }\ + )\ ) - TokenFramePop(); - - /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs( - parser, pos, tokenizer, *out, left_oper, out); -error: - return NULL; -} - -static -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - ParserBegin; - - pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); - - EndOfRule; +#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ + Parse_4(tok1, tok2, tok3, tok4, \ + Parse(\ + case tok5: {\ + __VA_ARGS__\ + }\ + )\ + ) - ParserEnd; -} +#define LookAhead_Keep() \ + pos = lookahead;\ + parser->token_keep = parser->token_cur -ecs_expr_node_t* ecs_script_parse_expr( - ecs_world_t *world, - ecs_script_t *script, - const char *name, - const char *expr) -{ - if (!script) { - script = flecs_script_new(world); +/* Same as Parse, but doesn't error out if token is not in handled cases */ +#define LookAhead(...)\ + const char *lookahead;\ + ecs_script_token_t lookahead_token;\ + const char *old_lh_token_cur = parser->token_cur;\ + if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ + switch(lookahead_token.kind) {\ + __VA_ARGS__\ + default:\ + tokenizer->stack.count --;\ + break;\ + }\ + if (old_lh_token_cur > parser->token_keep) {\ + parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ + } else {\ + parser->token_cur = parser->token_keep;\ + }\ } - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .scope = flecs_script_impl(script)->root, - .significant_newline = false - }; - - ecs_script_impl_t *impl = flecs_script_impl(script); +/* Lookahead N consecutive tokens */ +#define LookAhead_1(tok, ...)\ + LookAhead(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) - impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - impl->token_buffer = flecs_alloc( - &impl->allocator, impl->token_buffer_size); - parser.token_cur = impl->token_buffer; +#define LookAhead_2(tok1, tok2, ...)\ + LookAhead_1(tok1, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + pos = old_ptr;\ + ) - ecs_expr_node_t *out = NULL; +#define LookAhead_3(tok1, tok2, tok3, ...)\ + LookAhead_2(tok1, tok2, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + pos = old_ptr;\ + ) - const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); - if (!result) { - goto error; +/* Open scope */ +#define Scope(s, ...) {\ + ecs_script_scope_t *old_scope = parser->scope;\ + parser->scope = s;\ + __VA_ARGS__\ + parser->scope = old_scope;\ } - flecs_script_expr_visit_type(script, out); - flecs_script_expr_visit_fold(script, &out); +/* Parser loop */ +#define Loop(...)\ + int32_t token_stack_count = tokenizer->stack.count;\ + do {\ + tokenizer->stack.count = token_stack_count;\ + __VA_ARGS__\ + } while (true); - return out; -error: - return NULL; -} +#define EndOfRule return pos #endif -/** - * @file addons/script/parser.c - * @brief Script grammar parser. - */ - - -#ifdef FLECS_SCRIPT #define EcsTokEndOfStatement\ case ';':\ @@ -62531,17 +61064,238 @@ int flecs_script_eval_tag( return 0; } - ecs_entity_t src = flecs_script_get_src( - v, v->entity->eval, node->id.eval); - ecs_add_id(v->world, src, node->id.eval); + ecs_entity_t src = flecs_script_get_src( + v, v->entity->eval, node->id.eval); + ecs_add_id(v->world, src, node->id.eval); + + return 0; +} + +static +int flecs_script_eval_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (v->template) { + return 0; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->template) { + return 0; + } + + ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + + if (node->expr && node->expr[0]) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + if (!ti) { + return -1; + } + + const EcsType *type = ecs_get(v->world, ti->component, EcsType); + if (type) { + bool is_collection = false; + + switch(type->kind) { + case EcsPrimitiveType: + case EcsBitmaskType: + case EcsEnumType: + case EcsStructType: + case EcsOpaqueType: + break; + case EcsArrayType: + case EcsVectorType: + is_collection = true; + break; + } + + if (node->is_collection != is_collection) { + char *id_str = ecs_id_str(v->world, ti->component); + if (node->is_collection && !is_collection) { + flecs_script_eval_error(v, node, + "type %s is not a collection (use '%s: {...}')", + id_str, id_str); + } else { + flecs_script_eval_error(v, node, + "type %s is a collection (use '%s: [...]')", + id_str, id_str); + } + ecs_os_free(id_str); + return -1; + } + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, src, node->id.eval), + .type = ti->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); + } + ti->hooks.ctor(value.ptr, 1, ti); + } + + if (ecs_os_strcmp(node->expr, "{}")) { + if (flecs_script_eval_expr(v, node->expr, &value)) { + return -1; + } + } + + ecs_modified_id(v->world, src, node->id.eval); + } else { + ecs_add_id(v->world, src, node->id.eval); + } + + return 0; +} + +static +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; + } + + if (v->template) { + return 0; + } + + ecs_id_t var_id = var->value.type; + + if (var->value.ptr) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, var_id); + if (!ti) { + return -1; + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id), + .type = var_id + }; + + ecs_value_copy_w_type_info(v->world, ti, value.ptr, var->value.ptr); + + ecs_modified_id(v->world, v->entity->eval, var_id); + } else { + ecs_add_id(v->world, v->entity->eval, var_id); + } + + return 0; +} + +static +int flecs_script_eval_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + if (v->template) { + return 0; + } + + ecs_script_scope_t *scope = ecs_script_current_scope(v); + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); + scope = scope->parent; + + if (!scope) { + flecs_script_eval_error(v, node, + "entity '%s' is in root scope which cannot have a default type", + v->entity->name); + return -1; + } + + ecs_id_t default_type = scope->default_component_eval; + if (!default_type) { + flecs_script_eval_error(v, node, + "scope for entity '%s' does not have a default type", + v->entity->name); + return -1; + } + + if (ecs_get_type_info(v->world, default_type) == NULL) { + char *id_str = ecs_id_str(v->world, default_type); + flecs_script_eval_error(v, node, + "cannot use tag '%s' as default type in assignment", + id_str); + ecs_os_free(id_str); + return -1; + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), + .type = default_type + }; + + if (flecs_script_eval_expr(v, node->expr, &value)) { + return -1; + } + + ecs_modified_id(v->world, v->entity->eval, default_type); + + return 0; +} + +static +int flecs_script_eval_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_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; + } + + if (v->template) { + return 0; + } + + ecs_allocator_t *a = v->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types + *value = var->value; return 0; } static -int flecs_script_eval_component( +int flecs_script_eval_with_tag( ecs_script_eval_visitor_t *v, - ecs_script_component_t *node) + ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; @@ -62551,14 +61305,20 @@ int flecs_script_eval_component( return 0; } - if (!v->entity) { - if (node->id.second) { - flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", - node->id.first, node->id.second); - } else { - flecs_script_eval_error(v, node, "missing entity for component %s", - node->id.first); - } + ecs_allocator_t *a = v->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = node->id.eval; + value->ptr = NULL; + + return 0; +} + +static +int flecs_script_eval_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { return -1; } @@ -62566,5919 +61326,6704 @@ int flecs_script_eval_component( return 0; } - ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + ecs_allocator_t *a = v->allocator; + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + + ecs_value_t *value = flecs_script_with_append(a, v, ti); + value->type = node->id.eval; + value->ptr = NULL; if (node->expr && node->expr[0]) { - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, node->id.eval); if (!ti) { return -1; } - const EcsType *type = ecs_get(v->world, ti->component, EcsType); - if (type) { - bool is_collection = false; + value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + value->type = ti->component; // Expression parser needs actual type - switch(type->kind) { - case EcsPrimitiveType: - case EcsBitmaskType: - case EcsEnumType: - case EcsStructType: - case EcsOpaqueType: - break; - case EcsArrayType: - case EcsVectorType: - is_collection = true; - break; - } + if (ti->hooks.ctor) { + ti->hooks.ctor(value->ptr, 1, ti); + } - if (node->is_collection != is_collection) { - char *id_str = ecs_id_str(v->world, ti->component); - if (node->is_collection && !is_collection) { - flecs_script_eval_error(v, node, - "type %s is not a collection (use '%s: {...}')", - id_str, id_str); - } else { - flecs_script_eval_error(v, node, - "type %s is a collection (use '%s: [...]')", - id_str, id_str); - } - ecs_os_free(id_str); - return -1; - } + if (flecs_script_eval_expr(v, node->expr, value)) { + return -1; } - - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, src, node->id.eval), - .type = ti->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); + value->type = node->id.eval; // Restore so we're adding actual id + } + + return 0; +} + +static +int flecs_script_eval_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + ecs_allocator_t *a = v->allocator; + int32_t prev_with_count = flecs_script_with_count(v); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); + int result = 0; + + if (ecs_script_visit_scope(v, node->expressions)) { + result = -1; + goto error; + } + + ecs_value_t *value = flecs_script_with_last(v); + if (!value->ptr) { + if (ecs_is_valid(v->world, value->type)) { + node->scope->default_component_eval = value->type; + } + } + + if (ecs_script_visit_scope(v, node->scope)) { + result = -1; + goto error; + } + +error: + flecs_script_with_set_count(a, v, prev_with_count); + flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); + return result; +} + +static +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + ecs_allocator_t *a = v->allocator; + int32_t len = ecs_os_strlen(node->name); + + if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { + char *path = flecs_strdup(a, node->name); + path[len - 2] = '\0'; + + ecs_entity_t from = ecs_lookup(v->world, path); + if (!from) { + flecs_script_eval_error(v, node, + "unresolved path '%s' in using statement", path); + flecs_strfree(a, path); + return -1; + } + + /* Add each child of the scope to using stack */ + ecs_iter_t it = ecs_children(v->world, from); + while (ecs_children_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_vec_append_t( + a, &v->using, ecs_entity_t)[0] = it.entities[i]; } - ti->hooks.ctor(value.ptr, 1, ti); } - if (ecs_os_strcmp(node->expr, "{}")) { - if (flecs_script_eval_expr(v, node->expr, &value)) { + flecs_strfree(a, path); + } else { + ecs_entity_t from = ecs_lookup_path_w_sep( + v->world, 0, node->name, NULL, NULL, false); + if (!from) { + from = ecs_entity(v->world, { + .name = node->name, + .root_sep = "" + }); + + if (!from) { return -1; } } - ecs_modified_id(v->world, src, node->id.eval); - } else { - ecs_add_id(v->world, src, node->id.eval); + ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; } return 0; } static -int flecs_script_eval_var_component( +int flecs_script_eval_module( ecs_script_eval_visitor_t *v, - ecs_script_var_component_t *node) + ecs_script_module_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); + ecs_entity_t m = flecs_script_create_entity(v, node->name); + if (!m) { return -1; } - if (v->template) { - return 0; + ecs_add_id(v->world, m, EcsModule); + + v->module = m; + v->parent = m; + + return 0; +} + +static +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); + if (!var) { + flecs_script_eval_error(v, node, + "variable '%s' redeclared", node->name); + return -1; } - ecs_id_t var_id = var->value.type; + if (node->type) { + ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + if (!type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for const variable '%s'", + node->type, node->name); + return -1; + } - if (var->value.ptr) { - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, var_id); + const ecs_type_info_t *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; } - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id), - .type = var_id - }; + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = type; + var->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + if (flecs_script_eval_expr(v, node->expr, &var->value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } + } else { + /* 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; + } + + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_value_copy_w_type_info(v->world, ti, value.ptr, var->value.ptr); + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = value.type; + var->type_info = ti; - ecs_modified_id(v->world, v->entity->eval, var_id); - } else { - ecs_add_id(v->world, v->entity->eval, var_id); + 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); } return 0; } static -int flecs_script_eval_default_component( +int flecs_script_eval_pair_scope( ecs_script_eval_visitor_t *v, - ecs_script_default_component_t *node) + ecs_script_pair_scope_t *node) { - if (!v->entity) { - flecs_script_eval_error(v, node, - "missing entity for default component"); - return -1; + ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); + if (!first) { + first = flecs_script_create_entity(v, node->id.first); + if (!first) { + return -1; + } } - if (v->template) { - return 0; + ecs_entity_t second = flecs_script_create_entity(v, node->id.second); + if (!second) { + return -1; } - ecs_script_scope_t *scope = ecs_script_current_scope(v); - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); - scope = scope->parent; + ecs_allocator_t *a = v->allocator; + ecs_entity_t prev_first = v->with_relationship; + ecs_entity_t prev_second = 0; + int32_t prev_with_relationship_sp = v->with_relationship_sp; - if (!scope) { - flecs_script_eval_error(v, node, - "entity '%s' is in root scope which cannot have a default type", - v->entity->name); - return -1; - } + v->with_relationship = first; - ecs_id_t default_type = scope->default_component_eval; - if (!default_type) { - flecs_script_eval_error(v, node, - "scope for entity '%s' does not have a default type", - v->entity->name); - return -1; + if (prev_first != first) { + /* Append new element to with stack */ + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = ecs_pair(first, second); + value->ptr = NULL; + v->with_relationship_sp = flecs_script_with_count(v) - 1; + } else { + /* Get existing with element for current relationhip stack */ + ecs_value_t *value = ecs_vec_get_t( + &v->with, ecs_value_t, v->with_relationship_sp); + ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, + ECS_INTERNAL_ERROR, NULL); + prev_second = ECS_PAIR_SECOND(value->type); + value->type = ecs_pair(first, second); + value->ptr = NULL; } - if (ecs_get_type_info(v->world, default_type) == NULL) { - char *id_str = ecs_id_str(v->world, default_type); - flecs_script_eval_error(v, node, - "cannot use tag '%s' as default type in assignment", - id_str); - ecs_os_free(id_str); + if (ecs_script_visit_scope(v, node->scope)) { return -1; } - ecs_value_t value = { - .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), - .type = default_type - }; - - if (flecs_script_eval_expr(v, node->expr, &value)) { - return -1; + if (prev_second) { + ecs_value_t *value = ecs_vec_get_t( + &v->with, ecs_value_t, v->with_relationship_sp); + value->type = ecs_pair(first, prev_second); + } else { + flecs_script_with_set_count(a, v, v->with_relationship_sp); } - ecs_modified_id(v->world, v->entity->eval, default_type); + v->with_relationship = prev_first; + v->with_relationship_sp = prev_with_relationship_sp; return 0; } static -int flecs_script_eval_with_var( +int flecs_script_eval_if( ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) + ecs_script_if_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); + ecs_value_t condval = { .type = 0, .ptr = NULL }; + if (flecs_script_eval_expr(v, node->expr, &condval)) { return -1; } - if (v->template) { - return 0; + bool cond; + if (condval.type == ecs_id(ecs_bool_t)) { + cond = *(bool*)(condval.ptr); + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor( + v->world, condval.type, condval.ptr); + cond = ecs_meta_get_bool(&cur); } - ecs_allocator_t *a = v->allocator; - ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types - *value = var->value; + ecs_value_free(v->world, condval.type, condval.ptr); + + if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { + return -1; + } return 0; } static -int flecs_script_eval_with_tag( +int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, - ecs_script_tag_t *node) + ecs_script_annot_t *node) { - if (flecs_script_eval_id(v, node, &node->id)) { + if (!v->base.next) { + flecs_script_eval_error(v, node, + "annotation '%s' is not applied to anything", node->name); return -1; } - if (v->template) { - return 0; + ecs_script_node_kind_t kind = v->base.next->kind; + if (kind != EcsAstEntity && kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "annotation must be applied to an entity"); + return -1; } ecs_allocator_t *a = v->allocator; - ecs_value_t *value = flecs_script_with_append(a, v, NULL); - value->type = node->id.eval; - value->ptr = NULL; + ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; return 0; } -static -int flecs_script_eval_with_component( +int flecs_script_eval_node( ecs_script_eval_visitor_t *v, - ecs_script_component_t *node) + ecs_script_node_t *node) { - if (flecs_script_eval_id(v, node, &node->id)) { - return -1; - } - - if (v->template) { + switch(node->kind) { + case EcsAstScope: + return flecs_script_eval_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_eval_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_eval_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_eval_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_eval_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_eval_with_var( + v, (ecs_script_var_node_t*)node); + case EcsAstWithTag: + return flecs_script_eval_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_eval_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_eval_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_eval_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return flecs_script_eval_module( + v, (ecs_script_module_t*)node); + case EcsAstAnnotation: + return flecs_script_eval_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return flecs_script_eval_template( + v, (ecs_script_template_node_t*)node); + case EcsAstProp: return 0; + case EcsAstConst: + return flecs_script_eval_const( + v, (ecs_script_var_node_t*)node); + case EcsAstEntity: + return flecs_script_eval_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_eval_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_eval_if( + v, (ecs_script_if_t*)node); } - ecs_allocator_t *a = v->allocator; - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, node->id.eval); + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} - ecs_value_t *value = flecs_script_with_append(a, v, ti); - value->type = node->id.eval; - value->ptr = NULL; +void flecs_script_eval_visit_init( + ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v) +{ + *v = (ecs_script_eval_visitor_t){ + .base = { + .script = script, + .visit = (ecs_visit_action_t)flecs_script_eval_node + }, + .world = script->pub.world, + .allocator = &script->allocator + }; - if (node->expr && node->expr[0]) { - if (!ti) { - return -1; - } + flecs_stack_init(&v->stack); + ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); + ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); + ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); + ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); + + /* Always include flecs.meta */ + ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = + ecs_lookup(v->world, "flecs.meta"); +} + +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v) +{ + ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); + ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); + ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); + ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); + flecs_stack_fini(&v->stack); +} + +int ecs_script_eval( + ecs_script_t *script) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl(script); + flecs_script_eval_visit_init(impl, &v); + int result = ecs_script_visit(impl, &v, flecs_script_eval_node); + flecs_script_eval_visit_fini(&v); + return result; +} - value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); - value->type = ti->component; // Expression parser needs actual type +#endif - if (ti->hooks.ctor) { - ti->hooks.ctor(value->ptr, 1, ti); - } +/** + * @file addons/script/visit_free.c + * @brief Script free visitor (frees AST resources). + */ - if (flecs_script_eval_expr(v, node->expr, value)) { - return -1; - } - value->type = node->id.eval; // Restore so we're adding actual id - } +#ifdef FLECS_SCRIPT - return 0; +static +void flecs_script_scope_free( + ecs_script_visit_t *v, + ecs_script_scope_t *node) +{ + ecs_script_visit_scope(v, node); + ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); + flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); } static -int flecs_script_eval_with( - ecs_script_eval_visitor_t *v, +void flecs_script_with_free( + ecs_script_visit_t *v, ecs_script_with_t *node) { - ecs_allocator_t *a = v->allocator; - int32_t prev_with_count = flecs_script_with_count(v); - ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); - int result = 0; - - if (ecs_script_visit_scope(v, node->expressions)) { - result = -1; - goto error; - } - - ecs_value_t *value = flecs_script_with_last(v); - if (!value->ptr) { - if (ecs_is_valid(v->world, value->type)) { - node->scope->default_component_eval = value->type; - } - } - - if (ecs_script_visit_scope(v, node->scope)) { - result = -1; - goto error; - } - -error: - flecs_script_with_set_count(a, v, prev_with_count); - flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); - return result; + flecs_script_scope_free(v, node->expressions); + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_using( - ecs_script_eval_visitor_t *v, - ecs_script_using_t *node) +void flecs_script_template_free( + ecs_script_visit_t *v, + ecs_script_template_node_t *node) { - ecs_allocator_t *a = v->allocator; - int32_t len = ecs_os_strlen(node->name); - - if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { - char *path = flecs_strdup(a, node->name); - path[len - 2] = '\0'; - - ecs_entity_t from = ecs_lookup(v->world, path); - if (!from) { - flecs_script_eval_error(v, node, - "unresolved path '%s' in using statement", path); - flecs_strfree(a, path); - return -1; - } - - /* Add each child of the scope to using stack */ - ecs_iter_t it = ecs_children(v->world, from); - while (ecs_children_next(&it)) { - int32_t i, count = it.count; - for (i = 0; i < count; i ++) { - ecs_vec_append_t( - a, &v->using, ecs_entity_t)[0] = it.entities[i]; - } - } - - flecs_strfree(a, path); - } else { - ecs_entity_t from = ecs_lookup_path_w_sep( - v->world, 0, node->name, NULL, NULL, false); - if (!from) { - from = ecs_entity(v->world, { - .name = node->name, - .root_sep = "" - }); - - if (!from) { - return -1; - } - } - - ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; - } - - return 0; + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_module( - ecs_script_eval_visitor_t *v, - ecs_script_module_t *node) +void flecs_script_entity_free( + ecs_script_visit_t *v, + ecs_script_entity_t *node) { - ecs_entity_t m = flecs_script_create_entity(v, node->name); - if (!m) { - return -1; - } - - ecs_add_id(v->world, m, EcsModule); - - v->module = m; - v->parent = m; - - return 0; + flecs_script_scope_free(v, node->scope); } static -int flecs_script_eval_const( - ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) +void flecs_script_pair_scope_free( + ecs_script_visit_t *v, + ecs_script_pair_scope_t *node) { - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "variable '%s' redeclared", node->name); - return -1; - } - - if (node->type) { - ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); - if (!type) { - flecs_script_eval_error(v, node, - "unresolved type '%s' for const variable '%s'", - node->type, node->name); - return -1; - } - - const ecs_type_info_t *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->stack, ti->size, ti->alignment); - var->value.type = type; - var->type_info = ti; - - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } + flecs_script_scope_free(v, node->scope); +} - if (flecs_script_eval_expr(v, node->expr, &var->value)) { - flecs_script_eval_error(v, node, - "failed to evaluate expression for const variable '%s'", - node->name); - return -1; - } - } else { - /* 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; - } +static +void flecs_script_if_free( + ecs_script_visit_t *v, + ecs_script_if_t *node) +{ + flecs_script_scope_free(v, node->if_true); + flecs_script_scope_free(v, node->if_false); +} - ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); +static +int flecs_script_stmt_free( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + ecs_allocator_t *a = &v->script->allocator; + switch(node->kind) { + case EcsAstScope: + flecs_script_scope_free(v, (ecs_script_scope_t*)node); + break; + case EcsAstWith: + flecs_script_with_free(v, (ecs_script_with_t*)node); + flecs_free_t(a, ecs_script_with_t, node); + break; + case EcsAstTemplate: + flecs_script_template_free(v, (ecs_script_template_node_t*)node); + flecs_free_t(a, ecs_script_template_node_t, node); + break; + case EcsAstEntity: + flecs_script_entity_free(v, (ecs_script_entity_t*)node); + flecs_free_t(a, ecs_script_entity_t, node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); + flecs_free_t(a, ecs_script_pair_scope_t, node); + break; + case EcsAstIf: + flecs_script_if_free(v, (ecs_script_if_t*)node); + flecs_free_t(a, ecs_script_if_t, node); + break; + case EcsAstTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstComponent: + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstDefaultComponent: + flecs_free_t(a, ecs_script_default_component_t, node); + break; + case EcsAstVarComponent: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithVar: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstWithComponent: + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstUsing: + flecs_free_t(a, ecs_script_using_t, node); + break; + case EcsAstModule: + flecs_free_t(a, ecs_script_module_t, node); + break; + case EcsAstAnnotation: + flecs_free_t(a, ecs_script_annot_t, node); + break; + case EcsAstProp: + case EcsAstConst: + flecs_free_t(a, ecs_script_var_node_t, node); + break; + } - var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); - var->value.type = value.type; - var->type_info = ti; + return 0; +} - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } +int flecs_script_visit_free( + ecs_script_t *script) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_visit_t v = { + .script = flecs_script_impl(script) + }; - 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); + if (ecs_script_visit( + flecs_script_impl(script), &v, flecs_script_stmt_free)) + { + goto error; } return 0; +error: + return - 1; } -static -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) { - first = flecs_script_create_entity(v, node->id.first); - if (!first) { - return -1; - } - } +#endif - ecs_entity_t second = flecs_script_create_entity(v, node->id.second); - if (!second) { - return -1; - } +/** + * @file addons/script/visit_to_str.c + * @brief Script AST to string visitor. + */ - ecs_allocator_t *a = v->allocator; - ecs_entity_t prev_first = v->with_relationship; - ecs_entity_t prev_second = 0; - int32_t prev_with_relationship_sp = v->with_relationship_sp; - v->with_relationship = first; +#ifdef FLECS_SCRIPT - if (prev_first != first) { - /* Append new element to with stack */ - ecs_value_t *value = flecs_script_with_append(a, v, NULL); - value->type = ecs_pair(first, second); - value->ptr = NULL; - v->with_relationship_sp = flecs_script_with_count(v) - 1; - } else { - /* Get existing with element for current relationhip stack */ - ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); - ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, - ECS_INTERNAL_ERROR, NULL); - prev_second = ECS_PAIR_SECOND(value->type); - value->type = ecs_pair(first, second); - value->ptr = NULL; - } +typedef struct ecs_script_str_visitor_t { + ecs_script_visit_t base; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_script_str_visitor_t; - if (ecs_script_visit_scope(v, node->scope)) { - return -1; - } +static +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope); - if (prev_second) { - ecs_value_t *value = ecs_vec_get_t( - &v->with, ecs_value_t, v->with_relationship_sp); - value->type = ecs_pair(first, prev_second); - } else { - flecs_script_with_set_count(a, v, v->with_relationship_sp); +static +void flecs_scriptbuf_append( + ecs_script_str_visitor_t *v, + const char *fmt, + ...) +{ + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; } - v->with_relationship = prev_first; - v->with_relationship_sp = prev_with_relationship_sp; + va_list args; + va_start(args, fmt); + ecs_strbuf_vappend(v->buf, fmt, args); + va_end(args); - return 0; + if (fmt[strlen(fmt) - 1] == '\n') { + v->newline = true; + } } static -int flecs_script_eval_if( - ecs_script_eval_visitor_t *v, - ecs_script_if_t *node) +void flecs_scriptbuf_appendstr( + ecs_script_str_visitor_t *v, + const char *str) { - ecs_value_t condval = { .type = 0, .ptr = NULL }; - if (flecs_script_eval_expr(v, node->expr, &condval)) { - return -1; - } - - bool cond; - if (condval.type == ecs_id(ecs_bool_t)) { - cond = *(bool*)(condval.ptr); - } else { - ecs_meta_cursor_t cur = ecs_meta_cursor( - v->world, condval.type, condval.ptr); - cond = ecs_meta_get_bool(&cur); + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; } - ecs_value_free(v->world, condval.type, condval.ptr); + ecs_strbuf_appendstr(v->buf, str); - if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { - return -1; + if (str[strlen(str) - 1] == '\n') { + v->newline = true; } - - return 0; } static -int flecs_script_eval_annot( - ecs_script_eval_visitor_t *v, - ecs_script_annot_t *node) +void flecs_script_id_to_str( + ecs_script_str_visitor_t *v, + ecs_script_id_t *id) { - if (!v->base.next) { - flecs_script_eval_error(v, node, - "annotation '%s' is not applied to anything", node->name); - return -1; + if (id->flag) { + if (id->flag == ECS_AUTO_OVERRIDE) { + flecs_scriptbuf_appendstr(v, "auto_override | "); + } else { + flecs_scriptbuf_appendstr(v, "??? | "); + } } - ecs_script_node_kind_t kind = v->base.next->kind; - if (kind != EcsAstEntity && kind != EcsAstAnnotation) { - flecs_script_eval_error(v, node, - "annotation must be applied to an entity"); - return -1; + if (id->second) { + flecs_scriptbuf_append(v, "(%s, %s)", + id->first, id->second); + } else { + flecs_scriptbuf_appendstr(v, id->first); } +} - ecs_allocator_t *a = v->allocator; - ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; - - return 0; +static +void flecs_script_expr_to_str( + ecs_script_str_visitor_t *v, + const char *expr) +{ + if (expr) { + flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); + } else { + flecs_scriptbuf_appendstr(v, "{}"); + } } -int flecs_script_eval_node( - ecs_script_eval_visitor_t *v, +static +const char* flecs_script_node_to_str( ecs_script_node_t *node) { switch(node->kind) { - case EcsAstScope: - return flecs_script_eval_scope( - v, (ecs_script_scope_t*)node); - case EcsAstTag: - return flecs_script_eval_tag( - v, (ecs_script_tag_t*)node); - case EcsAstComponent: - return flecs_script_eval_component( - v, (ecs_script_component_t*)node); - case EcsAstVarComponent: - return flecs_script_eval_var_component( - v, (ecs_script_var_component_t*)node); - case EcsAstDefaultComponent: - return flecs_script_eval_default_component( - v, (ecs_script_default_component_t*)node); - case EcsAstWithVar: - return flecs_script_eval_with_var( - v, (ecs_script_var_node_t*)node); + case EcsAstScope: return "scope"; case EcsAstWithTag: - return flecs_script_eval_with_tag( - v, (ecs_script_tag_t*)node); + case EcsAstTag: return "tag"; case EcsAstWithComponent: - return flecs_script_eval_with_component( - v, (ecs_script_component_t*)node); - case EcsAstWith: - return flecs_script_eval_with( - v, (ecs_script_with_t*)node); - case EcsAstUsing: - return flecs_script_eval_using( - v, (ecs_script_using_t*)node); - case EcsAstModule: - return flecs_script_eval_module( - v, (ecs_script_module_t*)node); - case EcsAstAnnotation: - return flecs_script_eval_annot( - v, (ecs_script_annot_t*)node); - case EcsAstTemplate: - return flecs_script_eval_template( - v, (ecs_script_template_node_t*)node); - case EcsAstProp: - return 0; - case EcsAstConst: - return flecs_script_eval_const( - v, (ecs_script_var_node_t*)node); - case EcsAstEntity: - return flecs_script_eval_entity( - v, (ecs_script_entity_t*)node); - case EcsAstPairScope: - return flecs_script_eval_pair_scope( - v, (ecs_script_pair_scope_t*)node); - case EcsAstIf: - return flecs_script_eval_if( - v, (ecs_script_if_t*)node); + case EcsAstComponent: return "component"; + case EcsAstWithVar: + case EcsAstVarComponent: return "var"; + case EcsAstDefaultComponent: return "default_component"; + case EcsAstWith: return "with"; + case EcsAstUsing: return "using"; + case EcsAstModule: return "module"; + case EcsAstAnnotation: return "annot"; + case EcsAstTemplate: return "template"; + case EcsAstProp: return "prop"; + case EcsAstConst: return "const"; + case EcsAstEntity: return "entity"; + case EcsAstPairScope: return "pair_scope"; + case EcsAstIf: return "if"; } + return "???"; +} - ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +static +void flecs_scriptbuf_node( + ecs_script_str_visitor_t *v, + ecs_script_node_t *node) +{ + flecs_scriptbuf_append(v, "%s%s%s: ", + ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); } -void flecs_script_eval_visit_init( - ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v) +static +void flecs_script_tag_to_str( + ecs_script_str_visitor_t *v, + ecs_script_tag_t *node) { - *v = (ecs_script_eval_visitor_t){ - .base = { - .script = script, - .visit = (ecs_visit_action_t)flecs_script_eval_node - }, - .world = script->pub.world, - .allocator = &script->allocator - }; - - flecs_stack_init(&v->stack); - ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); - ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); - ecs_vec_init_t(v->allocator, &v->with_type_info, ecs_type_info_t*, 0); - ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); - - /* Always include flecs.meta */ - ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = - ecs_lookup(v->world, "flecs.meta"); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, "\n"); } -void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v) +static +void flecs_script_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_component_t *node) { - ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); - ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); - ecs_vec_fini_t(v->allocator, &v->with_type_info, ecs_type_info_t*); - ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); - flecs_stack_fini(&v->stack); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + if (node->expr) { + flecs_scriptbuf_appendstr(v, ": "); + flecs_script_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); } -int ecs_script_eval( - ecs_script_t *script) +static +void flecs_script_default_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_default_component_t *node) { - ecs_script_eval_visitor_t v; - ecs_script_impl_t *impl = flecs_script_impl(script); - flecs_script_eval_visit_init(impl, &v); - int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v); - return result; + flecs_scriptbuf_node(v, &node->node); + if (node->expr) { + flecs_script_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); } -#endif - -/** - * @file addons/script/visit_free.c - * @brief Script free visitor (frees AST resources). - */ +static +void flecs_script_with_var_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_component_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_scriptbuf_appendstr(v, "\n"); +} +static +void flecs_script_with_to_str( + ecs_script_str_visitor_t *v, + ecs_script_with_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + + flecs_scriptbuf_appendstr(v, "{\n"); + v->depth ++; + flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->expressions); + flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} -#ifdef FLECS_SCRIPT +static +void flecs_script_using_to_str( + ecs_script_str_visitor_t *v, + ecs_script_using_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); +} static -void flecs_script_scope_free( - ecs_script_visit_t *v, - ecs_script_scope_t *node) +void flecs_script_module_to_str( + ecs_script_str_visitor_t *v, + ecs_script_module_t *node) { - ecs_script_visit_scope(v, node); - ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); - flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); } static -void flecs_script_with_free( - ecs_script_visit_t *v, - ecs_script_with_t *node) +void flecs_script_annot_to_str( + ecs_script_str_visitor_t *v, + ecs_script_annot_t *node) { - flecs_script_scope_free(v, node->expressions); - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, + ECS_GREEN, node->expr, ECS_NORMAL); + flecs_scriptbuf_appendstr(v, "\n"); } static -void flecs_script_template_free( - ecs_script_visit_t *v, +void flecs_script_template_to_str( + ecs_script_str_visitor_t *v, ecs_script_template_node_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_script_scope_to_str(v, node->scope); } static -void flecs_script_entity_free( - ecs_script_visit_t *v, +void flecs_script_var_node_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_node_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + if (node->type) { + flecs_scriptbuf_append(v, "%s : %s = ", + node->name, + node->type); + } else { + flecs_scriptbuf_append(v, "%s = ", + node->name); + } + flecs_script_expr_to_str(v, node->expr); + flecs_scriptbuf_appendstr(v, "\n"); +} + +static +void flecs_script_entity_to_str( + ecs_script_str_visitor_t *v, ecs_script_entity_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + if (node->kind) { + flecs_scriptbuf_append(v, "%s ", node->kind); + } + if (node->name) { + flecs_scriptbuf_append(v, "%s ", node->name); + } else { + flecs_scriptbuf_appendstr(v, " "); + } + + if (!flecs_scope_is_empty(node->scope)) { + flecs_script_scope_to_str(v, node->scope); + } else { + flecs_scriptbuf_appendstr(v, "\n"); + } } static -void flecs_script_pair_scope_free( - ecs_script_visit_t *v, +void flecs_script_pair_scope_to_str( + ecs_script_str_visitor_t *v, ecs_script_pair_scope_t *node) { - flecs_script_scope_free(v, node->scope); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, " "); + flecs_script_scope_to_str(v, node->scope); } static -void flecs_script_if_free( - ecs_script_visit_t *v, +void flecs_script_if_to_str( + ecs_script_str_visitor_t *v, ecs_script_if_t *node) { - flecs_script_scope_free(v, node->if_true); - flecs_script_scope_free(v, node->if_false); + flecs_scriptbuf_node(v, &node->node); + flecs_script_expr_to_str(v, node->expr); + + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_true); + flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_false); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); } static -int flecs_script_stmt_free( - ecs_script_visit_t *v, +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope) +{ + if (!ecs_vec_count(&scope->stmts)) { + flecs_scriptbuf_appendstr(v, "{}\n"); + return 0; + } + + flecs_scriptbuf_appendstr(v, "{\n"); + + v->depth ++; + + if (ecs_script_visit_scope(v, scope)) { + return -1; + } + + v->depth --; + + flecs_scriptbuf_appendstr(v, "}\n"); + + return 0; +} + +static +int flecs_script_stmt_to_str( + ecs_script_str_visitor_t *v, ecs_script_node_t *node) { - ecs_allocator_t *a = &v->script->allocator; switch(node->kind) { case EcsAstScope: - flecs_script_scope_free(v, (ecs_script_scope_t*)node); - break; - case EcsAstWith: - flecs_script_with_free(v, (ecs_script_with_t*)node); - flecs_free_t(a, ecs_script_with_t, node); - break; - case EcsAstTemplate: - flecs_script_template_free(v, (ecs_script_template_node_t*)node); - flecs_free_t(a, ecs_script_template_node_t, node); - break; - case EcsAstEntity: - flecs_script_entity_free(v, (ecs_script_entity_t*)node); - flecs_free_t(a, ecs_script_entity_t, node); - break; - case EcsAstPairScope: - flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); - flecs_free_t(a, ecs_script_pair_scope_t, node); - break; - case EcsAstIf: - flecs_script_if_free(v, (ecs_script_if_t*)node); - flecs_free_t(a, ecs_script_if_t, node); + if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { + return -1; + } break; case EcsAstTag: - flecs_free_t(a, ecs_script_tag_t, node); + case EcsAstWithTag: + flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); break; case EcsAstComponent: - flecs_free_t(a, ecs_script_component_t, node); - break; - case EcsAstDefaultComponent: - flecs_free_t(a, ecs_script_default_component_t, node); + case EcsAstWithComponent: + flecs_script_component_to_str(v, (ecs_script_component_t*)node); break; case EcsAstVarComponent: - flecs_free_t(a, ecs_script_var_component_t, node); - break; case EcsAstWithVar: - flecs_free_t(a, ecs_script_var_component_t, node); + flecs_script_with_var_to_str(v, + (ecs_script_var_component_t*)node); break; - case EcsAstWithTag: - flecs_free_t(a, ecs_script_tag_t, node); + case EcsAstDefaultComponent: + flecs_script_default_component_to_str(v, + (ecs_script_default_component_t*)node); break; - case EcsAstWithComponent: - flecs_free_t(a, ecs_script_component_t, node); + case EcsAstWith: + flecs_script_with_to_str(v, (ecs_script_with_t*)node); break; case EcsAstUsing: - flecs_free_t(a, ecs_script_using_t, node); + flecs_script_using_to_str(v, (ecs_script_using_t*)node); break; case EcsAstModule: - flecs_free_t(a, ecs_script_module_t, node); + flecs_script_module_to_str(v, (ecs_script_module_t*)node); break; case EcsAstAnnotation: - flecs_free_t(a, ecs_script_annot_t, node); + flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); + break; + case EcsAstTemplate: + flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); break; - case EcsAstProp: case EcsAstConst: - flecs_free_t(a, ecs_script_var_node_t, node); + case EcsAstProp: + flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); + break; + case EcsAstEntity: + flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); + break; + case EcsAstIf: + flecs_script_if_to_str(v, (ecs_script_if_t*)node); break; } return 0; } -int flecs_script_visit_free( - ecs_script_t *script) +int ecs_script_ast_to_buf( + ecs_script_t *script, + ecs_strbuf_t *buf) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_visit_t v = { - .script = flecs_script_impl(script) - }; - - if (ecs_script_visit( - flecs_script_impl(script), &v, flecs_script_stmt_free)) - { + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = buf }; + if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { goto error; } return 0; error: + ecs_strbuf_reset(buf); return - 1; } +char* ecs_script_ast_to_str( + ecs_script_t *script) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + if (ecs_script_ast_to_buf(script, &buf)) { + goto error; + } + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + #endif /** - * @file addons/script/visit_to_str.c - * @brief Script AST to string visitor. + * @file addons/monitor.c + * @brief Stats addon module. */ +/** + * @file addons/stats/stats.h + * @brief Internal functions/types for stats addon. + */ -#ifdef FLECS_SCRIPT +#ifndef FLECS_STATS_PRIVATE_H +#define FLECS_STATS_PRIVATE_H -typedef struct ecs_script_str_visitor_t { - ecs_script_visit_t base; - ecs_strbuf_t *buf; - int32_t depth; - bool newline; -} ecs_script_str_visitor_t; -static -int flecs_script_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_scope_t *scope); +typedef struct { + /* Statistics API interface */ + void (*copy_last)(void *stats, void *src); + void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); + void (*reduce)(void *stats, void *src); + void (*reduce_last)(void *stats, void *last, int32_t reduce_count); + void (*repeat_last)(void* stats); + void (*set_t)(void *stats, int32_t t); + void (*fini)(void *stats); -static -void flecs_scriptbuf_append( - ecs_script_str_visitor_t *v, - const char *fmt, - ...) -{ - if (v->newline) { - ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); - v->newline = false; - } + /* Size of statistics type */ + ecs_size_t stats_size; - va_list args; - va_start(args, fmt); - ecs_strbuf_vappend(v->buf, fmt, args); - va_end(args); + /* Id of component that contains the statistics */ + ecs_entity_t monitor_component_id; - if (fmt[strlen(fmt) - 1] == '\n') { - v->newline = true; - } -} + /* Id of component used to query for monitored resources (optional) */ + ecs_id_t query_component_id; +} ecs_stats_api_t; + +void flecs_stats_api_import( + ecs_world_t *world, + ecs_stats_api_t *api); + +void FlecsWorldSummaryImport( + ecs_world_t *world); + +void FlecsWorldMonitorImport( + ecs_world_t *world); + +void FlecsSystemMonitorImport( + ecs_world_t *world); + +void FlecsPipelineMonitorImport( + ecs_world_t *world); + +#endif + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(FlecsStats); + +ecs_entity_t EcsPeriod1s = 0; +ecs_entity_t EcsPeriod1m = 0; +ecs_entity_t EcsPeriod1h = 0; +ecs_entity_t EcsPeriod1d = 0; +ecs_entity_t EcsPeriod1w = 0; + +#define FlecsDayIntervalCount (24) +#define FlecsWeekIntervalCount (168) + +typedef struct { + ecs_stats_api_t api; + ecs_query_t *query; +} ecs_monitor_stats_ctx_t; + +typedef struct { + ecs_stats_api_t api; +} ecs_reduce_stats_ctx_t; + +typedef struct { + ecs_stats_api_t api; + int32_t interval; +} ecs_aggregate_stats_ctx_t; static -void flecs_scriptbuf_appendstr( - ecs_script_str_visitor_t *v, - const char *str) -{ - if (v->newline) { - ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); - v->newline = false; +void MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + ecs_monitor_stats_ctx_t *ctx = it->ctx; + + EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 0); + + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(hdr->elapsed * 60); + int32_t i, dif = t_next - t_last; + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + if (!dif) { + hdr->reduce_count ++; } - ecs_strbuf_appendstr(v->buf, str); + ecs_iter_t qit; + int32_t cur = -1, count = 0; + void *stats = NULL; + ecs_map_t *stats_map = NULL; - if (str[strlen(str) - 1] == '\n') { - v->newline = true; + if (ctx->query) { + /* Query results are stored in a map */ + qit = ecs_query_iter(it->world, ctx->query); + stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); + } else { + /* No query, so tracking stats for single element */ + stats = ECS_OFFSET_T(hdr, EcsStatsHeader); } -} -static -void flecs_script_id_to_str( - ecs_script_str_visitor_t *v, - ecs_script_id_t *id) -{ - if (id->flag) { - if (id->flag == ECS_AUTO_OVERRIDE) { - flecs_scriptbuf_appendstr(v, "auto_override | "); - } else { - flecs_scriptbuf_appendstr(v, "??? | "); + do { + ecs_entity_t res = 0; + if (ctx->query) { + /* Query, fetch resource entity & stats pointer */ + if (cur == (count - 1)) { + if (!ecs_query_next(&qit)) { + break; + } + + cur = 0; + count = qit.count; + if (!count) { + cur = -1; + continue; + } + } else { + cur ++; + } + + res = qit.entities[cur]; + stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); + ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); + } + + if (!dif) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, stats); + } + + ctx->api.get(world, res, stats); + + if (!dif) { + /* Still in same interval, combine with last measurement */ + ctx->api.reduce_last(stats, last, hdr->reduce_count); + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + ctx->api.repeat_last(stats); + } + } + + if (last && ctx->api.fini) { + ctx->api.fini(last); } - } - if (id->second) { - flecs_scriptbuf_append(v, "(%s, %s)", - id->first, id->second); - } else { - flecs_scriptbuf_appendstr(v, id->first); - } -} + if (!ctx->query) { + break; + } + } while (true); -static -void flecs_script_expr_to_str( - ecs_script_str_visitor_t *v, - const char *expr) -{ - if (expr) { - flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); - } else { - flecs_scriptbuf_appendstr(v, "{}"); + if (dif > 1) { + hdr->reduce_count = 0; } } static -const char* flecs_script_node_to_str( - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstScope: return "scope"; - case EcsAstWithTag: - case EcsAstTag: return "tag"; - case EcsAstWithComponent: - case EcsAstComponent: return "component"; - case EcsAstWithVar: - case EcsAstVarComponent: return "var"; - case EcsAstDefaultComponent: return "default_component"; - case EcsAstWith: return "with"; - case EcsAstUsing: return "using"; - case EcsAstModule: return "module"; - case EcsAstAnnotation: return "annot"; - case EcsAstTemplate: return "template"; - case EcsAstProp: return "prop"; - case EcsAstConst: return "const"; - case EcsAstEntity: return "entity"; - case EcsAstPairScope: return "pair_scope"; - case EcsAstIf: return "if"; +void ReduceStats(ecs_iter_t *it) { + ecs_reduce_stats_ctx_t *ctx = it->ctx; + + void *dst = ecs_field_w_size(it, 0, 0); + void *src = ecs_field_w_size(it, 0, 1); + + dst = ECS_OFFSET_T(dst, EcsStatsHeader); + src = ECS_OFFSET_T(src, EcsStatsHeader); + + if (!ctx->api.query_component_id) { + ctx->api.reduce(dst, src); + } else { + ecs_map_iter_t mit = ecs_map_iter(src); + while (ecs_map_next(&mit)) { + void *src_el = ecs_map_ptr(&mit); + void *dst_el = ecs_map_ensure_alloc( + dst, ctx->api.stats_size, ecs_map_key(&mit)); + ctx->api.reduce(dst_el, src_el); + } } - return "???"; } static -void flecs_scriptbuf_node( - ecs_script_str_visitor_t *v, - ecs_script_node_t *node) -{ - flecs_scriptbuf_append(v, "%s%s%s: ", - ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); -} +void AggregateStats(ecs_iter_t *it) { + ecs_aggregate_stats_ctx_t *ctx = it->ctx; + int32_t interval = ctx->interval; -static -void flecs_script_tag_to_str( - ecs_script_str_visitor_t *v, - ecs_script_tag_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - flecs_scriptbuf_appendstr(v, "\n"); -} + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 1); -static -void flecs_script_component_to_str( - ecs_script_str_visitor_t *v, - ecs_script_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - if (node->expr) { - flecs_scriptbuf_appendstr(v, ": "); - flecs_script_expr_to_str(v, node->expr); + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); + void *dst_map = NULL; + void *src_map = NULL; + if (ctx->api.query_component_id) { + dst_map = dst; + src_map = src; + dst = NULL; + src = NULL; } - flecs_scriptbuf_appendstr(v, "\n"); -} -static -void flecs_script_default_component_to_str( - ecs_script_str_visitor_t *v, - ecs_script_default_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->expr) { - flecs_script_expr_to_str(v, node->expr); + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + ecs_map_iter_t mit; + if (src_map) { + mit = ecs_map_iter(src_map); } - flecs_scriptbuf_appendstr(v, "\n"); -} -static -void flecs_script_with_var_to_str( - ecs_script_str_visitor_t *v, - ecs_script_var_component_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s ", node->name); - flecs_scriptbuf_appendstr(v, "\n"); -} + do { + if (src_map) { + if (!ecs_map_next(&mit)) { + break; + } -static -void flecs_script_with_to_str( - ecs_script_str_visitor_t *v, - ecs_script_with_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - - flecs_scriptbuf_appendstr(v, "{\n"); - v->depth ++; - flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->expressions); - flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->scope); - v->depth --; - flecs_scriptbuf_appendstr(v, "}\n"); -} + src = ecs_map_ptr(&mit); + dst = ecs_map_ensure_alloc( + dst_map, ctx->api.stats_size, ecs_map_key(&mit)); + } -static -void flecs_script_using_to_str( - ecs_script_str_visitor_t *v, - ecs_script_using_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s\n", node->name); -} + if (dst_hdr->reduce_count != 0) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, dst); + } -static -void flecs_script_module_to_str( - ecs_script_str_visitor_t *v, - ecs_script_module_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s\n", node->name); -} + /* Reduce from minutes to the current day */ + ctx->api.reduce(dst, src); -static -void flecs_script_annot_to_str( - ecs_script_str_visitor_t *v, - ecs_script_annot_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, - ECS_GREEN, node->expr, ECS_NORMAL); - flecs_scriptbuf_appendstr(v, "\n"); -} + if (dst_hdr->reduce_count != 0) { + ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); + } -static -void flecs_script_template_to_str( - ecs_script_str_visitor_t *v, - ecs_script_template_node_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_append(v, "%s ", node->name); - flecs_script_scope_to_str(v, node->scope); -} + if (last && ctx->api.fini != NULL) { + ctx->api.fini(last); + } -static -void flecs_script_var_node_to_str( - ecs_script_str_visitor_t *v, - ecs_script_var_node_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->type) { - flecs_scriptbuf_append(v, "%s : %s = ", - node->name, - node->type); - } else { - flecs_scriptbuf_append(v, "%s = ", - node->name); - } - flecs_script_expr_to_str(v, node->expr); - flecs_scriptbuf_appendstr(v, "\n"); -} + if (!src_map) { + break; + } + } while (true); -static -void flecs_script_entity_to_str( - ecs_script_str_visitor_t *v, - ecs_script_entity_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - if (node->kind) { - flecs_scriptbuf_append(v, "%s ", node->kind); - } - if (node->name) { - flecs_scriptbuf_append(v, "%s ", node->name); - } else { - flecs_scriptbuf_appendstr(v, " "); + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 0; } +} - if (!flecs_scope_is_empty(node->scope)) { - flecs_script_scope_to_str(v, node->scope); - } else { - flecs_scriptbuf_appendstr(v, "\n"); +static +void flecs_monitor_ctx_free( + void *ptr) +{ + ecs_monitor_stats_ctx_t *ctx = ptr; + if (ctx->query) { + ecs_query_fini(ctx->query); } + ecs_os_free(ctx); } static -void flecs_script_pair_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_pair_scope_t *node) +void flecs_reduce_ctx_free( + void *ptr) { - flecs_scriptbuf_node(v, &node->node); - flecs_script_id_to_str(v, &node->id); - flecs_scriptbuf_appendstr(v, " "); - flecs_script_scope_to_str(v, node->scope); + ecs_os_free(ptr); } static -void flecs_script_if_to_str( - ecs_script_str_visitor_t *v, - ecs_script_if_t *node) +void flecs_aggregate_ctx_free( + void *ptr) { - flecs_scriptbuf_node(v, &node->node); - flecs_script_expr_to_str(v, node->expr); - - flecs_scriptbuf_appendstr(v, " {\n"); - v->depth ++; - flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->if_true); - flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); - flecs_script_scope_to_str(v, node->if_false); - v->depth --; - flecs_scriptbuf_appendstr(v, "}\n"); + ecs_os_free(ptr); } -static -int flecs_script_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_scope_t *scope) +void flecs_stats_api_import( + ecs_world_t *world, + ecs_stats_api_t *api) { - if (!ecs_vec_count(&scope->stmts)) { - flecs_scriptbuf_appendstr(v, "{}\n"); - return 0; + ecs_entity_t kind = api->monitor_component_id; + ecs_entity_t prev = ecs_set_scope(world, kind); + + ecs_query_t *q = NULL; + if (api->query_component_id) { + q = ecs_query(world, { + .terms = {{ .id = api->query_component_id }}, + .cache_kind = EcsQueryCacheNone, + .flags = EcsQueryMatchDisabled + }); } - flecs_scriptbuf_appendstr(v, "{\n"); + // Called each frame, collects 60 measurements per second + { + ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); + ctx->api = *api; + ctx->query = q; - v->depth ++; + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = MonitorStats, + .ctx = ctx, + .ctx_free = flecs_monitor_ctx_free + }); + } - if (ecs_script_visit_scope(v, scope)) { - return -1; + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m; + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; + + mw1m = ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .interval = 1.0, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); } - v->depth --; + // Called each minute, reduces into 60 measurements per hour + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; - flecs_scriptbuf_appendstr(v, "}\n"); + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); + } - return 0; -} + // Called each minute, reduces into 60 measurements per day + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsDayIntervalCount; -static -int flecs_script_stmt_to_str( - ecs_script_str_visitor_t *v, - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstScope: - if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { - return -1; - } - break; - case EcsAstTag: - case EcsAstWithTag: - flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); - break; - case EcsAstComponent: - case EcsAstWithComponent: - flecs_script_component_to_str(v, (ecs_script_component_t*)node); - break; - case EcsAstVarComponent: - case EcsAstWithVar: - flecs_script_with_var_to_str(v, - (ecs_script_var_component_t*)node); - break; - case EcsAstDefaultComponent: - flecs_script_default_component_to_str(v, - (ecs_script_default_component_t*)node); - break; - case EcsAstWith: - flecs_script_with_to_str(v, (ecs_script_with_t*)node); - break; - case EcsAstUsing: - flecs_script_using_to_str(v, (ecs_script_using_t*)node); - break; - case EcsAstModule: - flecs_script_module_to_str(v, (ecs_script_module_t*)node); - break; - case EcsAstAnnotation: - flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); - break; - case EcsAstTemplate: - flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); - break; - case EcsAstConst: - case EcsAstProp: - flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); - break; - case EcsAstEntity: - flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); - break; - case EcsAstPairScope: - flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); - break; - case EcsAstIf: - flecs_script_if_to_str(v, (ecs_script_if_t*)node); - break; + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1d), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); } - return 0; + // Called each hour, reduces into 60 measurements per week + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsWeekIntervalCount; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1w), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); + } + + ecs_set_scope(world, prev); + + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); } -int ecs_script_ast_to_buf( - ecs_script_t *script, - ecs_strbuf_t *buf) +void FlecsStatsImport( + ecs_world_t *world) { - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_str_visitor_t v = { .buf = buf }; - if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { - goto error; - } + ECS_MODULE_DEFINE(world, FlecsStats); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_UNITS + ECS_IMPORT(world, FlecsUnits); +#endif +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsStats), + "Module that automatically monitors statistics for the world & systems"); +#endif - return 0; -error: - ecs_strbuf_reset(buf); - return - 1; -} + ecs_set_name_prefix(world, "Ecs"); -char* ecs_script_ast_to_str( - ecs_script_t *script) -{ - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (ecs_script_ast_to_buf(script, &buf)) { - goto error; - } + EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); + EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); + EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); + EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); + EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); - return ecs_strbuf_get(&buf); -error: - return NULL; + FlecsWorldSummaryImport(world); + FlecsWorldMonitorImport(world); + FlecsSystemMonitorImport(world); + FlecsPipelineMonitorImport(world); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); + } } #endif /** - * @file addons/monitor.c - * @brief Stats addon module. - */ - -/** - * @file addons/stats/stats.h - * @brief Internal functions/types for stats addon. + * @file addons/stats/pipeline_monitor.c + * @brief Stats addon pipeline monitor */ -#ifndef FLECS_STATS_PRIVATE_H -#define FLECS_STATS_PRIVATE_H - - -typedef struct { - /* Statistics API interface */ - void (*copy_last)(void *stats, void *src); - void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); - void (*reduce)(void *stats, void *src); - void (*reduce_last)(void *stats, void *last, int32_t reduce_count); - void (*repeat_last)(void* stats); - void (*set_t)(void *stats, int32_t t); - void (*fini)(void *stats); - - /* Size of statistics type */ - ecs_size_t stats_size; - - /* Id of component that contains the statistics */ - ecs_entity_t monitor_component_id; - - /* Id of component used to query for monitored resources (optional) */ - ecs_id_t query_component_id; -} ecs_stats_api_t; -void flecs_stats_api_import( - ecs_world_t *world, - ecs_stats_api_t *api); +#ifdef FLECS_STATS -void FlecsWorldSummaryImport( - ecs_world_t *world); +ECS_COMPONENT_DECLARE(EcsPipelineStats); -void FlecsWorldMonitorImport( - ecs_world_t *world); +static +void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); + ecs_pipeline_stats_fini(stats); + ecs_os_free(stats); + } + ecs_map_fini(&ptr->stats); +} -void FlecsSystemMonitorImport( - ecs_world_t *world); +static ECS_CTOR(EcsPipelineStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) -void FlecsPipelineMonitorImport( - ecs_world_t *world); +static ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) -#endif +static ECS_MOVE(EcsPipelineStats, dst, src, { + flecs_pipeline_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) +static ECS_DTOR(EcsPipelineStats, ptr, { + flecs_pipeline_monitor_dtor(ptr); +}) -#ifdef FLECS_STATS +static +void flecs_pipeline_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_pipeline_stats_t*)stats)->t = t; +} -ECS_COMPONENT_DECLARE(FlecsStats); -ecs_entity_t EcsPeriod1s = 0; -ecs_entity_t EcsPeriod1m = 0; -ecs_entity_t EcsPeriod1h = 0; -ecs_entity_t EcsPeriod1d = 0; -ecs_entity_t EcsPeriod1w = 0; +static +void flecs_pipeline_stats_copy_last( + void *stats, + void *src) +{ + ecs_pipeline_stats_copy_last(stats, src); +} -#define FlecsDayIntervalCount (24) -#define FlecsWeekIntervalCount (168) +static +void flecs_pipeline_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) +{ + ecs_pipeline_stats_get(world, res, stats); +} -typedef struct { - ecs_stats_api_t api; - ecs_query_t *query; -} ecs_monitor_stats_ctx_t; +static +void flecs_pipeline_stats_reduce( + void *stats, + void *src) +{ + ecs_pipeline_stats_reduce(stats, src); +} -typedef struct { - ecs_stats_api_t api; -} ecs_reduce_stats_ctx_t; +static +void flecs_pipeline_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) +{ + ecs_pipeline_stats_reduce_last(stats, last, reduce_count); +} -typedef struct { - ecs_stats_api_t api; - int32_t interval; -} ecs_aggregate_stats_ctx_t; +static +void flecs_pipeline_stats_repeat_last( + void* stats) +{ + ecs_pipeline_stats_repeat_last(stats); +} static -void MonitorStats(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; - ecs_monitor_stats_ctx_t *ctx = it->ctx; +void flecs_pipeline_stats_fini( + void *stats) +{ + ecs_pipeline_stats_fini(stats); +} - EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 0); +void FlecsPipelineMonitorImport( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); - ecs_ftime_t elapsed = hdr->elapsed; - hdr->elapsed += it->delta_time; + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_ctor(EcsPipelineStats), + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); - int32_t t_last = (int32_t)(elapsed * 60); - int32_t t_next = (int32_t)(hdr->elapsed * 60); - int32_t i, dif = t_next - t_last; - void *stats_storage = ecs_os_alloca(ctx->api.stats_size); - void *last = NULL; + ecs_stats_api_t api = { + .copy_last = flecs_pipeline_stats_copy_last, + .get = flecs_pipeline_stats_get, + .reduce = flecs_pipeline_stats_reduce, + .reduce_last = flecs_pipeline_stats_reduce_last, + .repeat_last = flecs_pipeline_stats_repeat_last, + .set_t = flecs_pipeline_stats_set_t, + .fini = flecs_pipeline_stats_fini, + .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), + .monitor_component_id = ecs_id(EcsPipelineStats), + .query_component_id = ecs_id(EcsPipeline) + }; - if (!dif) { - hdr->reduce_count ++; - } + flecs_stats_api_import(world, &api); +} - ecs_iter_t qit; - int32_t cur = -1, count = 0; - void *stats = NULL; - ecs_map_t *stats_map = NULL; +#endif - if (ctx->query) { - /* Query results are stored in a map */ - qit = ecs_query_iter(it->world, ctx->query); - stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); - } else { - /* No query, so tracking stats for single element */ - stats = ECS_OFFSET_T(hdr, EcsStatsHeader); - } +/** + * @file addons/stats.c + * @brief Stats addon. + */ - do { - ecs_entity_t res = 0; - if (ctx->query) { - /* Query, fetch resource entity & stats pointer */ - if (cur == (count - 1)) { - if (!ecs_query_next(&qit)) { - break; - } - cur = 0; - count = qit.count; - if (!count) { - cur = -1; - continue; - } - } else { - cur ++; - } - res = qit.entities[cur]; - stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); - ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); - } +#ifdef FLECS_STATS - if (!dif) { - /* Copy last value so we can pass it to reduce_last */ - last = stats_storage; - ecs_os_memset(last, 0, ctx->api.stats_size); - ctx->api.copy_last(last, stats); - } +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (ecs_float_t)(value)) - ctx->api.get(world, res, stats); +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (double)(value)) - if (!dif) { - /* Still in same interval, combine with last measurement */ - ctx->api.reduce_last(stats, last, hdr->reduce_count); - } else if (dif > 1) { - /* More than 16ms has passed, backfill */ - for (i = 1; i < dif; i ++) { - ctx->api.repeat_last(stats); - } - } +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) - if (last && ctx->api.fini) { - ctx->api.fini(last); - } +#define ECS_METRIC_LAST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) - if (!ctx->query) { - break; - } - } while (true); +static +int32_t t_next( + int32_t t) +{ + return (t + 1) % ECS_STAT_WINDOW; +} - if (dif > 1) { - hdr->reduce_count = 0; - } +static +int32_t t_prev( + int32_t t) +{ + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static -void ReduceStats(ecs_iter_t *it) { - ecs_reduce_stats_ctx_t *ctx = it->ctx; +void flecs_gauge_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) +{ + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; +} - void *dst = ecs_field_w_size(it, 0, 0); - void *src = ecs_field_w_size(it, 0, 1); +static +double flecs_counter_record( + ecs_metric_t *m, + int32_t t, + double value) +{ + int32_t tp = t_prev(t); + double prev = m->counter.value[tp]; + m->counter.value[t] = value; + double gauge_value = value - prev; + if (gauge_value < 0) { + gauge_value = 0; /* Counters are monotonically increasing */ + } + flecs_gauge_record(m, t, (ecs_float_t)gauge_value); + return gauge_value; +} - dst = ECS_OFFSET_T(dst, EcsStatsHeader); - src = ECS_OFFSET_T(src, EcsStatsHeader); +static +void flecs_metric_print( + const char *name, + ecs_float_t value) +{ + ecs_size_t len = ecs_os_strlen(name); + ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); +} - if (!ctx->api.query_component_id) { - ctx->api.reduce(dst, src); - } else { - ecs_map_iter_t mit = ecs_map_iter(src); - while (ecs_map_next(&mit)) { - void *src_el = ecs_map_ptr(&mit); - void *dst_el = ecs_map_ensure_alloc( - dst, ctx->api.stats_size, ecs_map_key(&mit)); - ctx->api.reduce(dst_el, src_el); - } - } +static +void flecs_gauge_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->gauge.avg[t]); } static -void AggregateStats(ecs_iter_t *it) { - ecs_aggregate_stats_ctx_t *ctx = it->ctx; - int32_t interval = ctx->interval; +void flecs_counter_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->counter.rate.avg[t]); +} - EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); - EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 1); +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src) +{ + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); - void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); - void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); - void *dst_map = NULL; - void *src_map = NULL; - if (ctx->api.query_component_id) { - dst_map = dst; - src_map = src; - dst = NULL; - src = NULL; + bool min_set = false; + dst->gauge.avg[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.max[t_dst] = 0; + + ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; + + if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { + dst->gauge.min[t_dst] = src->gauge.min[t]; + min_set = true; + } + if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { + dst->gauge.max[t_dst] = src->gauge.max[t]; + } } - void *stats_storage = ecs_os_alloca(ctx->api.stats_size); - void *last = NULL; + dst->counter.value[t_dst] = src->counter.value[t_src]; - ecs_map_iter_t mit; - if (src_map) { - mit = ecs_map_iter(src_map); +error: + return; +} + +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t prev, + int32_t count) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = t_next(prev); + + if (m->gauge.min[t] < m->gauge.min[prev]) { + m->gauge.min[prev] = m->gauge.min[t]; } - do { - if (src_map) { - if (!ecs_map_next(&mit)) { - break; - } + if (m->gauge.max[t] > m->gauge.max[prev]) { + m->gauge.max[prev] = m->gauge.max[t]; + } - src = ecs_map_ptr(&mit); - dst = ecs_map_ensure_alloc( - dst_map, ctx->api.stats_size, ecs_map_key(&mit)); - } + ecs_float_t fcount = (ecs_float_t)(count + 1); + ecs_float_t cur = m->gauge.avg[prev]; + ecs_float_t next = m->gauge.avg[t]; - if (dst_hdr->reduce_count != 0) { - /* Copy last value so we can pass it to reduce_last */ - last = stats_storage; - ecs_os_memset(last, 0, ctx->api.stats_size); - ctx->api.copy_last(last, dst); - } + cur *= ((fcount - 1) / fcount); + next *= 1 / fcount; - /* Reduce from minutes to the current day */ - ctx->api.reduce(dst, src); + m->gauge.avg[prev] = cur + next; + m->counter.value[prev] = m->counter.value[t]; - if (dst_hdr->reduce_count != 0) { - ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); - } +error: + return; +} - if (last && ctx->api.fini != NULL) { - ctx->api.fini(last); - } +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - if (!src_map) { - break; - } - } while (true); + m->gauge.avg[dst] = m->gauge.avg[src]; + m->gauge.min[dst] = m->gauge.min[src]; + m->gauge.max[dst] = m->gauge.max[src]; + m->counter.value[dst] = m->counter.value[src]; - /* A day has 60 24 minute intervals */ - dst_hdr->reduce_count ++; - if (dst_hdr->reduce_count >= interval) { - dst_hdr->reduce_count = 0; - } +error: + return; } static -void flecs_monitor_ctx_free( - void *ptr) +void flecs_stats_reduce( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) { - ecs_monitor_stats_ctx_t *ctx = ptr; - if (ctx->query) { - ecs_query_fini(ctx->query); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } - ecs_os_free(ctx); } static -void flecs_reduce_ctx_free( - void *ptr) +void flecs_stats_reduce_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src, + int32_t count) { - ecs_os_free(ptr); + int32_t t_dst_next = t_next(t_dst); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + /* Reduce into previous value */ + ecs_metric_reduce_last(dst_cur, t_dst, count); + + /* Restore old value */ + dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; + } } static -void flecs_aggregate_ctx_free( - void *ptr) +void flecs_stats_repeat_last( + ecs_metric_t *cur, + ecs_metric_t *last, + int32_t t) { - ecs_os_free(ptr); + int32_t prev = t_prev(t); + for (; cur <= last; cur ++) { + ecs_metric_copy(cur, t, prev); + } } -void flecs_stats_api_import( - ecs_world_t *world, - ecs_stats_api_t *api) +static +void flecs_stats_copy_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) { - ecs_entity_t kind = api->monitor_component_id; - ecs_entity_t prev = ecs_set_scope(world, kind); - - ecs_query_t *q = NULL; - if (api->query_component_id) { - q = ecs_query(world, { - .terms = {{ .id = api->query_component_id }}, - .cache_kind = EcsQueryCacheNone, - .flags = EcsQueryMatchDisabled - }); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } +} - // Called each frame, collects 60 measurements per second - { - ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); - ctx->api = *api; - ctx->query = q; - - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = MonitorStats, - .ctx = ctx, - .ctx_free = flecs_monitor_ctx_free - }); - } +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - // Called each second, reduces into 60 measurements per minute - ecs_entity_t mw1m; - { - ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); - ctx->api = *api; + world = ecs_get_world(world); - mw1m = ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .interval = 1.0, - .ctx = ctx, - .ctx_free = flecs_reduce_ctx_free - }); - } + int32_t t = s->t = t_next(s->t); - // Called each minute, reduces into 60 measurements per hour - { - ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); - ctx->api = *api; + double delta_frame_count = + ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); + ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); + ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); + ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_reduce_ctx_free - }); + double delta_world_time = + ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); + ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); + ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); + ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); + if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { + ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); + } else { + ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } - // Called each minute, reduces into 60 measurements per day - { - ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); - ctx->api = *api; - ctx->interval = FlecsDayIntervalCount; - - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1d), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_aggregate_ctx_free - }); - } + ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); + ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); - // Called each hour, reduces into 60 measurements per week - { - ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); - ctx->api = *api; - ctx->interval = FlecsWeekIntervalCount; + ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); + ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); - ecs_system(world, { - .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), - .query.terms = {{ - .id = ecs_pair(kind, EcsPeriod1w), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = ctx, - .ctx_free = flecs_aggregate_ctx_free - }); + ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); + ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); + if (ecs_is_alive(world, EcsSystem)) { + ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } + 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_set_scope(world, prev); - - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); - ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); -} - -void FlecsStatsImport( - ecs_world_t *world) -{ - ECS_MODULE_DEFINE(world, FlecsStats); - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsTimer); -#ifdef FLECS_META - ECS_IMPORT(world, FlecsMeta); -#endif -#ifdef FLECS_UNITS - ECS_IMPORT(world, FlecsUnits); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsStats), - "Module that automatically monitors statistics for the world & systems"); -#endif + 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); + ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); + ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); + ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); + ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); + ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); + ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); + ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); + ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); + ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); - ecs_set_name_prefix(world, "Ecs"); + int64_t outstanding_allocs = ecs_os_api_malloc_count + + ecs_os_api_calloc_count - ecs_os_api_free_count; + ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); + ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); + ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); + ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); - EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); - EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); - EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); - EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); - EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); + outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); - FlecsWorldSummaryImport(world); - FlecsWorldMonitorImport(world); - FlecsSystemMonitorImport(world); - FlecsPipelineMonitorImport(world); - - if (ecs_os_has_time()) { - ecs_measure_frame_time(world, true); - ecs_measure_system_time(world, true); - } -} + outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); +#ifdef FLECS_HTTP + ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); + ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); + ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); + ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); + ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); + ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); + ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); + ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); + ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif -/** - * @file addons/stats/pipeline_monitor.c - * @brief Stats addon pipeline monitor - */ +error: + return; +} +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} -#ifdef FLECS_STATS +void ecs_world_stats_reduce_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +} -ECS_COMPONENT_DECLARE(EcsPipelineStats); +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} -static -void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { - ecs_map_iter_t it = ecs_map_iter(&ptr->stats); - while (ecs_map_next(&it)) { - ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); - ecs_pipeline_stats_fini(stats); - ecs_os_free(stats); - } - ecs_map_fini(&ptr->stats); +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static ECS_CTOR(EcsPipelineStats, ptr, { - ecs_os_zeromem(ptr); - ecs_map_init(&ptr->stats, NULL); -}) +void ecs_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; -static ECS_COPY(EcsPipelineStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); -}) + int32_t t = s->t = t_next(s->t); + ecs_query_count_t counts = ecs_query_count(query); + ECS_GAUGE_RECORD(&s->result_count, t, counts.results); + ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); + ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); -static ECS_MOVE(EcsPipelineStats, dst, src, { - flecs_pipeline_monitor_dtor(dst); - ecs_os_memcpy_t(dst, src, EcsPipelineStats); - ecs_os_zeromem(src); -}) +error: + return; +} -static ECS_DTOR(EcsPipelineStats, ptr, { - flecs_pipeline_monitor_dtor(ptr); -}) +void ecs_query_cache_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} -static -void flecs_pipeline_stats_set_t( - void *stats, int32_t t) +void ecs_query_cache_stats_reduce_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src, + int32_t count) { - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_pipeline_stats_t*)stats)->t = t; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } +void ecs_query_cache_stats_repeat_last( + ecs_query_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} -static -void flecs_pipeline_stats_copy_last( - void *stats, - void *src) +void ecs_query_cache_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) { - ecs_pipeline_stats_copy_last(stats, src); + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static -void flecs_pipeline_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) +#ifdef FLECS_SYSTEM + +bool ecs_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) { - ecs_pipeline_stats_get(world, res, stats); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); + if (!ptr) { + return false; + } + + ecs_query_stats_get(world, ptr->query, &s->query); + int32_t t = s->query.t; + + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + + s->task = !(ptr->query->flags & EcsQueryMatchThis); + + return true; +error: + return false; } -static -void flecs_pipeline_stats_reduce( - void *stats, - void *src) +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_pipeline_stats_reduce(stats, src); + ecs_query_cache_stats_reduce(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } -static -void flecs_pipeline_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) +void ecs_system_stats_reduce_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src, + int32_t count) { - ecs_pipeline_stats_reduce_last(stats, last, reduce_count); + ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); + dst->task = src->task; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } -static -void flecs_pipeline_stats_repeat_last( - void* stats) +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats) { - ecs_pipeline_stats_repeat_last(stats); + ecs_query_cache_stats_repeat_last(&stats->query); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->query.t)); } -static -void flecs_pipeline_stats_fini( - void *stats) +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_pipeline_stats_fini(stats); + ecs_query_cache_stats_copy_last(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } -void FlecsPipelineMonitorImport( - ecs_world_t *world) +#endif + +#ifdef FLECS_PIPELINE + +bool ecs_pipeline_stats_get( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) { - ECS_COMPONENT_DEFINE(world, EcsPipelineStats); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - ecs_set_hooks(world, EcsPipelineStats, { - .ctor = ecs_ctor(EcsPipelineStats), - .copy = ecs_copy(EcsPipelineStats), - .move = ecs_move(EcsPipelineStats), - .dtor = ecs_dtor(EcsPipelineStats) - }); + const ecs_world_t *world = ecs_get_world(stage); + const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); + if (!pqc) { + return false; + } + ecs_pipeline_state_t *pq = pqc->state; + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_stats_api_t api = { - .copy_last = flecs_pipeline_stats_copy_last, - .get = flecs_pipeline_stats_get, - .reduce = flecs_pipeline_stats_reduce, - .reduce_last = flecs_pipeline_stats_reduce_last, - .repeat_last = flecs_pipeline_stats_repeat_last, - .set_t = flecs_pipeline_stats_set_t, - .fini = flecs_pipeline_stats_fini, - .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), - .monitor_component_id = ecs_id(EcsPipelineStats), - .query_component_id = ecs_id(EcsPipeline) - }; + int32_t sys_count = 0, active_sys_count = 0; - flecs_stats_api_import(world, &api); -} + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + active_sys_count += it.count; + } + + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } + + /* Also count synchronization points */ + ecs_vec_t *ops = &pq->ops; + ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); + int32_t pip_count = active_sys_count + ecs_vec_count(ops); -#endif + if (!sys_count) { + return false; + } -/** - * @file addons/stats.c - * @brief Stats addon. - */ + if (op) { + ecs_entity_t *systems = NULL; + if (pip_count) { + ecs_vec_init_if_t(&s->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); + systems = ecs_vec_first_t(&s->systems, ecs_entity_t); + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(stage, pq->query); + + int32_t i, i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + for (i = 0; i < it.count; i ++) { + systems[i_system ++] = it.entities[i]; + ran_since_merge ++; + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + systems[i_system ++] = 0; /* 0 indicates a merge point */ + } + } + } -#ifdef FLECS_STATS + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); + } -#define ECS_GAUGE_RECORD(m, t, value)\ - flecs_gauge_record(m, t, (ecs_float_t)(value)) + /* Get sync point statistics */ + int32_t i, count = ecs_vec_count(ops); + if (count) { + ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); + op = ecs_vec_first_t(ops, ecs_pipeline_op_t); -#define ECS_COUNTER_RECORD(m, t, value)\ - flecs_counter_record(m, t, (double)(value)) + for (i = 0; i < count; i ++) { + ecs_pipeline_op_t *cur = &op[i]; + ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, + ecs_sync_stats_t, i); -#define ECS_METRIC_FIRST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) + ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); + ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, + cur->commands_enqueued); -#define ECS_METRIC_LAST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + el->system_count = cur->count; + el->multi_threaded = cur->multi_threaded; + el->immediate = cur->immediate; + } + } + } -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; -} + s->t = t_next(s->t); -static -int32_t t_prev( - int32_t t) -{ - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; + return true; +error: + return false; } -static -void flecs_gauge_record( - ecs_metric_t *m, - int32_t t, - ecs_float_t value) +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) { - m->gauge.avg[t] = value; - m->gauge.min[t] = value; - m->gauge.max[t] = value; + ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); + ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } -static -double flecs_counter_record( - ecs_metric_t *m, - int32_t t, - double value) +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - int32_t tp = t_prev(t); - double prev = m->counter.value[tp]; - m->counter.value[t] = value; - double gauge_value = value - prev; - if (gauge_value < 0) { - gauge_value = 0; /* Counters are monotonically increasing */ + int32_t system_count = ecs_vec_count(&src->systems); + ecs_vec_init_if_t(&dst->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); + ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); + ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); + ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; } - flecs_gauge_record(m, t, (ecs_float_t)gauge_value); - return gauge_value; + + dst->t = t_next(dst->t); } -static -void flecs_metric_print( - const char *name, - ecs_float_t value) +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src, + int32_t count) { - ecs_size_t len = ecs_os_strlen(name); - ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t, count); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; + } + + dst->t = t_prev(dst->t); } -static -void flecs_gauge_print( - const char *name, - int32_t t, - const ecs_metric_t *m) +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats) { - flecs_metric_print(name, m->gauge.avg[t]); + int32_t i, sync_count = ecs_vec_count(&stats->sync_points); + ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *el = &syncs[i]; + flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), + (stats->t)); + } + + stats->t = t_next(stats->t); } -static -void flecs_counter_print( - const char *name, - int32_t t, - const ecs_metric_t *m) +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - flecs_metric_print(name, m->counter.rate.avg[t]); + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; + } } -void ecs_metric_reduce( - ecs_metric_t *dst, - const ecs_metric_t *src, - int32_t t_dst, - int32_t t_src) +#endif + +void ecs_world_stats_log( + const ecs_world_t *world, + const ecs_world_stats_t *s) { - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = s->t; - bool min_set = false; - dst->gauge.avg[t_dst] = 0; - dst->gauge.min[t_dst] = 0; - dst->gauge.max[t_dst] = 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + world = ecs_get_world(world); + + flecs_counter_print("Frame", t, &s->frame.frame_count); + ecs_trace("-------------------------------------"); + flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); + flecs_counter_print("systems ran", t, &s->frame.systems_ran); + ecs_trace(""); + flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); + flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); + ecs_trace(""); + flecs_gauge_print("actual FPS", t, &s->performance.fps); + flecs_counter_print("frame time", t, &s->performance.frame_time); + flecs_counter_print("system time", t, &s->performance.system_time); + flecs_counter_print("merge time", t, &s->performance.merge_time); + flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); + ecs_trace(""); + flecs_gauge_print("tag id count", t, &s->components.tag_count); + flecs_gauge_print("component id count", t, &s->components.component_count); + flecs_gauge_print("pair id count", t, &s->components.pair_count); + flecs_gauge_print("type count", t, &s->components.type_count); + flecs_counter_print("id create count", t, &s->components.create_count); + flecs_counter_print("id delete count", t, &s->components.delete_count); + ecs_trace(""); + flecs_gauge_print("alive entity count", t, &s->entities.count); + flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); + ecs_trace(""); + flecs_gauge_print("query count", t, &s->queries.query_count); + flecs_gauge_print("observer count", t, &s->queries.observer_count); + flecs_gauge_print("system count", t, &s->queries.system_count); + ecs_trace(""); + flecs_gauge_print("table count", t, &s->tables.count); + flecs_gauge_print("empty table count", t, &s->tables.empty_count); + flecs_counter_print("table create count", t, &s->tables.create_count); + flecs_counter_print("table delete count", t, &s->tables.delete_count); + ecs_trace(""); + flecs_counter_print("add commands", t, &s->commands.add_count); + flecs_counter_print("remove commands", t, &s->commands.remove_count); + flecs_counter_print("delete commands", t, &s->commands.delete_count); + flecs_counter_print("clear commands", t, &s->commands.clear_count); + flecs_counter_print("set commands", t, &s->commands.set_count); + flecs_counter_print("ensure commands", t, &s->commands.ensure_count); + flecs_counter_print("modified commands", t, &s->commands.modified_count); + flecs_counter_print("other commands", t, &s->commands.other_count); + flecs_counter_print("discarded commands", t, &s->commands.discard_count); + flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); + flecs_counter_print("batched commands", t, &s->commands.batched_count); + ecs_trace(""); + +error: + return; +} - int32_t i; - for (i = 0; i < ECS_STAT_WINDOW; i ++) { - int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; +#endif - if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { - dst->gauge.min[t_dst] = src->gauge.min[t]; - min_set = true; - } - if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { - dst->gauge.max[t_dst] = src->gauge.max[t]; - } - } +/** + * @file addons/stats/system_monitor.c + * @brief Stats addon system monitor + */ - dst->counter.value[t_dst] = src->counter.value[t_src]; -error: - return; -} +#ifdef FLECS_STATS -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t prev, - int32_t count) -{ - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t t = t_next(prev); +ECS_COMPONENT_DECLARE(EcsSystemStats); - if (m->gauge.min[t] < m->gauge.min[prev]) { - m->gauge.min[prev] = m->gauge.min[t]; +static +void flecs_system_monitor_dtor(EcsSystemStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *stats = ecs_map_ptr(&it); + ecs_os_free(stats); } + ecs_map_fini(&ptr->stats); +} - if (m->gauge.max[t] > m->gauge.max[prev]) { - m->gauge.max[prev] = m->gauge.max[t]; - } +static ECS_CTOR(EcsSystemStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) - ecs_float_t fcount = (ecs_float_t)(count + 1); - ecs_float_t cur = m->gauge.avg[prev]; - ecs_float_t next = m->gauge.avg[t]; +static ECS_COPY(EcsSystemStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); +}) - cur *= ((fcount - 1) / fcount); - next *= 1 / fcount; +static ECS_MOVE(EcsSystemStats, dst, src, { + flecs_system_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsSystemStats); + ecs_os_zeromem(src); +}) - m->gauge.avg[prev] = cur + next; - m->counter.value[prev] = m->counter.value[t]; +static ECS_DTOR(EcsSystemStats, ptr, { + flecs_system_monitor_dtor(ptr); +}) -error: - return; +static +void flecs_system_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_system_stats_t*)stats)->query.t = t; } -void ecs_metric_copy( - ecs_metric_t *m, - int32_t dst, - int32_t src) +static +void flecs_system_stats_copy_last( + void *stats, + void *src) { - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - - m->gauge.avg[dst] = m->gauge.avg[src]; - m->gauge.min[dst] = m->gauge.min[src]; - m->gauge.max[dst] = m->gauge.max[src]; - m->counter.value[dst] = m->counter.value[src]; - -error: - return; + ecs_system_stats_copy_last(stats, src); } static -void flecs_stats_reduce( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) +void flecs_system_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) { - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); - } + ecs_system_stats_get(world, res, stats); } static -void flecs_stats_reduce_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src, - int32_t count) +void flecs_system_stats_reduce( + void *stats, + void *src) { - int32_t t_dst_next = t_next(t_dst); - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - /* Reduce into previous value */ - ecs_metric_reduce_last(dst_cur, t_dst, count); - - /* Restore old value */ - dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; - } + ecs_system_stats_reduce(stats, src); } static -void flecs_stats_repeat_last( - ecs_metric_t *cur, - ecs_metric_t *last, - int32_t t) +void flecs_system_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - int32_t prev = t_prev(t); - for (; cur <= last; cur ++) { - ecs_metric_copy(cur, t, prev); - } + ecs_system_stats_reduce_last(stats, last, reduce_count); } static -void flecs_stats_copy_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) +void flecs_system_stats_repeat_last( + void* stats) { - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; - } + ecs_system_stats_repeat_last(stats); } -void ecs_world_stats_get( - const ecs_world_t *world, - ecs_world_stats_t *s) +void FlecsSystemMonitorImport( + ecs_world_t *world) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - int32_t t = s->t = t_next(s->t); - - double delta_frame_count = - ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); - ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); - ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); - ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); - ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); - ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); - ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); + ECS_COMPONENT_DEFINE(world, EcsSystemStats); - double delta_world_time = - ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); - ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); - ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); - ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); - ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); - ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); - ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); - ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); - if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { - ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); - } else { - ECS_GAUGE_RECORD(&s->performance.fps, t, 0); - } + ecs_set_hooks(world, EcsSystemStats, { + .ctor = ecs_ctor(EcsSystemStats), + .copy = ecs_copy(EcsSystemStats), + .move = ecs_move(EcsSystemStats), + .dtor = ecs_dtor(EcsSystemStats) + }); - ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); - ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); + ecs_stats_api_t api = { + .copy_last = flecs_system_stats_copy_last, + .get = flecs_system_stats_get, + .reduce = flecs_system_stats_reduce, + .reduce_last = flecs_system_stats_reduce_last, + .repeat_last = flecs_system_stats_repeat_last, + .set_t = flecs_system_stats_set_t, + .stats_size = ECS_SIZEOF(ecs_system_stats_t), + .monitor_component_id = ecs_id(EcsSystemStats), + .query_component_id = EcsSystem + }; - ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); - ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); - ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); - ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); - ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); - ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); + flecs_stats_api_import(world, &api); +} - ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); - ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); - if (ecs_is_alive(world, EcsSystem)) { - ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); - } - 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); +#endif - 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); - ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); - ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); - ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); - ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); - ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); - ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); - ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); - ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); - ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); +/** + * @file addons/stats/world_monitor.c + * @brief Stats addon world monitor. + */ - int64_t outstanding_allocs = ecs_os_api_malloc_count + - ecs_os_api_calloc_count - ecs_os_api_free_count; - ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); - ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); - ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); - ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); - outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; - ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); - ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); - ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); +#ifdef FLECS_STATS - outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; - ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); - ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); - ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); +ECS_COMPONENT_DECLARE(EcsWorldStats); -#ifdef FLECS_HTTP - ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); - ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); - ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); - ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); - ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); - ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); - ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); - ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); - ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); -#endif +static +void flecs_world_stats_get( + ecs_world_t *world, ecs_entity_t res, void *stats) +{ + (void)res; + ecs_world_stats_get(world, stats); +} -error: - return; +static +void flecs_world_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_world_stats_t*)stats)->t = t; } -void ecs_world_stats_reduce( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) +static +void flecs_world_stats_copy_last( + void *stats, + void *src) { - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); + ecs_world_stats_copy_last(stats, src); } -void ecs_world_stats_reduce_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src, - int32_t count) +static +void flecs_world_stats_reduce( + void *stats, + void *src) { - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); + ecs_world_stats_reduce(stats, src); } -void ecs_world_stats_repeat_last( - ecs_world_stats_t *stats) +static +void flecs_world_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); + ecs_world_stats_reduce_last(stats, last, reduce_count); } -void ecs_world_stats_copy_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) +static +void flecs_world_stats_repeat_last( + void* stats) { - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); + ecs_world_stats_repeat_last(stats); } -void ecs_query_stats_get( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *s) +void FlecsWorldMonitorImport( + ecs_world_t *world) { - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; + ECS_COMPONENT_DEFINE(world, EcsWorldStats); - int32_t t = s->t = t_next(s->t); - ecs_query_count_t counts = ecs_query_count(query); - ECS_GAUGE_RECORD(&s->result_count, t, counts.results); - ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); - ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); + ecs_set_hooks(world, EcsWorldStats, { + .ctor = flecs_default_ctor + }); -error: - return; + ecs_stats_api_t api = { + .copy_last = flecs_world_stats_copy_last, + .get = flecs_world_stats_get, + .reduce = flecs_world_stats_reduce, + .reduce_last = flecs_world_stats_reduce_last, + .repeat_last = flecs_world_stats_repeat_last, + .set_t = flecs_world_stats_set_t, + .fini = NULL, + .stats_size = ECS_SIZEOF(ecs_world_stats_t), + .monitor_component_id = ecs_id(EcsWorldStats) + }; + + flecs_stats_api_import(world, &api); } -void ecs_query_cache_stats_reduce( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) +#endif + +/** + * @file addons/world_summary.c + * @brief Monitor addon. + */ + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(EcsWorldSummary); + +static +void flecs_copy_world_summary( + ecs_world_t *world, + EcsWorldSummary *dst) { - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); + const ecs_world_info_t *info = ecs_get_world_info(world); + + dst->target_fps = (double)info->target_fps; + dst->time_scale = (double)info->time_scale; + + dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; + dst->system_time_last = (double)info->system_time_total - dst->system_time_total; + dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; + + dst->frame_time_total = (double)info->frame_time_total; + dst->system_time_total = (double)info->system_time_total; + dst->merge_time_total = (double)info->merge_time_total; + + dst->frame_count ++; + dst->command_count += + info->cmd.add_count + + info->cmd.remove_count + + info->cmd.delete_count + + info->cmd.clear_count + + info->cmd.set_count + + info->cmd.ensure_count + + info->cmd.modified_count + + info->cmd.discard_count + + info->cmd.event_count + + info->cmd.other_count; + + dst->build_info = *ecs_get_build_info(); } -void ecs_query_cache_stats_reduce_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src, - int32_t count) -{ - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +static +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_copy_world_summary(it->world, &summary[i]); + } } -void ecs_query_cache_stats_repeat_last( - ecs_query_stats_t *stats) -{ - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); +static +void OnSetWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); + ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); + } } -void ecs_query_cache_stats_copy_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) +void FlecsWorldSummaryImport( + ecs_world_t *world) { - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); -} + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); -#ifdef FLECS_SYSTEM +#if defined(FLECS_META) && defined(FLECS_UNITS) + ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); + ecs_struct(world, { + .entity = ecs_id(EcsWorldSummary), + .members = { + { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, + { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, + { .name = "command_count", .type = ecs_id(ecs_u64_t) }, + { .name = "build_info", .type = build_info } + } + }); +#endif + const ecs_world_info_t *info = ecs_get_world_info(world); -bool ecs_system_stats_get( - const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *s) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "UpdateWorldSummary", + .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) + }), + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = UpdateWorldSummary + }); + + ecs_observer(world, { + .entity = ecs_entity(world, { + .name = "OnSetWorldSummary" + }), + .events = { EcsOnSet }, + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = OnSetWorldSummary + }); + + ecs_set(world, EcsWorld, EcsWorldSummary, { + .target_fps = (double)info->target_fps, + .time_scale = (double)info->time_scale + }); + + EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); + flecs_copy_world_summary(world, summary); + ecs_modified(world, EcsWorld, EcsWorldSummary); +} - world = ecs_get_world(world); +#endif - const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); - if (!ptr) { - return false; - } +/** + * @file addons/system/system.c + * @brief System addon. + */ - ecs_query_stats_get(world, ptr->query, &s->query); - int32_t t = s->query.t; - ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); +#ifdef FLECS_SYSTEM - s->task = !(ptr->query->flags & EcsQueryMatchThis); - return true; -error: - return false; -} +ecs_mixins_t ecs_system_t_mixins = { + .type_name = "ecs_system_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_system_t, world), + [EcsMixinEntity] = offsetof(ecs_system_t, entity), + [EcsMixinDtor] = offsetof(ecs_system_t, dtor) + } +}; -void ecs_system_stats_reduce( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - ecs_query_cache_stats_reduce(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t); -} +/* -- Public API -- */ -void ecs_system_stats_reduce_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src, - int32_t count) +ecs_entity_t flecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) { - ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); - dst->task = src->task; - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); -} + ecs_ftime_t time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; -void ecs_system_stats_repeat_last( - ecs_system_stats_t *stats) -{ - ecs_query_cache_stats_repeat_last(&stats->query); - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->query.t)); -} + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } -void ecs_system_stats_copy_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - ecs_query_cache_stats_copy_last(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); -} + if (tick_source) { + const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); -#endif + if (tick) { + time_elapsed = tick->time_elapsed; -#ifdef FLECS_PIPELINE + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } -bool ecs_pipeline_stats_get( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) -{ - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + ecs_os_perf_trace_push(system_data->name); - const ecs_world_t *world = ecs_get_world(stage); - const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); - if (!pqc) { - return false; + if (ecs_should_log_3()) { + char *path = ecs_get_path(world, system); + ecs_dbg_3("worker %d: %s", stage_index, path); + ecs_os_free(path); } - ecs_pipeline_state_t *pq = pqc->state; - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t sys_count = 0, active_sys_count = 0; + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; - } - active_sys_count += it.count; + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } else { + stage = world->stages[0]; } - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } + /* Prepare the query iterator */ + ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; - /* Also count synchronization points */ - ecs_vec_t *ops = &pq->ops; - ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); - int32_t pip_count = active_sys_count + ecs_vec_count(ops); + qit.system = system; + qit.delta_time = delta_time; + qit.delta_system_time = time_elapsed; + qit.param = param; + qit.ctx = system_data->ctx; + qit.callback_ctx = system_data->callback_ctx; + qit.run_ctx = system_data->run_ctx; - if (!sys_count) { - return false; + flecs_defer_begin(world, stage); + + if (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; } - if (op) { - ecs_entity_t *systems = NULL; - if (pip_count) { - ecs_vec_init_if_t(&s->systems, ecs_entity_t); - ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); - systems = ecs_vec_first_t(&s->systems, ecs_entity_t); + ecs_entity_t old_system = flecs_stage_set_system(stage, system); + ecs_iter_action_t action = system_data->action; + it->callback = action; - /* Populate systems vector, keep track of sync points */ - it = ecs_query_iter(stage, pq->query); - - int32_t i, i_system = 0, ran_since_merge = 0; - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; + ecs_run_action_t run = system_data->run; + if (run) { + /* If system query matches nothing, the system run callback doesn't have + * anything to iterate, so the iterator resources don't get cleaned up + * automatically, so clean it up here. */ + if (system_data->query->flags & EcsQueryMatchNothing) { + it->next = flecs_default_next_callback; /* Return once */ + run(it); + ecs_iter_fini(&qit); + } else { + run(it); + } + } else { + if (system_data->query->term_count) { + if (it == &qit) { + while (ecs_query_next(&qit)) { + action(&qit); } - - for (i = 0; i < it.count; i ++) { - systems[i_system ++] = it.entities[i]; - ran_since_merge ++; - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; - op++; - systems[i_system ++] = 0; /* 0 indicates a merge point */ - } + } else { + while (ecs_iter_next(it)) { + action(it); } } - - systems[i_system ++] = 0; /* Last merge */ - ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { - ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); + action(&qit); + ecs_iter_fini(&qit); } + } - /* Get sync point statistics */ - int32_t i, count = ecs_vec_count(ops); - if (count) { - ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); - op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + flecs_stage_set_system(stage, old_system); + + if (measure_time) { + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); + } + + flecs_defer_end(world, stage); + + ecs_os_perf_trace_pop(system_data->name); + + return it->interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + return flecs_run_intern( + world, stage, system, system_data, stage_index, stage_count, + delta_time, param); +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_run_intern( + world, stage, system, system_data, 0, 0, delta_time, param); +} - for (i = 0; i < count; i ++) { - ecs_pipeline_op_t *cur = &op[i]; - ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, - ecs_sync_stats_t, i); +/* System deinitialization */ +static +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); + } - ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); - ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, - cur->commands_enqueued); + if (sys->callback_ctx_free) { + sys->callback_ctx_free(sys->callback_ctx); + } - el->system_count = cur->count; - el->multi_threaded = cur->multi_threaded; - el->immediate = cur->immediate; - } - } + if (sys->run_ctx_free) { + sys->run_ctx_free(sys->run_ctx); } - s->t = t_next(s->t); + /* Safe cast, type owns name */ + ecs_os_free(ECS_CONST_CAST(char*, sys->name)); - return true; -error: - return false; + flecs_poly_free(sys, ecs_system_t); } -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) +/* ecs_poly_dtor_t-compatible wrapper */ +static +void flecs_system_poly_fini(void *sys) { - ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); - ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); + flecs_system_fini(sys); } -void ecs_pipeline_stats_reduce( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) +static +int flecs_system_init_timer( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_system_desc_t *desc) { - int32_t system_count = ecs_vec_count(&src->systems); - ecs_vec_init_if_t(&dst->systems, ecs_entity_t); - ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); - ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); - ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); - ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { + char *name = ecs_get_path(world, entity); + ecs_err("system %s cannot have both interval and rate set", name); + ecs_os_free(name); + return -1; + } - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, src->t); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; + if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || + ECS_NEQZERO(desc->tick_source)) + { +#ifdef FLECS_TIMER + if (ECS_NEQZERO(desc->interval)) { + ecs_set_interval(world, entity, desc->interval); + } + + if (desc->rate) { + ecs_entity_t tick_source = desc->tick_source; + ecs_set_rate(world, entity, desc->rate, tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + (void)world; + (void)entity; + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif } - dst->t = t_next(dst->t); + return 0; } -void ecs_pipeline_stats_reduce_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src, - int32_t count) +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) { - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_system_desc_t was not initialized to zero"); + ecs_assert(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, src->t, count); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_entity(world, {0}); } - dst->t = t_prev(dst->t); -} + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); + if (!poly->poly) { + ecs_system_t *system = flecs_poly_new(ecs_system_t); + ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); + + poly->poly = system; + system->world = world; + system->dtor = flecs_system_poly_fini; + system->entity = entity; -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats) -{ - int32_t i, sync_count = ecs_vec_count(&stats->sync_points); - ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = entity; - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *el = &syncs[i]; - flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), - (stats->t)); - } + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; + } - stats->t = t_next(stats->t); -} + /* Prevent the system from moving while we're initializing */ + flecs_defer_begin(world, world->stages[0]); -void ecs_pipeline_stats_copy_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) -{ - int32_t i, sync_count = ecs_vec_count(&src->sync_points); - ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); - ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); - ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); - ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + system->query = query; + system->query_entity = query->entity; - for (i = 0; i < sync_count; i ++) { - ecs_sync_stats_t *dst_el = &dst_syncs[i]; - ecs_sync_stats_t *src_el = &src_syncs[i]; - flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), - ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); - dst_el->system_count = src_el->system_count; - dst_el->multi_threaded = src_el->multi_threaded; - dst_el->immediate = src_el->immediate; - } -} + system->run = desc->run; + system->action = desc->callback; -#endif + system->ctx = desc->ctx; + system->callback_ctx = desc->callback_ctx; + system->run_ctx = desc->run_ctx; -void ecs_world_stats_log( - const ecs_world_t *world, - const ecs_world_stats_t *s) -{ - int32_t t = s->t; + system->ctx_free = desc->ctx_free; + system->callback_ctx_free = desc->callback_ctx_free; + system->run_ctx_free = desc->run_ctx_free; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + system->tick_source = desc->tick_source; - world = ecs_get_world(world); - - flecs_counter_print("Frame", t, &s->frame.frame_count); - ecs_trace("-------------------------------------"); - flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); - flecs_counter_print("systems ran", t, &s->frame.systems_ran); - ecs_trace(""); - flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); - flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); - ecs_trace(""); - flecs_gauge_print("actual FPS", t, &s->performance.fps); - flecs_counter_print("frame time", t, &s->performance.frame_time); - flecs_counter_print("system time", t, &s->performance.system_time); - flecs_counter_print("merge time", t, &s->performance.merge_time); - flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); - ecs_trace(""); - flecs_gauge_print("tag id count", t, &s->components.tag_count); - flecs_gauge_print("component id count", t, &s->components.component_count); - flecs_gauge_print("pair id count", t, &s->components.pair_count); - flecs_gauge_print("type count", t, &s->components.type_count); - flecs_counter_print("id create count", t, &s->components.create_count); - flecs_counter_print("id delete count", t, &s->components.delete_count); - ecs_trace(""); - flecs_gauge_print("alive entity count", t, &s->entities.count); - flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); - ecs_trace(""); - flecs_gauge_print("query count", t, &s->queries.query_count); - flecs_gauge_print("observer count", t, &s->queries.observer_count); - flecs_gauge_print("system count", t, &s->queries.system_count); - ecs_trace(""); - flecs_gauge_print("table count", t, &s->tables.count); - flecs_gauge_print("empty table count", t, &s->tables.empty_count); - flecs_counter_print("table create count", t, &s->tables.create_count); - flecs_counter_print("table delete count", t, &s->tables.delete_count); - ecs_trace(""); - flecs_counter_print("add commands", t, &s->commands.add_count); - flecs_counter_print("remove commands", t, &s->commands.remove_count); - flecs_counter_print("delete commands", t, &s->commands.delete_count); - flecs_counter_print("clear commands", t, &s->commands.clear_count); - flecs_counter_print("set commands", t, &s->commands.set_count); - flecs_counter_print("ensure commands", t, &s->commands.ensure_count); - flecs_counter_print("modified commands", t, &s->commands.modified_count); - flecs_counter_print("other commands", t, &s->commands.other_count); - flecs_counter_print("discarded commands", t, &s->commands.discard_count); - flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); - flecs_counter_print("batched commands", t, &s->commands.batched_count); - ecs_trace(""); - -error: - return; -} + system->multi_threaded = desc->multi_threaded; + system->immediate = desc->immediate; + + system->name = ecs_get_path(world, entity); + + if (flecs_system_init_timer(world, entity, desc)) { + ecs_delete(world, entity); + ecs_defer_end(world); + goto error; + } -#endif + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } -/** - * @file addons/stats/system_monitor.c - * @brief Stats addon system monitor - */ + ecs_defer_end(world); + } else { + flecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t *system = (ecs_system_t*)poly->poly; + if (system->ctx_free) { + if (system->ctx && system->ctx != desc->ctx) { + system->ctx_free(system->ctx); + } + } -#ifdef FLECS_STATS + if (system->callback_ctx_free) { + if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { + system->callback_ctx_free(system->callback_ctx); + system->callback_ctx_free = NULL; + system->callback_ctx = NULL; + } + } -ECS_COMPONENT_DECLARE(EcsSystemStats); + if (system->run_ctx_free) { + if (system->run_ctx && system->run_ctx != desc->run_ctx) { + system->run_ctx_free(system->run_ctx); + system->run_ctx_free = NULL; + system->run_ctx = NULL; + } + } -static -void flecs_system_monitor_dtor(EcsSystemStats *ptr) { - ecs_map_iter_t it = ecs_map_iter(&ptr->stats); - while (ecs_map_next(&it)) { - ecs_system_stats_t *stats = ecs_map_ptr(&it); - ecs_os_free(stats); - } - ecs_map_fini(&ptr->stats); -} + if (desc->run) { + system->run = desc->run; + if (!desc->callback) { + system->action = NULL; + } + } -static ECS_CTOR(EcsSystemStats, ptr, { - ecs_os_zeromem(ptr); - ecs_map_init(&ptr->stats, NULL); -}) + if (desc->callback) { + system->action = desc->callback; + if (!desc->run) { + system->run = NULL; + } + } -static ECS_COPY(EcsSystemStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); -}) + if (desc->ctx) { + system->ctx = desc->ctx; + } -static ECS_MOVE(EcsSystemStats, dst, src, { - flecs_system_monitor_dtor(dst); - ecs_os_memcpy_t(dst, src, EcsSystemStats); - ecs_os_zeromem(src); -}) + if (desc->callback_ctx) { + system->callback_ctx = desc->callback_ctx; + } -static ECS_DTOR(EcsSystemStats, ptr, { - flecs_system_monitor_dtor(ptr); -}) + if (desc->run_ctx) { + system->run_ctx = desc->run_ctx; + } -static -void flecs_system_stats_set_t( - void *stats, int32_t t) -{ - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_system_stats_t*)stats)->query.t = t; -} + if (desc->ctx_free) { + system->ctx_free = desc->ctx_free; + } -static -void flecs_system_stats_copy_last( - void *stats, - void *src) -{ - ecs_system_stats_copy_last(stats, src); -} + if (desc->callback_ctx_free) { + system->callback_ctx_free = desc->callback_ctx_free; + } -static -void flecs_system_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) -{ - ecs_system_stats_get(world, res, stats); -} + if (desc->run_ctx_free) { + system->run_ctx_free = desc->run_ctx_free; + } -static -void flecs_system_stats_reduce( - void *stats, - void *src) -{ - ecs_system_stats_reduce(stats, src); -} + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } -static -void flecs_system_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_system_stats_reduce_last(stats, last, reduce_count); + if (desc->immediate) { + system->immediate = desc->immediate; + } + + if (flecs_system_init_timer(world, entity, desc)) { + return 0; + } + } + + flecs_poly_modified(world, entity, ecs_system_t); + + return entity; +error: + return 0; } -static -void flecs_system_stats_repeat_last( - void* stats) +const ecs_system_t* ecs_system_get( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_system_stats_repeat_last(stats); + return flecs_poly_get(world, entity, ecs_system_t); } -void FlecsSystemMonitorImport( +void FlecsSystemImport( ecs_world_t *world) { - ECS_COMPONENT_DEFINE(world, EcsSystemStats); + ECS_MODULE(world, FlecsSystem); +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsSystem), + "Module that implements Flecs systems"); +#endif - ecs_set_hooks(world, EcsSystemStats, { - .ctor = ecs_ctor(EcsSystemStats), - .copy = ecs_copy(EcsSystemStats), - .move = ecs_move(EcsSystemStats), - .dtor = ecs_dtor(EcsSystemStats) - }); + ecs_set_name_prefix(world, "Ecs"); - ecs_stats_api_t api = { - .copy_last = flecs_system_stats_copy_last, - .get = flecs_system_stats_get, - .reduce = flecs_system_stats_reduce, - .reduce_last = flecs_system_stats_reduce_last, - .repeat_last = flecs_system_stats_repeat_last, - .set_t = flecs_system_stats_set_t, - .stats_size = ECS_SIZEOF(ecs_system_stats_t), - .monitor_component_id = ecs_id(EcsSystemStats), - .query_component_id = EcsSystem - }; + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); - flecs_stats_api_import(world, &api); + /* Make sure to never inherit system component. This makes sure that any + * term created for the System component will default to 'self' traversal, + * which improves efficiency of the query. */ + ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); } #endif /** - * @file addons/stats/world_monitor.c - * @brief Stats addon world monitor. + * @file query/compiler/compile.c + * @brief Compile query program from query. */ -#ifdef FLECS_STATS +static +bool flecs_query_var_is_anonymous( + const ecs_query_impl_t *query, + ecs_var_id_t var_id) +{ + ecs_query_var_t *var = &query->vars[var_id]; + return var->anonymous; +} + +ecs_var_id_t flecs_query_add_var( + ecs_query_impl_t *query, + const char *name, + ecs_vec_t *vars, + ecs_var_kind_t kind) +{ + const char *dot = NULL; + if (name) { + dot = strchr(name, '.'); + if (dot) { + kind = EcsVarEntity; /* lookup variables are always entities */ + } + } + + ecs_hashmap_t *var_index = NULL; + ecs_var_id_t var_id = EcsVarNone; + if (name) { + if (kind == EcsVarAny) { + var_id = flecs_query_find_var_id(query, name, EcsVarEntity); + if (var_id != EcsVarNone) { + return var_id; + } + + var_id = flecs_query_find_var_id(query, name, EcsVarTable); + if (var_id != EcsVarNone) { + return var_id; + } + + kind = EcsVarTable; + } else { + var_id = flecs_query_find_var_id(query, name, kind); + if (var_id != EcsVarNone) { + return var_id; + } + } + + if (kind == EcsVarTable) { + var_index = &query->tvar_index; + } else { + var_index = &query->evar_index; + } + + /* If we're creating an entity var, check if it has a table variant */ + if (kind == EcsVarEntity && var_id == EcsVarNone) { + var_id = flecs_query_find_var_id(query, name, EcsVarTable); + } + } + + ecs_query_var_t *var; + ecs_var_id_t result; + if (vars) { + var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); + result = var->id = flecs_itovar(ecs_vec_count(vars)); + } else { + ecs_dbg_assert(query->var_count < query->var_size, + ECS_INTERNAL_ERROR, NULL); + var = &query->vars[query->var_count]; + result = var->id = flecs_itovar(query->var_count); + query->var_count ++; + } -ECS_COMPONENT_DECLARE(EcsWorldStats); + var->kind = flecs_ito(int8_t, kind); + var->name = name; + var->table_id = var_id; + var->base_id = 0; + var->lookup = NULL; + flecs_set_var_label(var, NULL); -static -void flecs_world_stats_get( - ecs_world_t *world, ecs_entity_t res, void *stats) -{ - (void)res; - ecs_world_stats_get(world, stats); -} + if (name) { + flecs_name_index_init_if(var_index, NULL); + flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name[0] == '_'; -static -void flecs_world_stats_set_t( - void *stats, int32_t t) -{ - ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); - ((ecs_world_stats_t*)stats)->t = t; -} + /* Handle variables that require a by-name lookup, e.g. $this.wheel */ + if (dot != NULL) { + ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); + var->lookup = dot + 1; + } + } -static -void flecs_world_stats_copy_last( - void *stats, - void *src) -{ - ecs_world_stats_copy_last(stats, src); + return result; } static -void flecs_world_stats_reduce( - void *stats, - void *src) +ecs_var_id_t flecs_query_add_var_for_term_id( + ecs_query_impl_t *query, + ecs_term_ref_t *term_id, + ecs_vec_t *vars, + ecs_var_kind_t kind) { - ecs_world_stats_reduce(stats, src); -} + const char *name = flecs_term_ref_var_name(term_id); + if (!name) { + return EcsVarNone; + } -static -void flecs_world_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_world_stats_reduce_last(stats, last, reduce_count); + return flecs_query_add_var(query, name, vars, kind); } +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ static -void flecs_world_stats_repeat_last( - void* stats) -{ - ecs_world_stats_repeat_last(stats); -} - -void FlecsWorldMonitorImport( - ecs_world_t *world) +int flecs_query_discover_vars( + ecs_stage_t *stage, + ecs_query_impl_t *query) { - ECS_COMPONENT_DEFINE(world, EcsWorldStats); + ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ + ecs_vec_reset_t(NULL, vars, ecs_query_var_t); - ecs_set_hooks(world, EcsWorldStats, { - .ctor = flecs_default_ctor - }); + ecs_term_t *terms = query->pub.terms; + int32_t a, i, anonymous_count = 0, count = query->pub.term_count; + int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; + bool table_this = false, entity_before_table_this = false; - ecs_stats_api_t api = { - .copy_last = flecs_world_stats_copy_last, - .get = flecs_world_stats_get, - .reduce = flecs_world_stats_reduce, - .reduce_last = flecs_world_stats_reduce_last, - .repeat_last = flecs_world_stats_repeat_last, - .set_t = flecs_world_stats_set_t, - .fini = NULL, - .stats_size = ECS_SIZEOF(ecs_world_stats_t), - .monitor_component_id = ecs_id(EcsWorldStats) - }; + /* For This table lookups during discovery. This will be overwritten after + * discovery with whether the query actually has a This table variable. */ + query->pub.flags |= EcsQueryHasTableThisVar; - flecs_stats_api_import(world, &api); -} + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + ecs_term_ref_t *src = &term->src; -#endif + if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { + /* Keep track of which variables are first used in scope, so that we + * can mark them as anonymous. Terms inside a scope are collapsed + * into a single result, which means that outside of the scope the + * value of those variables is undefined. */ + if (!scope) { + scoped_var_index = ecs_vec_count(vars); + } + scope ++; + continue; + } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { + if (!--scope) { + /* Any new variables declared after entering a scope should be + * marked as anonymous. */ + int32_t v; + for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { + ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; + } + } + continue; + } -/** - * @file addons/world_summary.c - * @brief Monitor addon. - */ + ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( + query, first, vars, EcsVarEntity); + if (first_var_id == EcsVarNone) { + /* If first is not a variable, check if we need to insert anonymous + * variable for resolving component inheritance */ + if (term->flags_ & EcsTermIdInherited) { + anonymous_count += 2; /* table & entity variable */ + } + /* If first is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(first)) { + anonymous_count ++; + } + } -#ifdef FLECS_STATS + if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { + const char *var_name = flecs_term_ref_var_name(src); + if (var_name) { + ecs_var_id_t var_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (var_id == EcsVarNone || var_id == first_var_id) { + var_id = flecs_query_add_var( + query, var_name, vars, EcsVarEntity); + } -ECS_COMPONENT_DECLARE(EcsWorldSummary); + if (var_id != EcsVarNone) { + /* Mark variable as one for which we need to create a table + * variable. Don't create table variable now, so that we can + * store it in the non-public part of the variable array. */ + ecs_query_var_t *var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)var_id - 1); + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + if (!var->lookup) { + var->kind = EcsVarAny; + anonymous_table_count ++; + } -static -void flecs_copy_world_summary( - ecs_world_t *world, - EcsWorldSummary *dst) -{ - const ecs_world_info_t *info = ecs_get_world_info(world); + if (((1llu << term->field_index) & query->pub.data_fields)) { + /* Can't have an anonymous variable as source of a term + * that returns a component. We need to return each + * instance of the component, whereas anonymous + * variables are not guaranteed to be resolved to + * individual entities. */ + if (var->anonymous) { + ecs_err( + "can't use anonymous variable '%s' as source of " + "data term", var->name); + goto error; + } + } - dst->target_fps = (double)info->target_fps; - dst->time_scale = (double)info->time_scale; + /* Track which variable ids are used as field source */ + if (!query->src_vars) { + query->src_vars = flecs_calloc_n(&stage->allocator, + ecs_var_id_t, query->pub.field_count); + } - dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; - dst->system_time_last = (double)info->system_time_total - dst->system_time_total; - dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; + query->src_vars[term->field_index] = var_id; + } + } else { + if (flecs_term_ref_is_wildcard(src)) { + anonymous_count ++; + } + } + } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { + if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); + } + } - dst->frame_time_total = (double)info->frame_time_total; - dst->system_time_total = (double)info->system_time_total; - dst->merge_time_total = (double)info->merge_time_total; + if (flecs_query_add_var_for_term_id( + query, second, vars, EcsVarEntity) == EcsVarNone) + { + /* If second is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(second)) { + anonymous_count ++; + } + } - dst->frame_count ++; - dst->command_count += - info->cmd.add_count + - info->cmd.remove_count + - info->cmd.delete_count + - info->cmd.clear_count + - info->cmd.set_count + - info->cmd.ensure_count + - info->cmd.modified_count + - info->cmd.discard_count + - info->cmd.event_count + - info->cmd.other_count; + if (src->id & EcsIsVariable && second->id & EcsIsVariable) { + if (term->flags_ & EcsTermTransitive) { + /* Anonymous variable to store temporary id for finding + * targets for transitive relationship, see compile_term. */ + anonymous_count ++; + } + } - dst->build_info = *ecs_get_build_info(); -} + /* If member term, make sure source is available as entity */ + if (term->flags_ & EcsTermIsMember) { + flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); + } -static -void UpdateWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + /* Track if a This entity variable is used before a potential This table + * variable. If this happens, the query has no This table variable */ + if (ECS_TERM_REF_ID(src) == EcsThis) { + table_this = true; + } - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - flecs_copy_world_summary(it->world, &summary[i]); + if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { + if (!table_this) { + entity_before_table_this = true; + } + } } -} -static -void OnSetWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + int32_t var_count = ecs_vec_count(vars); + ecs_var_id_t placeholder = EcsVarNone - 1; + bool replace_placeholders = false; - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); - ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); - } -} + /* Ensure lookup variables have table and/or entity variables */ + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->lookup) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; -void FlecsWorldSummaryImport( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsWorldSummary); + ecs_var_id_t base_table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + if (base_table_id != EcsVarNone) { + var->table_id = base_table_id; + } else if (anonymous_table_count) { + /* Scan for implicit anonymous table variables that haven't been + * inserted yet (happens after this step). Doing this here vs. + * ensures that anonymous variables are appended at the end of + * the variable array, while also ensuring that variable ids are + * stable (no swapping of table var ids that are in use). */ + for (a = 0; a < var_count; a ++) { + ecs_query_var_t *avar = ecs_vec_get_t( + vars, ecs_query_var_t, a); + if (avar->kind == EcsVarAny) { + if (!ecs_os_strcmp(avar->name, var_name)) { + base_table_id = (ecs_var_id_t)(a + 1); + break; + } + } + } + if (base_table_id != EcsVarNone) { + /* Set marker so we can set the new table id afterwards */ + var->table_id = placeholder; + replace_placeholders = true; + } + } -#if defined(FLECS_META) && defined(FLECS_UNITS) - ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); - ecs_struct(world, { - .entity = ecs_id(EcsWorldSummary), - .members = { - { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, - { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, - { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, - { .name = "command_count", .type = ecs_id(ecs_u64_t) }, - { .name = "build_info", .type = build_info } - } - }); -#endif - const ecs_world_info_t *info = ecs_get_world_info(world); + ecs_var_id_t base_entity_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (base_entity_id == EcsVarNone) { + /* Get name from table var (must exist). We can't use allocated + * name since variables don't own names. */ + const char *base_name = NULL; + if (base_table_id != EcsVarNone && base_table_id) { + ecs_query_var_t *base_table_var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)base_table_id - 1); + base_name = base_table_var->name; + } else { + base_name = EcsThisName; + } - ecs_system(world, { - .entity = ecs_entity(world, { - .name = "UpdateWorldSummary", - .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) - }), - .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, - .callback = UpdateWorldSummary - }); + base_entity_id = flecs_query_add_var( + query, base_name, vars, EcsVarEntity); + var = ecs_vec_get_t(vars, ecs_query_var_t, i); + } - ecs_observer(world, { - .entity = ecs_entity(world, { - .name = "OnSetWorldSummary" - }), - .events = { EcsOnSet }, - .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, - .callback = OnSetWorldSummary - }); + var->base_id = base_entity_id; - ecs_set(world, EcsWorld, EcsWorldSummary, { - .target_fps = (double)info->target_fps, - .time_scale = (double)info->time_scale - }); + ecs_os_free(var_name); + } + } - EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); - flecs_copy_world_summary(world, summary); - ecs_modified(world, EcsWorld, EcsWorldSummary); -} + var_count = ecs_vec_count(vars); -#endif + /* Add non-This table variables */ + if (anonymous_table_count) { + anonymous_table_count = 0; + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->kind == EcsVarAny) { + var->kind = EcsVarEntity; -/** - * @file addons/system/system.c - * @brief System addon. - */ + ecs_var_id_t var_id = flecs_query_add_var( + query, var->name, vars, EcsVarTable); + ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; + anonymous_table_count ++; + } + } + var_count = ecs_vec_count(vars); + } -#ifdef FLECS_SYSTEM + /* If any forward references to newly added anonymous tables exist, replace + * them with the actual table variable ids. */ + if (replace_placeholders) { + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->table_id == placeholder) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; + var->table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + ecs_assert(var->table_id != EcsVarNone, + ECS_INTERNAL_ERROR, NULL); -ecs_mixins_t ecs_system_t_mixins = { - .type_name = "ecs_system_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_system_t, world), - [EcsMixinEntity] = offsetof(ecs_system_t, entity), - [EcsMixinDtor] = offsetof(ecs_system_t, dtor) + ecs_os_free(var_name); + } + } } -}; -/* -- Public API -- */ - -ecs_entity_t flecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - ecs_system_t *system_data, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) -{ - ecs_ftime_t time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; + /* Always include spot for This variable, even if query doesn't use it */ + var_count ++; - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; + ecs_query_var_t *query_vars = &flecs_this_array; + if ((var_count + anonymous_count) > 1) { + query_vars = flecs_alloc(&stage->allocator, + (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + (var_count + anonymous_count)); } - if (tick_source) { - const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); + query->vars = query_vars; + query->var_count = var_count; + query->pub.var_count = flecs_ito(int8_t, var_count); + ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, + !entity_before_table_this); + query->var_size = var_count + anonymous_count; - if (tick) { - time_elapsed = tick->time_elapsed; + char **var_names; + if (query_vars != &flecs_this_array) { + query_vars[0].kind = EcsVarTable; + query_vars[0].name = NULL; + flecs_set_var_label(&query_vars[0], NULL); + query_vars[0].id = 0; + query_vars[0].table_id = EcsVarNone; + query_vars[0].lookup = NULL; - /* If timer hasn't fired we shouldn't run the system */ - if (!tick->tick) { - return 0; - } - } else { - /* If a timer has been set but the timer entity does not have the - * EcsTimer component, don't run the system. This can be the result - * of a single-shot timer that has fired already. Not resetting the - * timer field of the system will ensure that the system won't be - * ran after the timer has fired. */ - return 0; - } + var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), + var_count + anonymous_count); + var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); + } else { + var_names = &flecs_this_name_array; } - ecs_os_perf_trace_push(system_data->name); + query->pub.vars = (char**)var_names; - if (ecs_should_log_3()) { - char *path = ecs_get_path(world, system); - ecs_dbg_3("worker %d: %s", stage_index, path); - ecs_os_free(path); + query_vars ++; + var_names ++; + var_count --; + + if (var_count) { + ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); + ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); + for (i = 0; i < var_count; i ++) { + ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], + ECS_INTERNAL_ERROR, NULL); + var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); + } } - ecs_time_t time_start; - bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); - if (measure_time) { - ecs_os_get_time(&time_start); - } + /* Hide anonymous table variables from application */ + query->pub.var_count = + flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; - } else { - stage = world->stages[0]; + /* Sanity check to make sure that the public part of the variable array only + * contains entity variables. */ +#ifdef FLECS_DEBUG + for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { + ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } +#endif - /* Prepare the query iterator */ - ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); - ecs_iter_t *it = &qit; + return 0; +error: + return -1; +} - qit.system = system; - qit.delta_time = delta_time; - qit.delta_system_time = time_elapsed; - qit.param = param; - qit.ctx = system_data->ctx; - qit.callback_ctx = system_data->callback_ctx; - qit.run_ctx = system_data->run_ctx; +static +bool flecs_query_var_is_unknown( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_var_t *vars = query->vars; + if (ctx->written & (1ull << var_id)) { + return false; + } else { + ecs_var_id_t table_var = vars[var_id].table_id; + if (table_var != EcsVarNone) { + return flecs_query_var_is_unknown(query, table_var, ctx); + } + } + return true; +} - flecs_defer_begin(world, stage); +/* Returns whether term is unknown. A term is unknown when it has variable + * elements (first, second, src) that are all unknown. */ +static +bool flecs_query_term_is_unknown( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t dummy = {0}; + flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, + &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, + &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, + &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); - if (stage_count > 1 && system_data->multi_threaded) { - wit = ecs_worker_iter(it, stage_index, stage_count); - it = &wit; + bool has_vars = dummy.flags & + ((EcsQueryIsVar << EcsQueryFirst) | + (EcsQueryIsVar << EcsQuerySecond) | + (EcsQueryIsVar << EcsQuerySrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; } - ecs_entity_t old_system = flecs_stage_set_system(stage, system); - ecs_iter_action_t action = system_data->action; - it->callback = action; - - ecs_run_action_t run = system_data->run; - if (run) { - /* If system query matches nothing, the system run callback doesn't have - * anything to iterate, so the iterator resources don't get cleaned up - * automatically, so clean it up here. */ - if (system_data->query->flags & EcsQueryMatchNothing) { - it->next = flecs_default_next_callback; /* Return once */ - run(it); - ecs_iter_fini(&qit); - } else { - run(it); + if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { + return false; } - } else { - if (system_data->query->term_count) { - if (it == &qit) { - while (ecs_query_next(&qit)) { - action(&qit); - } - } else { - while (ecs_iter_next(it)) { - action(it); - } - } - } else { - action(&qit); - ecs_iter_fini(&qit); + } + if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { + return false; + } + } + if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { + return false; } } - flecs_stage_set_system(stage, old_system); + return true; +} - if (measure_time) { - system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); - } +/* Find the next known term from specified offset. This function is used to find + * a term that can be evaluated before a term that is unknown. Evaluating known + * before unknown terms can significantly decrease the search space. */ +static +int32_t flecs_query_term_next_known( + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) +{ + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - flecs_defer_end(world, stage); + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1ull << i)) { + continue; + } - ecs_os_perf_trace_pop(system_data->name); + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + continue; + } - return it->interrupted_by; -} + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; + } -/* -- Public API -- */ + if (flecs_query_term_is_unknown(query, term, ctx)) { + continue; + } -ecs_entity_t ecs_run_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return i; + } - return flecs_run_intern( - world, stage, system, system_data, stage_index, stage_count, - delta_time, param); + return -1; } -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - void *param) +/* If the first part of a query contains more than one trivial term, insert a + * special instruction which batch-evaluates multiple terms. */ +static +void flecs_query_insert_trivial_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_run_intern( - world, stage, system, system_data, 0, 0, delta_time, param); -} + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; + ecs_flags64_t trivial_set = 0; -/* System deinitialization */ -static -void flecs_system_fini(ecs_system_t *sys) { - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); + /* Trivial search always ignores prefabs and disabled entities */ + if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { + return; } - if (sys->callback_ctx_free) { - sys->callback_ctx_free(sys->callback_ctx); - } + /* Find trivial terms, which can be handled in single instruction */ + int32_t trivial_wildcard_terms = 0; + int32_t trivial_terms = 0; - if (sys->run_ctx_free) { - sys->run_ctx_free(sys->run_ctx); - } + for (i = 0; i < term_count; i ++) { + /* Term is already compiled */ + if (*compiled & (1ull << i)) { + continue; + } - /* Safe cast, type owns name */ - ecs_os_free(ECS_CONST_CAST(char*, sys->name)); + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsTrivial)) { + continue; + } - flecs_poly_free(sys, ecs_system_t); -} + /* We can only add trivial terms to plan if they no up traversal */ + if ((term->src.id & EcsTraverseFlags) != EcsSelf) { + continue; + } -/* ecs_poly_dtor_t-compatible wrapper */ -static -void flecs_system_poly_fini(void *sys) -{ - flecs_system_fini(sys); -} + /* Wildcards are not supported for trivial queries */ + if (ecs_id_is_wildcard(term->id)) { + continue; + } -static -int flecs_system_init_timer( - ecs_world_t *world, - ecs_entity_t entity, - const ecs_system_desc_t *desc) -{ - if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { - char *name = ecs_get_path(world, entity); - ecs_err("system %s cannot have both interval and rate set", name); - ecs_os_free(name); - return -1; + trivial_set |= (1llu << i); + + trivial_terms ++; } - if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || - ECS_NEQZERO(desc->tick_source)) - { -#ifdef FLECS_TIMER - if (ECS_NEQZERO(desc->interval)) { - ecs_set_interval(world, entity, desc->interval); + if (trivial_terms >= 2) { + /* Mark terms as compiled & populated */ + for (i = 0; i < q->term_count; i ++) { + if (trivial_set & (1llu << i)) { + *compiled |= (1ull << i); + } } - if (desc->rate) { - ecs_entity_t tick_source = desc->tick_source; - ecs_set_rate(world, entity, desc->rate, tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, entity, desc->tick_source); + /* If there's more than 1 trivial term, batch them in trivial search */ + ecs_query_op_t trivial = {0}; + if (!trivial_wildcard_terms) { + trivial.kind = EcsQueryTriv; } -#else - (void)world; - (void)entity; - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } - return 0; + /* Store the bitset with trivial terms on the instruction */ + trivial.src.entity = trivial_set; + flecs_query_op_insert(&trivial, ctx); + + /* Mark $this as written */ + ctx->written |= (1llu << 0); + } } -ecs_entity_t ecs_system_init( - ecs_world_t *world, - const ecs_system_desc_t *desc) +static +void flecs_query_insert_cache_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_system_desc_t was not initialized to zero"); - ecs_assert(!(world->flags & EcsWorldReadonly), - ECS_INVALID_WHILE_READONLY, NULL); - - ecs_entity_t entity = desc->entity; - if (!entity) { - entity = ecs_entity(world, {0}); + if (!query->cache) { + return; } - EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); - if (!poly->poly) { - ecs_system_t *system = flecs_poly_new(ecs_system_t); - ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); - - poly->poly = system; - system->world = world; - system->dtor = flecs_system_poly_fini; - system->entity = entity; + ecs_query_t *q = &query->pub; - ecs_query_desc_t query_desc = desc->query; - query_desc.entity = entity; + if (q->cache_kind == EcsQueryCacheAll) { + /* If all terms are cacheable, make sure no other terms are compiled */ + *compiled = 0xFFFFFFFFFFFFFFFF; + } else if (q->cache_kind == EcsQueryCacheAuto) { + /* The query is partially cacheable */ + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, entity); - return 0; - } + for (i = 0; i < count; i ++) { + if ((*compiled) & (1ull << i)) { + continue; + } - /* Prevent the system from moving while we're initializing */ - flecs_defer_begin(world, world->stages[0]); + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsCacheable)) { + continue; + } - system->query = query; - system->query_entity = query->entity; + *compiled |= (1ull << i); + } + } - system->run = desc->run; - system->action = desc->callback; + /* Insert the operation for cache traversal */ + ecs_query_op_t op = {0}; - system->ctx = desc->ctx; - system->callback_ctx = desc->callback_ctx; - system->run_ctx = desc->run_ctx; + if (q->flags & EcsQueryIsCacheable) { + op.kind = EcsQueryIsCache; + } else { + op.kind = EcsQueryCache; + } - system->ctx_free = desc->ctx_free; - system->callback_ctx_free = desc->callback_ctx_free; - system->run_ctx_free = desc->run_ctx_free; + flecs_query_write(0, &op.written); + flecs_query_write_ctx(0, ctx, false); + flecs_query_op_insert(&op, ctx); +} - system->tick_source = desc->tick_source; +static +bool flecs_term_ref_match_multiple( + ecs_term_ref_t *ref) +{ + return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); +} - system->multi_threaded = desc->multi_threaded; - system->immediate = desc->immediate; +static +bool flecs_term_match_multiple( + ecs_term_t *term) +{ + return flecs_term_ref_match_multiple(&term->first) || + flecs_term_ref_match_multiple(&term->second); +} - system->name = ecs_get_path(world, entity); +static +int flecs_query_insert_toggle( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &impl->pub; + int32_t i, j, term_count = q->term_count; + ecs_term_t *terms = q->terms; + ecs_flags64_t fields_done = 0; - if (flecs_system_init_timer(world, entity, desc)) { - ecs_delete(world, entity); - ecs_defer_end(world); - goto error; + for (i = 0; i < term_count; i ++) { + if (fields_done & (1llu << i)) { + continue; } - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, entity)); - } + ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsToggle) { + ecs_query_op_t cur = {0}; + flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, + &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); - ecs_defer_end(world); - } else { - flecs_poly_assert(poly->poly, ecs_system_t); - ecs_system_t *system = (ecs_system_t*)poly->poly; + ecs_flags64_t and_toggles = 0; + ecs_flags64_t not_toggles = 0; + ecs_flags64_t optional_toggles = 0; - if (system->ctx_free) { - if (system->ctx && system->ctx != desc->ctx) { - system->ctx_free(system->ctx); - } - } + for (j = i; j < term_count; j ++) { + if (fields_done & (1llu << j)) { + continue; + } - if (system->callback_ctx_free) { - if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { - system->callback_ctx_free(system->callback_ctx); - system->callback_ctx_free = NULL; - system->callback_ctx = NULL; - } - } + /* Also includes term[i], so flags get set correctly */ + term = &terms[j]; - if (system->run_ctx_free) { - if (system->run_ctx && system->run_ctx != desc->run_ctx) { - system->run_ctx_free(system->run_ctx); - system->run_ctx_free = NULL; - system->run_ctx = NULL; - } - } + /* If term is not for the same src, skip */ + ecs_query_op_t next = {0}; + flecs_query_compile_term_ref(NULL, impl, &next, &term->src, + &next.src, EcsQuerySrc, EcsVarAny, ctx, false); + if (next.src.entity != cur.src.entity || + next.flags != cur.flags) + { + continue; + } - if (desc->run) { - system->run = desc->run; - if (!desc->callback) { - system->action = NULL; + /* Source matches, set flag */ + if (term->oper == EcsNot) { + not_toggles |= (1llu << j); + } else if (term->oper == EcsOptional) { + optional_toggles |= (1llu << j); + } else { + and_toggles |= (1llu << j); + } + + fields_done |= (1llu << j); } - } - if (desc->callback) { - system->action = desc->callback; - if (!desc->run) { - system->run = NULL; + if (and_toggles || not_toggles) { + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggle; + op.src = cur.src; + op.flags = cur.flags; + + if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_write(op.src.var, &op.written); + } + + /* Encode fields: + * - first.entity is the fields that match enabled bits + * - second.entity is the fields that match disabled bits + */ + op.first.entity = and_toggles; + op.second.entity = not_toggles; + flecs_query_op_insert(&op, ctx); } - } - if (desc->ctx) { - system->ctx = desc->ctx; - } + /* Insert separate instructions for optional terms. To make sure + * entities are returned in batches where fields are never partially + * set or unset, the result must be split up into batches that have + * the exact same toggle masks. Instead of complicating the toggle + * instruction with code to scan for blocks that have the same bits + * set, separate instructions let the query engine backtrack to get + * the right results. */ + if (optional_toggles) { + for (j = i; j < term_count; j ++) { + uint64_t field_bit = 1ull << j; + if (!(optional_toggles & field_bit)) { + continue; + } - if (desc->callback_ctx) { - system->callback_ctx = desc->callback_ctx; + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggleOption; + op.src = cur.src; + op.first.entity = field_bit; + op.flags = cur.flags; + flecs_query_op_insert(&op, ctx); + } + } } + } - if (desc->run_ctx) { - system->run_ctx = desc->run_ctx; - } + return 0; +} - if (desc->ctx_free) { - system->ctx_free = desc->ctx_free; - } +static +int flecs_query_insert_fixed_src_terms( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &impl->pub; + int32_t i, term_count = q->term_count; + ecs_term_t *terms = q->terms; - if (desc->callback_ctx_free) { - system->callback_ctx_free = desc->callback_ctx_free; - } + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - if (desc->run_ctx_free) { - system->run_ctx_free = desc->run_ctx_free; + if (term->oper == EcsNot) { + /* If term has not operator and variables for first/second, we can't + * put the term first as this could prevent us from getting back + * valid results. For example: + * !$var(e), Tag($var) + * + * Here, the first term would evaluate to false (and cause the + * entire query not to match) if 'e' has any components. + * + * However, when reordering we get results: + * Tag($var), !$var(e) + * + * Now the query returns all entities with Tag, that 'e' does not + * have as component. For this reason, queries should never use + * unwritten variables in not terms- and we should also not reorder + * terms in a way that results in doing this. */ + if (flecs_term_match_multiple(term)) { + continue; + } } - if (desc->multi_threaded) { - system->multi_threaded = desc->multi_threaded; + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; } - if (desc->immediate) { - system->immediate = desc->immediate; - } + if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { + if (flecs_query_compile_term(world, impl, term, ctx)) { + return -1; + } - if (flecs_system_init_timer(world, entity, desc)) { - return 0; + *compiled |= (1llu << i); } } - flecs_poly_modified(world, entity, ecs_system_t); - - return entity; -error: return 0; } -const ecs_system_t* ecs_system_get( - const ecs_world_t *world, - ecs_entity_t entity) +int flecs_query_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_query_impl_t *query) { - return flecs_poly_get(world, entity, ecs_system_t); -} + /* Compile query to operations. Only necessary for non-trivial queries, as + * trivial queries use trivial iterators that don't use query ops. */ + bool needs_plan = true; + ecs_flags32_t flags = query->pub.flags; + ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; + if ((flags & trivial_flags) == trivial_flags) { + if (query->cache) { + if (flags & EcsQueryIsCacheable) { + needs_plan = false; + } + } else { + if (!(flags & EcsQueryMatchWildcards)) { + needs_plan = false; + } + } + } -void FlecsSystemImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsSystem); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsSystem), - "Module that implements Flecs systems"); -#endif + if (!needs_plan) { + /* Initialize space for $this variable */ + query->pub.var_count = 1; + query->var_count = 1; + query->var_size = 1; + query->vars = &flecs_this_array; + query->pub.vars = &flecs_this_name_array; + query->pub.flags |= EcsQueryHasTableThisVar; + return 0; + } - ecs_set_name_prefix(world, "Ecs"); + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + ecs_query_compile_ctx_t ctx = {0}; + ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); + ctx.ops = &stage->operations; + ctx.cur = ctx.ctrlflow; + ctx.cur->lbl_begin = -1; + ctx.cur->lbl_begin = -1; + ecs_vec_clear(ctx.ops); - flecs_bootstrap_tag(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); + /* Find all variables defined in query */ + if (flecs_query_discover_vars(stage, query)) { + return -1; + } - /* Make sure to never inherit system component. This makes sure that any - * term created for the System component will default to 'self' traversal, - * which improves efficiency of the query. */ - ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); -} + /* If query contains fixed source terms, insert operation to set sources */ + int32_t i, term_count = q->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.id & EcsIsEntity) { + ecs_query_op_t set_fixed = {0}; + set_fixed.kind = EcsQuerySetFixed; + flecs_query_op_insert(&set_fixed, &ctx); + break; + } + } -#endif + /* If the query contains terms with fixed ids (no wildcards, variables), + * insert instruction that initializes ecs_iter_t::ids. This allows for the + * insertion of simpler instructions later on. + * If the query is entirely cacheable, ids are populated by the cache. */ + if (q->cache_kind != EcsQueryCacheAll) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_term_is_fixed_id(q, term) || + (term->src.id & EcsIsEntity && + !(term->src.id & ~EcsTermRefFlags))) + { + ecs_query_op_t set_ids = {0}; + set_ids.kind = EcsQuerySetIds; + flecs_query_op_insert(&set_ids, &ctx); + break; + } + } + } -/** - * @file query/compiler/compile.c - * @brief Compile query program from query. - */ + ecs_flags64_t compiled = 0; + /* Always evaluate terms with fixed source before other terms */ + flecs_query_insert_fixed_src_terms( + world, query, &compiled, &ctx); -static -bool flecs_query_var_is_anonymous( - const ecs_query_impl_t *query, - ecs_var_id_t var_id) -{ - ecs_query_var_t *var = &query->vars[var_id]; - return var->anonymous; -} + /* Compile cacheable terms */ + flecs_query_insert_cache_search(query, &compiled, &ctx); -ecs_var_id_t flecs_query_add_var( - ecs_query_impl_t *query, - const char *name, - ecs_vec_t *vars, - ecs_var_kind_t kind) -{ - const char *dot = NULL; - if (name) { - dot = strchr(name, '.'); - if (dot) { - kind = EcsVarEntity; /* lookup variables are always entities */ + /* Insert trivial term search if query allows for it */ + flecs_query_insert_trivial_search(query, &compiled, &ctx); + + /* If a query starts with one or more optional terms, first compile the non + * optional terms. This prevents having to insert an instruction that + * matches the query against every entity in the storage. + * Only skip optional terms at the start of the query so that any + * short-circuiting behavior isn't affected (a non-optional term can become + * optional if it uses a variable set in an optional term). */ + int32_t start_term = 0; + for (; start_term < term_count; start_term ++) { + if (terms[start_term].oper != EcsOptional) { + break; } } - ecs_hashmap_t *var_index = NULL; - ecs_var_id_t var_id = EcsVarNone; - if (name) { - if (kind == EcsVarAny) { - var_id = flecs_query_find_var_id(query, name, EcsVarEntity); - if (var_id != EcsVarNone) { - return var_id; + do { + /* Compile remaining query terms to instructions */ + for (i = start_term; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t compile = i; + + if (compiled & (1ull << i)) { + continue; /* Already compiled */ } - var_id = flecs_query_find_var_id(query, name, EcsVarTable); - if (var_id != EcsVarNone) { - return var_id; + if (term->oper == EcsOptional && start_term) { + /* Don't reorder past the first optional term that's not in the + * initial list of optional terms. This protects short + * circuiting branching in the query. + * A future algorithm could look at which variables are + * accessed by optional terms, and continue reordering terms + * that don't access those variables. */ + break; } - kind = EcsVarTable; - } else { - var_id = flecs_query_find_var_id(query, name, kind); - if (var_id != EcsVarNone) { - return var_id; + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + can_reorder = false; + } + + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_query_term_is_unknown(query, term, &ctx)) + { + int32_t term_index = flecs_query_term_next_known( + query, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &q->terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } + + if (flecs_query_compile_term(world, query, term, &ctx)) { + return -1; } + + compiled |= (1ull << compile); } - if (kind == EcsVarTable) { - var_index = &query->tvar_index; + if (start_term) { + start_term = 0; /* Repeat, now also insert optional terms */ } else { - var_index = &query->evar_index; + break; } + } while (true); - /* If we're creating an entity var, check if it has a table variant */ - if (kind == EcsVarEntity && var_id == EcsVarNone) { - var_id = flecs_query_find_var_id(query, name, EcsVarTable); + ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); + if (this_id != EcsVarNone) { + /* If This variable has been written as entity, insert an operation to + * assign it to it.entities for consistency. */ + if (ctx.written & (1ull << this_id)) { + ecs_query_op_t set_this = {0}; + set_this.kind = EcsQuerySetThis; + set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); + set_this.first.var = this_id; + flecs_query_op_insert(&set_this, &ctx); } } - ecs_query_var_t *var; - ecs_var_id_t result; - if (vars) { - var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); - result = var->id = flecs_itovar(ecs_vec_count(vars)); - } else { - ecs_dbg_assert(query->var_count < query->var_size, - ECS_INTERNAL_ERROR, NULL); - var = &query->vars[query->var_count]; - result = var->id = flecs_itovar(query->var_count); - query->var_count ++; + /* Make sure non-This variables are written as entities */ + if (query->vars) { + for (i = 0; i < query->var_count; i ++) { + ecs_query_var_t *var = &query->vars[i]; + if (var->id && var->kind == EcsVarTable && var->name) { + ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, + EcsVarEntity); + if (!flecs_query_is_written(var_id, ctx.written)) { + /* Skip anonymous variables */ + if (!flecs_query_var_is_anonymous(query, var_id)) { + flecs_query_insert_each(var->id, var_id, &ctx, false); + } + } + } + } } - var->kind = flecs_ito(int8_t, kind); - var->name = name; - var->table_id = var_id; - var->base_id = 0; - var->lookup = NULL; - flecs_set_var_label(var, NULL); + /* If query contains non-This variables as term source, build lookup array */ + if (query->src_vars) { + ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); + bool only_anonymous = true; - if (name) { - flecs_name_index_init_if(var_index, NULL); - flecs_name_index_ensure(var_index, var->id, name, 0, 0); - var->anonymous = name[0] == '_'; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } - /* Handle variables that require a by-name lookup, e.g. $this.wheel */ - if (dot != NULL) { - ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); - var->lookup = dot + 1; + if (!flecs_query_var_is_anonymous(query, var_id)) { + only_anonymous = false; + break; + } else { + /* Don't fetch component data for anonymous variables. Because + * not all metadata (such as it.sources) is initialized for + * anonymous variables, and because they may only be available + * as table variables (each is not guaranteed to be inserted for + * anonymous variables) the iterator may not have sufficient + * information to resolve component data. */ + for (int32_t t = 0; t < q->term_count; t ++) { + ecs_term_t *term = &q->terms[t]; + if (term->field_index == i) { + term->inout = EcsInOutNone; + } + } + } } - } - return result; -} - -static -ecs_var_id_t flecs_query_add_var_for_term_id( - ecs_query_impl_t *query, - ecs_term_ref_t *term_id, - ecs_vec_t *vars, - ecs_var_kind_t kind) -{ - const char *name = flecs_term_ref_var_name(term_id); - if (!name) { - return EcsVarNone; - } + /* Don't insert setvar instruction if all vars are anonymous */ + if (!only_anonymous) { + ecs_query_op_t set_vars = {0}; + set_vars.kind = EcsQuerySetVars; + flecs_query_op_insert(&set_vars, &ctx); + } - return flecs_query_add_var(query, name, vars, kind); -} + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } -/* This function walks over terms to discover which variables are used in the - * query. It needs to provide the following functionality: - * - create table vars for all variables used as source - * - create entity vars for all variables not used as source - * - create entity vars for all non-$this vars - * - create anonymous vars to store the content of wildcards - * - create anonymous vars to store result of lookups (for $var.child_name) - * - create anonymous vars for resolving component inheritance - * - create array that stores the source variable for each field - * - ensure table vars for non-$this variables are anonymous - * - ensure variables created inside scopes are anonymous - * - place anonymous variables after public variables in vars array - */ -static -int flecs_query_discover_vars( - ecs_stage_t *stage, - ecs_query_impl_t *query) -{ - ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ - ecs_vec_reset_t(NULL, vars, ecs_query_var_t); + if (query->vars[var_id].kind == EcsVarTable) { + var_id = flecs_query_find_var_id(query, query->vars[var_id].name, + EcsVarEntity); - ecs_term_t *terms = query->pub.terms; - int32_t a, i, anonymous_count = 0, count = query->pub.term_count; - int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; - bool table_this = false, entity_before_table_this = false; + /* Variables used as source that aren't This must be entities */ + ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } - /* For This table lookups during discovery. This will be overwritten after - * discovery with whether the query actually has a This table variable. */ - query->pub.flags |= EcsQueryHasTableThisVar; + query->src_vars[i] = var_id; + } + } - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *second = &term->second; - ecs_term_ref_t *src = &term->src; + ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { - /* Keep track of which variables are first used in scope, so that we - * can mark them as anonymous. Terms inside a scope are collapsed - * into a single result, which means that outside of the scope the - * value of those variables is undefined. */ - if (!scope) { - scoped_var_index = ecs_vec_count(vars); - } - scope ++; - continue; - } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { - if (!--scope) { - /* Any new variables declared after entering a scope should be - * marked as anonymous. */ - int32_t v; - for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { - ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; - } - } - continue; + /* If query is empty, insert Nothing instruction */ + if (!(term_count - ctx.skipped)) { + ecs_vec_clear(ctx.ops); + ecs_query_op_t nothing = {0}; + nothing.kind = EcsQueryNothing; + flecs_query_op_insert(¬hing, &ctx); + } else { + /* If query contains terms for toggleable components, insert toggle */ + if (!(q->flags & EcsQueryTableOnly)) { + flecs_query_insert_toggle(query, &ctx); } - ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( - query, first, vars, EcsVarEntity); - if (first_var_id == EcsVarNone) { - /* If first is not a variable, check if we need to insert anonymous - * variable for resolving component inheritance */ - if (term->flags_ & EcsTermIdInherited) { - anonymous_count += 2; /* table & entity variable */ - } + /* Insert yield. If program reaches this operation, a result was found */ + ecs_query_op_t yield = {0}; + yield.kind = EcsQueryYield; + flecs_query_op_insert(&yield, &ctx); + } - /* If first is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(first)) { - anonymous_count ++; - } - } + int32_t op_count = ecs_vec_count(ctx.ops); + if (op_count) { + query->op_count = op_count; + query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); + ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); + ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); + } - if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { - const char *var_name = flecs_term_ref_var_name(src); - if (var_name) { - ecs_var_id_t var_id = flecs_query_find_var_id( - query, var_name, EcsVarEntity); - if (var_id == EcsVarNone || var_id == first_var_id) { - var_id = flecs_query_add_var( - query, var_name, vars, EcsVarEntity); - } + return 0; +} - if (var_id != EcsVarNone) { - /* Mark variable as one for which we need to create a table - * variable. Don't create table variable now, so that we can - * store it in the non-public part of the variable array. */ - ecs_query_var_t *var = ecs_vec_get_t( - vars, ecs_query_var_t, (int32_t)var_id - 1); - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - if (!var->lookup) { - var->kind = EcsVarAny; - anonymous_table_count ++; - } +/** + * @file query/compiler/compiler_term.c + * @brief Compile query term. + */ - if (((1llu << term->field_index) & query->pub.data_fields)) { - /* Can't have an anonymous variable as source of a term - * that returns a component. We need to return each - * instance of the component, whereas anonymous - * variables are not guaranteed to be resolved to - * individual entities. */ - if (var->anonymous) { - ecs_err( - "can't use anonymous variable '%s' as source of " - "data term", var->name); - goto error; - } - } - /* Track which variable ids are used as field source */ - if (!query->src_vars) { - query->src_vars = flecs_calloc_n(&stage->allocator, - ecs_var_id_t, query->pub.field_count); - } +#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + +ecs_var_id_t flecs_query_find_var_id( + const ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - query->src_vars[term->field_index] = var_id; - } + if (kind == EcsVarTable) { + if (!ecs_os_strcmp(name, EcsThisName)) { + if (query->pub.flags & EcsQueryHasTableThisVar) { + return 0; } else { - if (flecs_term_ref_is_wildcard(src)) { - anonymous_count ++; - } - } - } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { - if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { - flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); - } - } - - if (flecs_query_add_var_for_term_id( - query, second, vars, EcsVarEntity) == EcsVarNone) - { - /* If second is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(second)) { - anonymous_count ++; + return EcsVarNone; } } - if (src->id & EcsIsVariable && second->id & EcsIsVariable) { - if (term->flags_ & EcsTermTransitive) { - /* Anonymous variable to store temporary id for finding - * targets for transitive relationship, see compile_term. */ - anonymous_count ++; - } + if (!flecs_name_index_is_init(&query->tvar_index)) { + return EcsVarNone; } - /* If member term, make sure source is available as entity */ - if (term->flags_ & EcsTermIsMember) { - flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); + uint64_t index = flecs_name_index_find( + &query->tvar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } + return flecs_utovar(index); + } - /* Track if a This entity variable is used before a potential This table - * variable. If this happens, the query has no This table variable */ - if (ECS_TERM_REF_ID(src) == EcsThis) { - table_this = true; + if (kind == EcsVarEntity) { + if (!flecs_name_index_is_init(&query->evar_index)) { + return EcsVarNone; } - if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { - if (!table_this) { - entity_before_table_this = true; - } + uint64_t index = flecs_name_index_find( + &query->evar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } + return flecs_utovar(index); } - int32_t var_count = ecs_vec_count(vars); - ecs_var_id_t placeholder = EcsVarNone - 1; - bool replace_placeholders = false; + ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); - /* Ensure lookup variables have table and/or entity variables */ - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->lookup) { - char *var_name = ecs_os_strdup(var->name); - var_name[var->lookup - var->name - 1] = '\0'; + /* If searching for any kind of variable, start with most specific */ + ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); + if (index != EcsVarNone) { + return index; + } - ecs_var_id_t base_table_id = flecs_query_find_var_id( - query, var_name, EcsVarTable); - if (base_table_id != EcsVarNone) { - var->table_id = base_table_id; - } else if (anonymous_table_count) { - /* Scan for implicit anonymous table variables that haven't been - * inserted yet (happens after this step). Doing this here vs. - * ensures that anonymous variables are appended at the end of - * the variable array, while also ensuring that variable ids are - * stable (no swapping of table var ids that are in use). */ - for (a = 0; a < var_count; a ++) { - ecs_query_var_t *avar = ecs_vec_get_t( - vars, ecs_query_var_t, a); - if (avar->kind == EcsVarAny) { - if (!ecs_os_strcmp(avar->name, var_name)) { - base_table_id = (ecs_var_id_t)(a + 1); - break; - } - } - } - if (base_table_id != EcsVarNone) { - /* Set marker so we can set the new table id afterwards */ - var->table_id = placeholder; - replace_placeholders = true; - } - } + return flecs_query_find_var_id(query, name, EcsVarTable); +} - ecs_var_id_t base_entity_id = flecs_query_find_var_id( - query, var_name, EcsVarEntity); - if (base_entity_id == EcsVarNone) { - /* Get name from table var (must exist). We can't use allocated - * name since variables don't own names. */ - const char *base_name = NULL; - if (base_table_id != EcsVarNone && base_table_id) { - ecs_query_var_t *base_table_var = ecs_vec_get_t( - vars, ecs_query_var_t, (int32_t)base_table_id - 1); - base_name = base_table_var->name; - } else { - base_name = EcsThisName; - } +static +ecs_var_id_t flecs_query_most_specific_var( + ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx) +{ + if (kind == EcsVarTable || kind == EcsVarEntity) { + return flecs_query_find_var_id(query, name, kind); + } - base_entity_id = flecs_query_add_var( - query, base_name, vars, EcsVarEntity); - var = ecs_vec_get_t(vars, ecs_query_var_t, i); - } + ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); + if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { + /* If entity variable is available and written to, it contains the most + * specific result and should be used. */ + return evar; + } - var->base_id = base_entity_id; + ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } - ecs_os_free(var_name); + /* If table var is written, and entity var doesn't exist or is not written, + * return table var */ + if (tvar != EcsVarNone) { + return tvar; + } else { + return evar; + } +} + +ecs_query_lbl_t flecs_query_op_insert( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); + int32_t count = ecs_vec_count(ctx->ops); + *elem = *op; + if (count > 1) { + if (ctx->cur->lbl_begin == -1) { + /* Variables written by previous instruction can't be written by + * this instruction, except when this is part of an OR chain. */ + elem->written &= ~elem[-1].written; } } - var_count = ecs_vec_count(vars); + elem->next = flecs_itolbl(count); + elem->prev = flecs_itolbl(count - 2); + return flecs_itolbl(count - 1); +} - /* Add non-This table variables */ - if (anonymous_table_count) { - anonymous_table_count = 0; - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->kind == EcsVarAny) { - var->kind = EcsVarEntity; +ecs_query_op_t* flecs_query_begin_block( + ecs_query_op_kind_t kind, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t op = {0}; + op.kind = flecs_ito(uint8_t, kind); + ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); + return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); +} - ecs_var_id_t var_id = flecs_query_add_var( - query, var->name, vars, EcsVarTable); - ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; - anonymous_table_count ++; - } - } +void flecs_query_end_block( + ecs_query_compile_ctx_t *ctx, + bool reset) +{ + ecs_query_op_t new_op = {0}; + new_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); + + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_begin].next = end; - var_count = ecs_vec_count(vars); + ecs_query_op_t *end_op = &ops[end]; + if (reset && ctx->cur->lbl_query != -1) { + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op->prev = ctx->cur->lbl_begin; + end_op->src = query_op->src; + end_op->first = query_op->first; + end_op->second = query_op->second; + end_op->flags = query_op->flags; + end_op->field_index = query_op->field_index; + } else { + end_op->prev = ctx->cur->lbl_begin; + end_op->field_index = -1; } - /* If any forward references to newly added anonymous tables exist, replace - * them with the actual table variable ids. */ - if (replace_placeholders) { - for (i = 0; i < var_count; i ++) { - ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); - if (var->table_id == placeholder) { - char *var_name = ecs_os_strdup(var->name); - var_name[var->lookup - var->name - 1] = '\0'; + ctx->cur->lbl_begin = -1; +} - var->table_id = flecs_query_find_var_id( - query, var_name, EcsVarTable); - ecs_assert(var->table_id != EcsVarNone, - ECS_INTERNAL_ERROR, NULL); +static +void flecs_query_begin_block_cond_eval( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + ecs_write_flags_t cond_write_state) +{ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + ecs_write_flags_t cond_mask = 0; - ecs_os_free(var_name); - } - } + if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { + first_var = op->first.var; + ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << first_var); + } + if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { + second_var = op->second.var; + ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << second_var); } - - /* Always include spot for This variable, even if query doesn't use it */ - var_count ++; - - ecs_query_var_t *query_vars = &flecs_this_array; - if ((var_count + anonymous_count) > 1) { - query_vars = flecs_alloc(&stage->allocator, - (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * - (var_count + anonymous_count)); + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { + src_var = op->src.var; + ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << src_var); } - query->vars = query_vars; - query->var_count = var_count; - query->pub.var_count = flecs_ito(int8_t, var_count); - ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, - !entity_before_table_this); - query->var_size = var_count + anonymous_count; - - char **var_names; - if (query_vars != &flecs_this_array) { - query_vars[0].kind = EcsVarTable; - query_vars[0].name = NULL; - flecs_set_var_label(&query_vars[0], NULL); - query_vars[0].id = 0; - query_vars[0].table_id = EcsVarNone; - query_vars[0].lookup = NULL; - - var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), - var_count + anonymous_count); - var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); - } else { - var_names = &flecs_this_name_array; + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; } - query->pub.vars = (char**)var_names; + /* If this term uses conditionally set variables, insert instruction that + * jumps over the term if the variables weren't set yet. */ + if (cond_mask & cond_write_state) { + ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); - query_vars ++; - var_names ++; - var_count --; + ecs_query_op_t jmp_op = {0}; + jmp_op.kind = EcsQueryIfVar; - if (var_count) { - ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); - ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); - for (i = 0; i < var_count; i ++) { - ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], - ECS_INTERNAL_ERROR, NULL); - var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); + if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); + jmp_op.first.var = first_var; + } + if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); + jmp_op.second.var = second_var; + } + if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + jmp_op.src.var = src_var; } - } - - /* Hide anonymous table variables from application */ - query->pub.var_count = - flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - /* Sanity check to make sure that the public part of the variable array only - * contains entity variables. */ -#ifdef FLECS_DEBUG - for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { - ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); + flecs_query_op_insert(&jmp_op, ctx); + } else { + ctx->cur->lbl_cond_eval = -1; } -#endif - - return 0; -error: - return -1; } static -bool flecs_query_var_is_unknown( - ecs_query_impl_t *query, - ecs_var_id_t var_id, +void flecs_query_end_block_cond_eval( ecs_query_compile_ctx_t *ctx) { - ecs_query_var_t *vars = query->vars; - if (ctx->written & (1ull << var_id)) { - return false; - } else { - ecs_var_id_t table_var = vars[var_id].table_id; - if (table_var != EcsVarNone) { - return flecs_query_var_is_unknown(query, table_var, ctx); - } + if (ctx->cur->lbl_cond_eval == -1) { + return; } - return true; + + ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_query_op_t end_op = {0}; + end_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); + + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_cond_eval].next = end; + + ecs_query_op_t *end_op_ptr = &ops[end]; + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op_ptr->prev = ctx->cur->lbl_cond_eval; + end_op_ptr->src = query_op->src; + end_op_ptr->first = query_op->first; + end_op_ptr->second = query_op->second; + end_op_ptr->flags = query_op->flags; + end_op_ptr->field_index = query_op->field_index; } -/* Returns whether term is unknown. A term is unknown when it has variable - * elements (first, second, src) that are all unknown. */ static -bool flecs_query_term_is_unknown( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) +void flecs_query_begin_block_or( + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) { - ecs_query_op_t dummy = {0}; - flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, - &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, - &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, - &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); - - bool has_vars = dummy.flags & - ((EcsQueryIsVar << EcsQueryFirst) | - (EcsQueryIsVar << EcsQuerySecond) | - (EcsQueryIsVar << EcsQuerySrc)); - if (!has_vars) { - /* If term has no variables (typically terms with a static src) there - * can't be anything that's unknown. */ - return false; - } + ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); + or_op->kind = EcsQueryOr; + or_op->field_index = term->field_index; - if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { - if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { - return false; - } - } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { - return false; + /* Set the source of the evaluate terms as source of the Or instruction. + * This lets the engine determine whether the variable has already been + * written. When the source is not yet written, an OR operation needs to + * take the union of all the terms in the OR chain. When the variable is + * known, it will return after the first matching term. + * + * In case a term in the OR expression is an equality predicate which + * compares the left hand side with a variable, the variable acts as an + * alias, so we can always assume that it's written. */ + bool add_src = true; + if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { + if (!(flecs_query_is_written(op->src.var, ctx->written))) { + add_src = false; } } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { - return false; + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (add_src) { + or_op->flags = (EcsQueryIsVar << EcsQuerySrc); + or_op->src = op->src; + ctx->cur->src_or = op->src; } - } - return true; + ctx->cur->src_written_or = flecs_query_is_written( + op->src.var, ctx->written); + } } -/* Find the next known term from specified offset. This function is used to find - * a term that can be evaluated before a term that is unknown. Evaluating known - * before unknown terms can significantly decrease the search space. */ static -int32_t flecs_query_term_next_known( - ecs_query_impl_t *query, - ecs_query_compile_ctx_t *ctx, - int32_t offset, - ecs_flags64_t compiled) +void flecs_query_end_block_or( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) { - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; + ecs_query_op_t op = {0}; + op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); - for (i = offset; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (compiled & (1ull << i)) { - continue; + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + int32_t i, prev_or = ctx->cur->lbl_begin + 1; + for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { + if (ops[i].next == FlecsRuleOrMarker) { + if (i == (end - 1)) { + ops[prev_or].prev = ctx->cur->lbl_begin; + } else { + ops[prev_or].prev = flecs_itolbl(i + 1); + } + + ops[i].next = flecs_itolbl(end); + + prev_or = i + 1; } + } - /* Only evaluate And terms */ - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - continue; + ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; + bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); + first->next = flecs_itolbl(end); + ops[end].prev = ctx->cur->lbl_begin; + ops[end - 1].prev = ctx->cur->lbl_begin; + + ctx->ctrlflow->in_or = false; + ctx->cur->lbl_begin = -1; + if (src_is_var) { + ecs_var_id_t src_var = first->src.var; + ctx->written |= (1llu << src_var); + + /* If src is a table variable, it is possible that this was resolved to + * an entity variable in all of the OR terms. If this is the case, mark + * entity variable as written as well. */ + ecs_query_var_t *var = &impl->vars[src_var]; + if (var->kind == EcsVarTable) { + const char *name = var->name; + if (!name) { + name = "this"; + } + + ecs_var_id_t evar = flecs_query_find_var_id( + impl, name, EcsVarEntity); + if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { + ctx->written |= (1llu << evar); + ctx->cond_written &= ~(1llu << evar); + } } + } + ctx->written |= ctx->cond_written; + + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { + /* Skip variable if it's the source for the OR chain */ + if (src_is_var && (i == first->src.var)) { continue; } - if (flecs_query_term_is_unknown(query, term, ctx)) { - continue; + if (!prev && cur) { + ecs_query_op_t reset_op = {0}; + reset_op.kind = EcsQueryReset; + reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + reset_op.src.var = flecs_itovar(i); + flecs_query_op_insert(&reset_op, ctx); } - - return i; } +} - return -1; +void flecs_query_insert_each( + ecs_var_id_t tvar, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t each = {0}; + each.kind = EcsQueryEach; + each.src.var = evar; + each.first.var = tvar; + each.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &each.written); + flecs_query_op_insert(&each, ctx); } -/* If the first part of a query contains more than one trivial term, insert a - * special instruction which batch-evaluates multiple terms. */ static -void flecs_query_insert_trivial_search( +void flecs_query_insert_lookup( + ecs_var_id_t base_var, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t lookup = {0}; + lookup.kind = EcsQueryLookup; + lookup.src.var = evar; + lookup.first.var = base_var; + lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &lookup.written); + flecs_query_op_insert(&lookup, ctx); +} + +static +void flecs_query_insert_unconstrained_transitive( ecs_query_impl_t *query, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) { - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - int32_t i, term_count = q->term_count; - ecs_flags64_t trivial_set = 0; + /* Create anonymous variable to store the target ids. This will return the + * list of targets without constraining the variable of the term, which + * needs to stay variable to find all transitive relationships for a src. */ + ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); - /* Trivial search always ignores prefabs and disabled entities */ - if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { - return; - } + /* First, find ids to start traversal from. This fixes op.second. */ + ecs_query_op_t find_ids = {0}; + find_ids.kind = EcsQueryIdsRight; + find_ids.field_index = -1; + find_ids.first = op->first; + find_ids.second = op->second; + find_ids.flags = op->flags; + find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); + find_ids.second.var = tgt; + flecs_query_write_ctx(tgt, ctx, cond_write); + flecs_query_write(tgt, &find_ids.written); + flecs_query_op_insert(&find_ids, ctx); - /* Find trivial terms, which can be handled in single instruction */ - int32_t trivial_wildcard_terms = 0; - int32_t trivial_terms = 0; + /* Next, iterate all tables for the ids. This fixes op.src */ + ecs_query_op_t and_op = {0}; + and_op.kind = EcsQueryAnd; + and_op.field_index = op->field_index; + and_op.first = op->first; + and_op.second = op->second; + and_op.src = op->src; + and_op.flags = op->flags | EcsQueryIsSelf; + and_op.second.var = tgt; + flecs_query_write_ctx(and_op.src.var, ctx, cond_write); + flecs_query_write(and_op.src.var, &and_op.written); + flecs_query_op_insert(&and_op, ctx); +} - for (i = 0; i < term_count; i ++) { - /* Term is already compiled */ - if (*compiled & (1ull << i)) { - continue; - } +static +void flecs_query_insert_inheritance( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + /* Anonymous variable to store the resolved component ids */ + ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsTrivial)) { - continue; - } + flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); + flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); - /* We can only add trivial terms to plan if they no up traversal */ - if ((term->src.id & EcsTraverseFlags) != EcsSelf) { - continue; - } + ecs_query_op_t trav_op = {0}; + trav_op.kind = EcsQueryTrav; + trav_op.field_index = -1; + trav_op.first.entity = EcsIsA; + trav_op.second.entity = ECS_TERM_REF_ID(&term->first); + trav_op.src.var = tvar; + trav_op.flags = EcsQueryIsSelf; + trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); + trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + trav_op.written |= (1ull << tvar); + if (term->first.id & EcsSelf) { + trav_op.match_flags |= EcsTermReflexive; + } + flecs_query_op_insert(&trav_op, ctx); + flecs_query_insert_each(tvar, evar, ctx, cond_write); - /* Wildcards are not supported for trivial queries */ - if (ecs_id_is_wildcard(term->id)) { - continue; - } + ecs_query_ref_t r = { .var = evar }; + op->first = r; + op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op->flags |= (EcsQueryIsVar << EcsQueryFirst); +} - trivial_set |= (1llu << i); +void flecs_query_compile_term_ref( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_term_ref_t *term_ref, + ecs_query_ref_t *ref, + ecs_flags8_t ref_kind, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx, + bool create_wildcard_vars) +{ + (void)world; - trivial_terms ++; + if (!ecs_term_ref_is_set(term_ref)) { + return; } - if (trivial_terms >= 2) { - /* Mark terms as compiled & populated */ - for (i = 0; i < q->term_count; i ++) { - if (trivial_set & (1llu << i)) { - *compiled |= (1ull << i); + if (term_ref->id & EcsIsVariable) { + op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); + const char *name = flecs_term_ref_var_name(term_ref); + if (name) { + ref->var = flecs_query_most_specific_var(query, name, kind, ctx); + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if (create_wildcard_vars) { + bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); + if (is_wildcard && (kind == EcsVarAny)) { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + } else { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); } + if (is_wildcard) { + flecs_set_var_label(&query->vars[ref->var], + ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); + } + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } + } - /* If there's more than 1 trivial term, batch them in trivial search */ - ecs_query_op_t trivial = {0}; - if (!trivial_wildcard_terms) { - trivial.kind = EcsQueryTriv; - } - - /* Store the bitset with trivial terms on the instruction */ - trivial.src.entity = trivial_set; - flecs_query_op_insert(&trivial, ctx); - - /* Mark $this as written */ - ctx->written |= (1llu << 0); + if (term_ref->id & EcsIsEntity) { + op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); + ref->entity = ECS_TERM_REF_ID(term_ref); } } static -void flecs_query_insert_cache_search( +int flecs_query_compile_ensure_vars( ecs_query_impl_t *query, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx, + bool cond_write, + bool *written_out) { - if (!query->cache) { - return; - } + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + bool written = false; - ecs_query_t *q = &query->pub; + if (flags & EcsQueryIsVar) { + ecs_var_id_t var_id = ref->var; + ecs_query_var_t *var = &query->vars[var_id]; - if (q->cache_kind == EcsQueryCacheAll) { - /* If all terms are cacheable, make sure no other terms are compiled */ - *compiled = 0xFFFFFFFFFFFFFFFF; - } else if (q->cache_kind == EcsQueryCacheAuto) { - /* The query is partially cacheable */ - ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; + if (var->kind == EcsVarEntity && + !flecs_query_is_written(var_id, ctx->written)) + { + /* If entity variable is not yet written but a table variant exists + * that has been written, insert each operation to translate from + * entity variable to table */ + ecs_var_id_t tvar = var->table_id; + if ((tvar != EcsVarNone) && + flecs_query_is_written(tvar, ctx->written)) + { + if (var->lookup) { + if (!flecs_query_is_written(tvar, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } + + if (!flecs_query_is_written(var->base_id, ctx->written)) { + flecs_query_insert_each( + tvar, var->base_id, ctx, cond_write); + } + } else { + flecs_query_insert_each(tvar, var_id, ctx, cond_write); + } - for (i = 0; i < count; i ++) { - if ((*compiled) & (1ull << i)) { - continue; + /* Variable was written, just not as entity */ + written = true; + } else if (var->lookup) { + if (!flecs_query_is_written(var->base_id, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } } + } - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsCacheable)) { - continue; - } + written |= flecs_query_is_written(var_id, ctx->written); + } else { + /* If it's not a variable, it's always written */ + written = true; + } - *compiled |= (1ull << i); - } + if (written_out) { + *written_out = written; } - /* Insert the operation for cache traversal */ - ecs_query_op_t op = {0}; + return 0; +} - if (q->flags & EcsQueryIsCacheable) { - op.kind = EcsQueryIsCache; +static +bool flecs_query_compile_lookup( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_var_t *var = &query->vars[var_id]; + if (var->lookup) { + flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); + return true; } else { - op.kind = EcsQueryCache; + return false; } - - flecs_query_write(0, &op.written); - flecs_query_write_ctx(0, ctx, false); - flecs_query_op_insert(&op, ctx); } static -bool flecs_term_ref_match_multiple( - ecs_term_ref_t *ref) +void flecs_query_insert_contains( + ecs_query_impl_t *query, + ecs_var_id_t src_var, + ecs_var_id_t other_var, + ecs_query_compile_ctx_t *ctx) { - return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); + ecs_query_op_t contains = {0}; + if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { + contains.kind = EcsQueryContain; + contains.src.var = src_var; + contains.first.var = other_var; + contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_op_insert(&contains, ctx); + } } static -bool flecs_term_match_multiple( - ecs_term_t *term) +void flecs_query_insert_pair_eq( + int32_t field_index, + ecs_query_compile_ctx_t *ctx) { - return flecs_term_ref_match_multiple(&term->first) || - flecs_term_ref_match_multiple(&term->second); + ecs_query_op_t contains = {0}; + contains.kind = EcsQueryPairEq; + contains.field_index = flecs_ito(int8_t, field_index); + flecs_query_op_insert(&contains, ctx); } static -int flecs_query_insert_toggle( - ecs_query_impl_t *impl, - ecs_query_compile_ctx_t *ctx) +int flecs_query_compile_builtin_pred( + ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_write_flags_t write_state) { - ecs_query_t *q = &impl->pub; - int32_t i, j, term_count = q->term_count; - ecs_term_t *terms = q->terms; - ecs_flags64_t fields_done = 0; + ecs_entity_t id = ECS_TERM_REF_ID(&term->first); - for (i = 0; i < term_count; i ++) { - if (fields_done & (1llu << i)) { - continue; - } + ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; + ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; + ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; + + ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_term_t *term = &terms[i]; - if (term->flags_ & EcsTermIsToggle) { - ecs_query_op_t cur = {0}; - flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, - &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); + if (id == EcsPredEq) { + if (term->second.id & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); + } - ecs_flags64_t and_toggles = 0; - ecs_flags64_t not_toggles = 0; - ecs_flags64_t optional_toggles = 0; + if (flags_2nd & EcsQueryIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", ecs_query_var_name(q, op->second.var)); + return -1; + } + } - for (j = i; j < term_count; j ++) { - if (fields_done & (1llu << j)) { - continue; - } + ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); + (void)flags_src; - /* Also includes term[i], so flags get set correctly */ - term = &terms[j]; + if (!(write_state & (1ull << op->src.var))) { + /* If this is an == operator with a right-hand side that resolves to a + * single entity, the left-hand side is allowed to be undefined, as the + * instruction will be evaluated as an assignment. */ + if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { + ecs_err("uninitialized variable '%s' on left-hand side of " + "equality operator", ecs_query_var_name(q, op->src.var)); + return -1; + } + } - /* If term is not for the same src, skip */ - ecs_query_op_t next = {0}; - flecs_query_compile_term_ref(NULL, impl, &next, &term->src, - &next.src, EcsQuerySrc, EcsVarAny, ctx, false); - if (next.src.entity != cur.src.entity || - next.flags != cur.flags) - { - continue; - } + return 0; +} - /* Source matches, set flag */ - if (term->oper == EcsNot) { - not_toggles |= (1llu << j); - } else if (term->oper == EcsOptional) { - optional_toggles |= (1llu << j); - } else { - and_toggles |= (1llu << j); - } +static +int flecs_query_ensure_scope_var( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx) +{ + ecs_var_id_t var = ref->var; - fields_done |= (1llu << j); + if (query->vars[var].kind == EcsVarEntity && + !flecs_query_is_written(var, ctx->written)) + { + ecs_var_id_t table_var = query->vars[var].table_id; + if (table_var != EcsVarNone && + flecs_query_is_written(table_var, ctx->written)) + { + if (flecs_query_compile_ensure_vars( + query, op, ref, ref_kind, ctx, false, NULL)) + { + goto error; } + } + } - if (and_toggles || not_toggles) { - ecs_query_op_t op = {0}; - op.kind = EcsQueryToggle; - op.src = cur.src; - op.flags = cur.flags; + return 0; +error: + return -1; +} - if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_write(op.src.var, &op.written); - } +static +int flecs_query_ensure_scope_vars( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + ecs_term_t *term) +{ + /* If the scope uses variables as entity that have only been written as + * table, resolve them as entities before entering the scope. */ + ecs_term_t *cur = term; + while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { + /* Dummy operation to obtain variable information for term */ + ecs_query_op_t op = {0}; + flecs_query_compile_term_ref(world, query, &op, &cur->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(world, query, &op, &cur->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); - /* Encode fields: - * - first.entity is the fields that match enabled bits - * - second.entity is the fields that match disabled bits - */ - op.first.entity = and_toggles; - op.second.entity = not_toggles; - flecs_query_op_insert(&op, ctx); + if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.first, EcsQueryFirst, ctx)) + { + goto error; } + } - /* Insert separate instructions for optional terms. To make sure - * entities are returned in batches where fields are never partially - * set or unset, the result must be split up into batches that have - * the exact same toggle masks. Instead of complicating the toggle - * instruction with code to scan for blocks that have the same bits - * set, separate instructions let the query engine backtrack to get - * the right results. */ - if (optional_toggles) { - for (j = i; j < term_count; j ++) { - uint64_t field_bit = 1ull << j; - if (!(optional_toggles & field_bit)) { - continue; - } - - ecs_query_op_t op = {0}; - op.kind = EcsQueryToggleOption; - op.src = cur.src; - op.first.entity = field_bit; - op.flags = cur.flags; - flecs_query_op_insert(&op, ctx); - } + if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.second, EcsQuerySecond, ctx)) + { + goto error; } } + + cur ++; } return 0; +error: + return -1; } static -int flecs_query_insert_fixed_src_terms( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_flags64_t *compiled, +void flecs_query_compile_push( ecs_query_compile_ctx_t *ctx) { - ecs_query_t *q = &impl->pub; - int32_t i, term_count = q->term_count; - ecs_term_t *terms = q->terms; + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_begin = -1; + ctx->cur->lbl_begin = -1; +} - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; +static +void flecs_query_compile_pop( + ecs_query_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; +} - if (term->oper == EcsNot) { - /* If term has not operator and variables for first/second, we can't - * put the term first as this could prevent us from getting back - * valid results. For example: - * !$var(e), Tag($var) - * - * Here, the first term would evaluate to false (and cause the - * entire query not to match) if 'e' has any components. - * - * However, when reordering we get results: - * Tag($var), !$var(e) - * - * Now the query returns all entities with Tag, that 'e' does not - * have as component. For this reason, queries should never use - * unwritten variables in not terms- and we should also not reorder - * terms in a way that results in doing this. */ - if (flecs_term_match_multiple(term)) { - continue; - } +static +int flecs_query_compile_0_src( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + /* If the term has a 0 source, check if it's a scope open/close */ + if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { + if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { + goto error; } - - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { - continue; + if (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + flecs_query_begin_block(EcsQueryNot, ctx); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + flecs_query_compile_push(ctx); + } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { + flecs_query_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { + flecs_query_end_block(ctx, false); } + } else { + /* Noop */ + } - if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { - if (flecs_query_compile_term(world, impl, term, ctx)) { - return -1; - } + return 0; +error: + return -1; +} - *compiled |= (1llu << i); +static +ecs_flags32_t flecs_query_to_table_flags( + const ecs_query_t *q) +{ + ecs_flags32_t query_flags = q->flags; + if (!(query_flags & EcsQueryMatchDisabled) || + !(query_flags & EcsQueryMatchPrefab)) + { + ecs_flags32_t table_flags = EcsTableNotQueryable; + if (!(query_flags & EcsQueryMatchDisabled)) { + table_flags |= EcsTableIsDisabled; + } + if (!(query_flags & EcsQueryMatchPrefab)) { + table_flags |= EcsTableIsPrefab; } - } + return table_flags; + } return 0; } -int flecs_query_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_query_impl_t *query) +static +bool flecs_query_select_all( + const ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_var_id_t src_var, + ecs_query_compile_ctx_t *ctx) { - /* Compile query to operations. Only necessary for non-trivial queries, as - * trivial queries use trivial iterators that don't use query ops. */ - bool needs_plan = true; - ecs_flags32_t flags = query->pub.flags; - ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; - if ((flags & trivial_flags) == trivial_flags) { - if (query->cache) { - if (flags & EcsQueryIsCacheable) { - needs_plan = false; - } + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; + + if (term->oper == EcsNot || term->oper == EcsOptional || + term->oper == EcsNotFrom || pred_match) + { + ecs_query_op_t match_any = {0}; + match_any.kind = EcsAnd; + match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); + match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); + match_any.src = op->src; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; } else { - if (!(flags & EcsQueryMatchWildcards)) { - needs_plan = false; - } + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); } - } + match_any.written = (1ull << src_var); + match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); + flecs_query_op_insert(&match_any, ctx); + flecs_query_write_ctx(op->src.var, ctx, false); - if (!needs_plan) { - /* Initialize space for $this variable */ - query->pub.var_count = 1; - query->var_count = 1; - query->var_size = 1; - query->vars = &flecs_this_array; - query->pub.vars = &flecs_this_name_array; - query->pub.flags |= EcsQueryHasTableThisVar; - return 0; + /* Update write administration */ + return true; } + return false; +} - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - ecs_query_compile_ctx_t ctx = {0}; - ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); - ctx.ops = &stage->operations; - ctx.cur = ctx.ctrlflow; - ctx.cur->lbl_begin = -1; - ctx.cur->lbl_begin = -1; - ecs_vec_clear(ctx.ops); +#ifdef FLECS_META +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); - /* Find all variables defined in query */ - if (flecs_query_discover_vars(stage, query)) { + first_id = ECS_TERM_REF_ID(&term->first); + + /* First compile as if it's a regular term, to match the component */ + term->flags_ &= (uint16_t)~EcsTermIsMember; + + /* Replace term id with member parent (the component) */ + ecs_entity_t component = ecs_get_parent(world, first_id); + if (!component) { + ecs_err("member without parent in query"); return -1; } - /* If query contains fixed source terms, insert operation to set sources */ - int32_t i, term_count = q->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->src.id & EcsIsEntity) { - ecs_query_op_t set_fixed = {0}; - set_fixed.kind = EcsQuerySetFixed; - flecs_query_op_insert(&set_fixed, &ctx); - break; - } + if (!ecs_has(world, component, EcsComponent)) { + ecs_err("parent of member is not a component"); + return -1; + } + + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; + + term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); + term->second.id = 0; + term->id = component; + + ctx->oper = (ecs_oper_kind_t)term->oper; + if (term->oper == EcsNot && !second_wildcard) { + /* When matching a member term with not operator, we need to cover both + * the case where an entity doesn't have the component, and where it + * does have the component, but doesn't match the member. */ + term->oper = EcsOptional; } - /* If the query contains terms with fixed ids (no wildcards, variables), - * insert instruction that initializes ecs_iter_t::ids. This allows for the - * insertion of simpler instructions later on. - * If the query is entirely cacheable, ids are populated by the cache. */ - if (q->cache_kind != EcsQueryCacheAll) { - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (flecs_term_is_fixed_id(q, term) || - (term->src.id & EcsIsEntity && - !(term->src.id & ~EcsTermRefFlags))) - { - ecs_query_op_t set_ids = {0}; - set_ids.kind = EcsQuerySetIds; - flecs_query_op_insert(&set_ids, &ctx); - break; - } - } + return 0; +} + +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + ecs_entity_t component = ECS_TERM_REF_ID(&term->first); + const EcsComponent *comp = ecs_get(world, component, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Restore term values */ + term->id = term_id; + term->first.id = first_id; + term->second.id = second_id; + term->flags_ |= EcsTermIsMember; + term->oper = flecs_ito(int16_t, ctx->oper); + + first_id = ECS_TERM_REF_ID(&term->first); + const EcsMember *member = ecs_get(world, first_id, EcsMember); + ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_var_t *var = &impl->vars[op->src.var]; + const char *var_name = flecs_term_ref_var_name(&term->src); + ecs_var_id_t evar = flecs_query_find_var_id( + impl, var_name, EcsVarEntity); + + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; + + if (term->oper == EcsOptional) { + second_wildcard = true; } - ecs_flags64_t compiled = 0; + ecs_query_op_t mbr_op = *op; + mbr_op.kind = EcsQueryMemberEq; + mbr_op.first.entity = /* Encode type size and member offset */ + flecs_ito(uint32_t, member->offset) | + (flecs_ito(uint64_t, comp->size) << 32); - /* Always evaluate terms with fixed source before other terms */ - flecs_query_insert_fixed_src_terms( - world, query, &compiled, &ctx); + /* If this is a term with a Not operator, conditionally evaluate member on + * whether term was set by previous operation (see begin_member_term). */ + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + if (second_wildcard && ctx->oper == EcsNot) { + /* A !(T.value, *) term doesn't need special operations */ + return 0; + } - /* Compile cacheable terms */ - flecs_query_insert_cache_search(query, &compiled, &ctx); + /* Resolve to entity variable before entering if block, so that we + * don't have different branches of the query working with different + * versions of the same variable. */ + if (var->kind == EcsVarTable) { + flecs_query_insert_each(op->src.var, evar, ctx, cond_write); + var = &impl->vars[evar]; + } - /* Insert trivial term search if query allows for it */ - flecs_query_insert_trivial_search(query, &compiled, &ctx); + ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); + if_op->other = term->field_index; - /* If a query starts with one or more optional terms, first compile the non - * optional terms. This prevents having to insert an instruction that - * matches the query against every entity in the storage. - * Only skip optional terms at the start of the query so that any - * short-circuiting behavior isn't affected (a non-optional term can become - * optional if it uses a variable set in an optional term). */ - int32_t start_term = 0; - for (; start_term < term_count; start_term ++) { - if (terms[start_term].oper != EcsOptional) { - break; + if (ctx->oper == EcsNot) { + mbr_op.kind = EcsQueryMemberNeq; } } - do { - /* Compile remaining query terms to instructions */ - for (i = start_term; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int32_t compile = i; - - if (compiled & (1ull << i)) { - continue; /* Already compiled */ - } + if (var->kind == EcsVarTable) { + /* If MemberEq is called on table variable, store it on .other member. + * This causes MemberEq to do double duty as 'each' instruction, + * which is faster than having to go back & forth between instructions + * while finding matching values. */ + mbr_op.other = flecs_itolbl(op->src.var + 1); - if (term->oper == EcsOptional && start_term) { - /* Don't reorder past the first optional term that's not in the - * initial list of optional terms. This protects short - * circuiting branching in the query. - * A future algorithm could look at which variables are - * accessed by optional terms, and continue reordering terms - * that don't access those variables. */ - break; - } + /* Mark entity variable as written */ + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &mbr_op.written); + } - bool can_reorder = true; - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - can_reorder = false; - } + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, + &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - /* If variables have been written, but this term has no known variables, - * first try to resolve terms that have known variables. This can - * significantly reduce the search space. - * Only perform this optimization after at least one variable has been - * written to, as all terms are unknown otherwise. */ - if (can_reorder && ctx.written && - flecs_query_term_is_unknown(query, term, &ctx)) + if (second_wildcard) { + mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + mbr_op.second.entity = EcsWildcard; + } else { + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, + &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + + if (term->second.id & EcsIsVariable) { + if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, + EcsQuerySecond, ctx, cond_write, NULL)) { - int32_t term_index = flecs_query_term_next_known( - query, &ctx, i + 1, compiled); - if (term_index != -1) { - term = &q->terms[term_index]; - compile = term_index; - i --; /* Repeat current term */ - } - } - - if (flecs_query_compile_term(world, query, term, &ctx)) { - return -1; + goto error; } - compiled |= (1ull << compile); + flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); + flecs_query_write(mbr_op.second.var, &mbr_op.written); } + } - if (start_term) { - start_term = 0; /* Repeat, now also insert optional terms */ - } else { - break; - } - } while (true); + flecs_query_op_insert(&mbr_op, ctx); - ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); - if (this_id != EcsVarNone) { - /* If This variable has been written as entity, insert an operation to - * assign it to it.entities for consistency. */ - if (ctx.written & (1ull << this_id)) { - ecs_query_op_t set_this = {0}; - set_this.kind = EcsQuerySetThis; - set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); - set_this.first.var = this_id; - flecs_query_op_insert(&set_this, &ctx); - } + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + flecs_query_end_block(ctx, false); } - /* Make sure non-This variables are written as entities */ - if (query->vars) { - for (i = 0; i < query->var_count; i ++) { - ecs_query_var_t *var = &query->vars[i]; - if (var->id && var->kind == EcsVarTable && var->name) { - ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, - EcsVarEntity); - if (!flecs_query_is_written(var_id, ctx.written)) { - /* Skip anonymous variables */ - if (!flecs_query_var_is_anonymous(query, var_id)) { - flecs_query_insert_each(var->id, var_id, &ctx, false); - } - } - } - } - } + return 0; +error: + return -1; +} +#else +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + (void)world; (void)term; (void)ctx; (void)first_id; + return 0; +} - /* If query contains non-This variables as term source, build lookup array */ - if (query->src_vars) { - ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); - bool only_anonymous = true; +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; + (void)first_id; (void)second_id; (void)cond_write; + return 0; +} +#endif - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; - } +static +void flecs_query_mark_last_or_op( + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); + op_ptr->next = FlecsRuleOrMarker; +} - if (!flecs_query_var_is_anonymous(query, var_id)) { - only_anonymous = false; - break; - } else { - /* Don't fetch component data for anonymous variables. Because - * not all metadata (such as it.sources) is initialized for - * anonymous variables, and because they may only be available - * as table variables (each is not guaranteed to be inserted for - * anonymous variables) the iterator may not have sufficient - * information to resolve component data. */ - for (int32_t t = 0; t < q->term_count; t ++) { - ecs_term_t *term = &q->terms[t]; - if (term->field_index == i) { - term->inout = EcsInOutNone; - } +static +void flecs_query_set_op_kind( + ecs_query_op_t *op, + ecs_term_t *term, + bool src_is_var) +{ + /* Default instruction for And operators. If the source is fixed (like for + * singletons or terms with an entity source), use With, which like And but + * just matches against a source (vs. finding a source). */ + op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; + + /* Ignore cascade flag */ + ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); + + /* Handle *From operators */ + if (term->oper == EcsAndFrom) { + op->kind = EcsQueryAndFrom; + } else if (term->oper == EcsOrFrom) { + op->kind = EcsQueryOrFrom; + } else if (term->oper == EcsNotFrom) { + op->kind = EcsQueryNotFrom; + + /* If query is transitive, use Trav(ersal) instruction */ + } else if (term->flags_ & EcsTermTransitive) { + ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); + op->kind = EcsQueryTrav; + + /* If term queries for union pair, use union instruction */ + } else if (term->flags_ & EcsTermIsUnion) { + if (op->kind == EcsQueryAnd) { + op->kind = EcsQueryUnionEq; + if (term->oper == EcsNot) { + if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { + term->oper = EcsAnd; + op->kind = EcsQueryUnionNeq; } } + } else { + op->kind = EcsQueryUnionEqWith; } - /* Don't insert setvar instruction if all vars are anonymous */ - if (!only_anonymous) { - ecs_query_op_t set_vars = {0}; - set_vars.kind = EcsQuerySetVars; - flecs_query_op_insert(&set_vars, &ctx); - } - - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; + if ((term->src.id & trav_flags) == EcsUp) { + if (op->kind == EcsQueryUnionEq) { + op->kind = EcsQueryUnionEqUp; } - - if (query->vars[var_id].kind == EcsVarTable) { - var_id = flecs_query_find_var_id(query, query->vars[var_id].name, - EcsVarEntity); - - /* Variables used as source that aren't This must be entities */ - ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + if (op->kind == EcsQueryUnionEq) { + op->kind = EcsQueryUnionEqSelfUp; } - - query->src_vars[i] = var_id; } - } - - ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - - /* If query is empty, insert Nothing instruction */ - if (!(term_count - ctx.skipped)) { - ecs_vec_clear(ctx.ops); - ecs_query_op_t nothing = {0}; - nothing.kind = EcsQueryNothing; - flecs_query_op_insert(¬hing, &ctx); } else { - /* If query contains terms for toggleable components, insert toggle */ - if (!(q->flags & EcsQueryTableOnly)) { - flecs_query_insert_toggle(query, &ctx); + if ((term->src.id & trav_flags) == EcsUp) { + op->kind = EcsQueryUp; + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + op->kind = EcsQuerySelfUp; + } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + op->kind = EcsQueryAndAny; } - - /* Insert yield. If program reaches this operation, a result was found */ - ecs_query_op_t yield = {0}; - yield.kind = EcsQueryYield; - flecs_query_op_insert(&yield, &ctx); } +} - int32_t op_count = ecs_vec_count(ctx.ops); - if (op_count) { - query->op_count = op_count; - query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); - ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); - ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); +int flecs_query_compile_term( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_id_t term_id = term->id; + ecs_entity_t first_id = term->first.id; + ecs_entity_t second_id = term->second.id; + bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; + bool member_term = (term->flags_ & EcsTermIsMember) != 0; + if (member_term) { + flecs_query_compile_begin_member_term(world, term, ctx, first_id); } - return 0; -} - -/** - * @file query/compiler/compiler_term.c - * @brief Compile query term. - */ + ecs_query_t *q = &query->pub; + bool first_term = term == q->terms; + bool first_is_var = term->first.id & EcsIsVariable; + bool second_is_var = term->second.id & EcsIsVariable; + bool src_is_var = term->src.id & EcsIsVariable; + bool src_is_wildcard = src_is_var && + (ECS_TERM_REF_ID(&term->src) == EcsWildcard || + ECS_TERM_REF_ID(&term->src) == EcsAny); + bool src_is_lookup = false; + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool is_optional = (term->oper == EcsOptional); + bool is_or = flecs_term_is_or(q, term); + bool first_or = false, last_or = false; + bool cond_write = term->oper == EcsOptional || is_or; + ecs_query_op_t op = {0}; + if (is_or) { + first_or = first_term || (term[-1].oper != EcsOr); + last_or = term->oper != EcsOr; + } -#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || + term->oper == EcsNotFrom) + { + const ecs_type_t *type = ecs_get_type(world, term->id); + if (!type) { + /* Empty type for id in *From operation is a noop */ + ctx->skipped ++; + return 0; + } -ecs_var_id_t flecs_query_find_var_id( - const ecs_query_impl_t *query, - const char *name, - ecs_var_kind_t kind) -{ - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = type->count; + ecs_id_t *ti_ids = type->array; - if (kind == EcsVarTable) { - if (!ecs_os_strcmp(name, EcsThisName)) { - if (query->pub.flags & EcsQueryHasTableThisVar) { - return 0; - } else { - return EcsVarNone; + for (i = 0; i < count; i ++) { + ecs_id_t ti_id = ti_ids[i]; + ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); + if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + break; } } - if (!flecs_name_index_is_init(&query->tvar_index)) { - return EcsVarNone; + if (i == count) { + /* Type did not contain any ids to perform operation on */ + ctx->skipped ++; + return 0; } + } - uint64_t index = flecs_name_index_find( - &query->tvar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; - } - return flecs_utovar(index); + /* !_ (don't match anything) terms always return nothing. */ + if (term->oper == EcsNot && term->id == EcsAny) { + op.kind = EcsQueryNothing; + flecs_query_op_insert(&op, ctx); + return 0; } - if (kind == EcsVarEntity) { - if (!flecs_name_index_is_init(&query->evar_index)) { - return EcsVarNone; + if (first_or) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } else if (is_or) { + ctx->written = ctx->ctrlflow->written_or; + } + + if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { + if (flecs_query_compile_0_src(world, query, term, ctx)) { + goto error; } + return 0; + } - uint64_t index = flecs_name_index_find( - &query->evar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; + if (builtin_pred) { + ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); + if (id_noflags == EcsWildcard || id_noflags == EcsAny) { + /* Noop */ + return 0; } - return flecs_utovar(index); } - ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); + op.field_index = flecs_ito(int8_t, term->field_index); + op.term_index = flecs_ito(int8_t, term - q->terms); - /* If searching for any kind of variable, start with most specific */ - ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); - if (index != EcsVarNone) { - return index; + flecs_query_set_op_kind(&op, term, src_is_var); + + bool is_not = (term->oper == EcsNot) && !builtin_pred; + + /* Save write state at start of term so we can use it to reliably track + * variables got written by this term. */ + ecs_write_flags_t cond_write_state = ctx->cond_written; + + /* Resolve variables and entities for operation arguments */ + flecs_query_compile_term_ref(world, query, &op, &term->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarAny, ctx, true); + + bool src_written = true; + if (src_is_var) { + src_is_lookup = query->vars[op.src.var].lookup != NULL; + src_written = flecs_query_is_written(op.src.var, ctx->written); } - return flecs_query_find_var_id(query, name, EcsVarTable); -} + /* Insert each instructions for table -> entity variable if needed */ + bool first_written, second_written; + if (flecs_query_compile_ensure_vars( + query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) + { + goto error; + } -static -ecs_var_id_t flecs_query_most_specific_var( - ecs_query_impl_t *query, - const char *name, - ecs_var_kind_t kind, - ecs_query_compile_ctx_t *ctx) -{ - if (kind == EcsVarTable || kind == EcsVarEntity) { - return flecs_query_find_var_id(query, name, kind); + if (flecs_query_compile_ensure_vars( + query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) + { + goto error; } - ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); - if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { - /* If entity variable is available and written to, it contains the most - * specific result and should be used. */ - return evar; + /* Store write state of variables for first OR term in chain which will get + * restored for the other terms in the chain, so that all OR terms make the + * same assumptions about which variables were already written. */ + if (first_or) { + ctx->ctrlflow->written_or = ctx->written; + } + + /* If an optional or not term is inserted for a source that's not been + * written to yet, insert instruction that selects all entities so we have + * something to match the optional/not against. */ + if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { + src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); + } + + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { + if (query->vars[op.src.var].kind == EcsVarTable) { + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); + ctx->ctrlflow->written_or |= (1llu << op.src.var); + } } - ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); - if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { - /* If variable of any kind is requested and variable hasn't been written - * yet, write to table variable */ - return tvar; + if (flecs_query_compile_ensure_vars( + query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) + { + goto error; } - /* If table var is written, and entity var doesn't exist or is not written, - * return table var */ - if (tvar != EcsVarNone) { - return tvar; - } else { - return evar; - } -} + /* If source is Any (_) and first and/or second are unconstrained, insert an + * ids instruction instead of an And */ + if (term->flags_ & EcsTermMatchAnySrc) { + op.kind = EcsQueryIds; + /* Use up-to-date written values after potentially inserting each */ + if (!first_written || !second_written) { + if (!first_written) { + /* If first is unknown, traverse left: <- (*, t) */ + if (ECS_TERM_REF_ID(&term->first) != EcsAny) { + op.kind = EcsQueryIdsLeft; + } + } else { + /* If second is wildcard, traverse right: (r, *) -> */ + if (ECS_TERM_REF_ID(&term->second) != EcsAny) { + op.kind = EcsQueryIdsRight; + } + } + op.src.entity = 0; + src_is_var = false; + op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); + } -ecs_query_lbl_t flecs_query_op_insert( - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); - int32_t count = ecs_vec_count(ctx->ops); - *elem = *op; - if (count > 1) { - if (ctx->cur->lbl_begin == -1) { - /* Variables written by previous instruction can't be written by - * this instruction, except when this is part of an OR chain. */ - elem->written &= ~elem[-1].written; + /* If source variable is not written and we're querying just for Any, insert + * a dedicated instruction that uses the Any record in the id index. Any + * queries that are evaluated against written sources can use Wildcard + * records, which is what the AndAny instruction does. */ + } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { + /* Lookup variables ($var.child_name) are always written */ + if (!src_is_lookup) { + op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ } } - elem->next = flecs_itolbl(count); - elem->prev = flecs_itolbl(count - 2); - return flecs_itolbl(count - 1); -} + /* If this is a transitive term and both the target and source are unknown, + * find the targets for the relationship first. This clusters together + * tables for the same target, which allows for more efficient usage of the + * traversal caches. */ + if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { + if (!src_written && !second_written) { + flecs_query_insert_unconstrained_transitive( + query, &op, ctx, cond_write); + } + } -ecs_query_op_t* flecs_query_begin_block( - ecs_query_op_kind_t kind, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t op = {0}; - op.kind = flecs_ito(uint8_t, kind); - ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); - return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); -} + /* Check if this term has variables that have been conditionally written, + * like variables written by an optional term. */ + if (ctx->cond_written) { + if (!is_or || first_or) { + flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); + } + } -void flecs_query_end_block( - ecs_query_compile_ctx_t *ctx, - bool reset) -{ - ecs_query_op_t new_op = {0}; - new_op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); - - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - ops[ctx->cur->lbl_begin].next = end; + /* If term can toggle and is Not, change operator to Optional as we + * have to match entities that have the component but disabled. */ + if (toggle_term && is_not) { + is_not = false; + is_optional = true; + } - ecs_query_op_t *end_op = &ops[end]; - if (reset && ctx->cur->lbl_query != -1) { - ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; - end_op->prev = ctx->cur->lbl_begin; - end_op->src = query_op->src; - end_op->first = query_op->first; - end_op->second = query_op->second; - end_op->flags = query_op->flags; - end_op->field_index = query_op->field_index; - } else { - end_op->prev = ctx->cur->lbl_begin; - end_op->field_index = -1; + /* Handle Not, Optional, Or operators */ + if (is_not) { + flecs_query_begin_block(EcsQueryNot, ctx); + } else if (is_optional) { + flecs_query_begin_block(EcsQueryOptional, ctx); + } else if (first_or) { + flecs_query_begin_block_or(&op, term, ctx); } - ctx->cur->lbl_begin = -1; -} + /* If term has component inheritance enabled, insert instruction to walk + * down the relationship tree of the id. */ + if (term->flags_ & EcsTermIdInherited) { + flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); + } -static -void flecs_query_begin_block_cond_eval( - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - ecs_write_flags_t cond_write_state) -{ - ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; - ecs_write_flags_t cond_mask = 0; + op.match_flags = term->flags_; - if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { - first_var = op->first.var; - ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << first_var); - } - if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { - second_var = op->second.var; - ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << second_var); - } - if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { - src_var = op->src.var; - ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - cond_mask |= (1ull << src_var); + ecs_write_flags_t write_state = ctx->written; + if (first_is_var) { + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op.flags |= (EcsQueryIsVar << EcsQueryFirst); } - /* Variables set in an OR chain are marked as conditional writes. However, - * writes from previous terms in the current OR chain shouldn't be treated - * as variables that are conditionally set, so instead use the write mask - * from before the chain started. */ - if (ctx->ctrlflow->in_or) { - cond_write_state = ctx->ctrlflow->cond_written_or; + if (term->src.id & EcsSelf) { + op.flags |= EcsQueryIsSelf; } - /* If this term uses conditionally set variables, insert instruction that - * jumps over the term if the variables weren't set yet. */ - if (cond_mask & cond_write_state) { - ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); - - ecs_query_op_t jmp_op = {0}; - jmp_op.kind = EcsQueryIfVar; - - if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); - jmp_op.first.var = first_var; + /* Insert instructions for lookup variables */ + if (first_is_var) { + if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { + write_state |= (1ull << op.first.var); // lookups are resolved inline } - if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); - jmp_op.second.var = second_var; + } + if (src_is_var) { + if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { + write_state |= (1ull << op.src.var); // lookups are resolved inline } - if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { - jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - jmp_op.src.var = src_var; + } + if (second_is_var) { + if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { + write_state |= (1ull << op.second.var); // lookups are resolved inline } - - flecs_query_op_insert(&jmp_op, ctx); - } else { - ctx->cur->lbl_cond_eval = -1; } -} -static -void flecs_query_end_block_cond_eval( - ecs_query_compile_ctx_t *ctx) -{ - if (ctx->cur->lbl_cond_eval == -1) { - return; + if (builtin_pred) { + if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { + goto error; + } } - ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_query_op_t end_op = {0}; - end_op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); - - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - ops[ctx->cur->lbl_cond_eval].next = end; - - ecs_query_op_t *end_op_ptr = &ops[end]; - ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; - end_op_ptr->prev = ctx->cur->lbl_cond_eval; - end_op_ptr->src = query_op->src; - end_op_ptr->first = query_op->first; - end_op_ptr->second = query_op->second; - end_op_ptr->flags = query_op->flags; - end_op_ptr->field_index = query_op->field_index; -} + /* If we're writing the $this variable, filter out disabled/prefab entities + * unless the query explicitly matches them. + * This could've been done with regular With instructions, but since + * filtering out disabled/prefab entities is the default and this check is + * cheap to perform on table flags, it's worth special casing. */ + if (!src_written && op.src.var == 0) { + op.other = flecs_itolbl(flecs_query_to_table_flags(q)); + } -static -void flecs_query_begin_block_or( - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); - or_op->kind = EcsQueryOr; - or_op->field_index = term->field_index; + /* After evaluating a term, a used variable is always written */ + if (src_is_var) { + flecs_query_write(op.src.var, &op.written); + flecs_query_write_ctx(op.src.var, ctx, cond_write); + } + if (first_is_var) { + flecs_query_write(op.first.var, &op.written); + flecs_query_write_ctx(op.first.var, ctx, cond_write); + } + if (second_is_var) { + flecs_query_write(op.second.var, &op.written); + flecs_query_write_ctx(op.second.var, ctx, cond_write); + } + + flecs_query_op_insert(&op, ctx); - /* Set the source of the evaluate terms as source of the Or instruction. - * This lets the engine determine whether the variable has already been - * written. When the source is not yet written, an OR operation needs to - * take the union of all the terms in the OR chain. When the variable is - * known, it will return after the first matching term. - * - * In case a term in the OR expression is an equality predicate which - * compares the left hand side with a variable, the variable acts as an - * alias, so we can always assume that it's written. */ - bool add_src = true; - if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { - if (!(flecs_query_is_written(op->src.var, ctx->written))) { - add_src = false; + ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + if (is_or && !member_term) { + flecs_query_mark_last_or_op(ctx); + } + + /* Handle self-references between src and first/second variables */ + if (src_is_var) { + if (first_is_var) { + flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); + } + if (second_is_var && op.first.var != op.second.var) { + flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); } } - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (add_src) { - or_op->flags = (EcsQueryIsVar << EcsQuerySrc); - or_op->src = op->src; - ctx->cur->src_or = op->src; + /* Handle self references between first and second variables */ + if (!ecs_id_is_wildcard(first_id)) { + if (first_is_var && !first_written && (op.first.var == op.second.var)) { + flecs_query_insert_pair_eq(term->field_index, ctx); } + } - ctx->cur->src_written_or = flecs_query_is_written( - op->src.var, ctx->written); + /* Handle closing of Not, Optional and Or operators */ + if (is_not) { + flecs_query_end_block(ctx, true); + } else if (is_optional) { + flecs_query_end_block(ctx, true); } -} -static -void flecs_query_end_block_or( - ecs_query_impl_t *impl, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t op = {0}; - op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); + /* Now that the term is resolved, evaluate member of component */ + if (member_term) { + flecs_query_compile_end_member_term(world, query, &op, term, ctx, + term_id, first_id, second_id, cond_write); + if (is_or) { + flecs_query_mark_last_or_op(ctx); + } + } - ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); - int32_t i, prev_or = ctx->cur->lbl_begin + 1; - for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { - if (ops[i].next == FlecsRuleOrMarker) { - if (i == (end - 1)) { - ops[prev_or].prev = ctx->cur->lbl_begin; - } else { - ops[prev_or].prev = flecs_itolbl(i + 1); - } + if (last_or) { + flecs_query_end_block_or(query, ctx); + } - ops[i].next = flecs_itolbl(end); + /* Handle closing of conditional evaluation */ + if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { + if (!is_or || last_or) { + flecs_query_end_block_cond_eval(ctx); + } + } - prev_or = i + 1; + /* Ensure that term id is set after evaluating Not */ + if (term->flags_ & EcsTermIdInherited) { + if (is_not) { + ecs_query_op_t set_id = {0}; + set_id.kind = EcsQuerySetId; + set_id.first.entity = term->id; + set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); + set_id.field_index = flecs_ito(int8_t, term->field_index); + flecs_query_op_insert(&set_id, ctx); } } - ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; - bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); - first->next = flecs_itolbl(end); - ops[end].prev = ctx->cur->lbl_begin; - ops[end - 1].prev = ctx->cur->lbl_begin; + return 0; +error: + return -1; +} - ctx->ctrlflow->in_or = false; - ctx->cur->lbl_begin = -1; - if (src_is_var) { - ecs_var_id_t src_var = first->src.var; - ctx->written |= (1llu << src_var); +/** + * @file query/engine/cache.c + * @brief Cached query implementation. + */ - /* If src is a table variable, it is possible that this was resolved to - * an entity variable in all of the OR terms. If this is the case, mark - * entity variable as written as well. */ - ecs_query_var_t *var = &impl->vars[src_var]; - if (var->kind == EcsVarTable) { - const char *name = var->name; - if (!name) { - name = "this"; - } - ecs_var_id_t evar = flecs_query_find_var_id( - impl, name, EcsVarEntity); - if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { - ctx->written |= (1llu << evar); - ctx->cond_written &= ~(1llu << evar); - } - } - } - ctx->written |= ctx->cond_written; +int32_t flecs_query_cache_table_count( + ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + return cache->cache.tables.count; +} - /* Scan which variables were conditionally written in the OR chain and - * reset instructions after the OR chain. If a variable is set in part one - * of a chain but not part two, there would be nothing writing to the - * variable in part two, leaving it to the previous value. To address this - * a reset is inserted that resets the variable value on redo. */ - for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { - ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); - ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); +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; +} - /* Skip variable if it's the source for the OR chain */ - if (src_is_var && (i == first->src.var)) { - continue; - } +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) { + return 0; + } - if (!prev && cur) { - ecs_query_op_t reset_op = {0}; - reset_op.kind = EcsQueryReset; - reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - reset_op.src.var = flecs_itovar(i); - flecs_query_op_insert(&reset_op, ctx); - } + for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { + result += ecs_table_count(cur->table); } -} -void flecs_query_insert_each( - ecs_var_id_t tvar, - ecs_var_id_t evar, - ecs_query_compile_ctx_t *ctx, - bool cond_write) -{ - ecs_query_op_t each = {0}; - each.kind = EcsQueryEach; - each.src.var = evar; - each.first.var = tvar; - each.flags = (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &each.written); - flecs_query_op_insert(&each, ctx); + return result; } static -void flecs_query_insert_lookup( - ecs_var_id_t base_var, - ecs_var_id_t evar, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +uint64_t flecs_query_cache_get_group_id( + ecs_query_cache_t *cache, + ecs_table_t *table) { - ecs_query_op_t lookup = {0}; - lookup.kind = EcsQueryLookup; - lookup.src.var = evar; - lookup.first.var = base_var; - lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &lookup.written); - flecs_query_op_insert(&lookup, ctx); + if (cache->group_by_callback) { + return cache->group_by_callback(cache->query->world, table, + cache->group_by, cache->group_by_ctx); + } else { + return 0; + } } static -void flecs_query_insert_unconstrained_transitive( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +void flecs_query_cache_compute_group_id( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - /* Create anonymous variable to store the target ids. This will return the - * list of targets without constraining the variable of the term, which - * needs to stay variable to find all transitive relationships for a src. */ - ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - /* First, find ids to start traversal from. This fixes op.second. */ - ecs_query_op_t find_ids = {0}; - find_ids.kind = EcsQueryIdsRight; - find_ids.field_index = -1; - find_ids.first = op->first; - find_ids.second = op->second; - find_ids.flags = op->flags; - find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); - find_ids.second.var = tgt; - flecs_query_write_ctx(tgt, ctx, cond_write); - flecs_query_write(tgt, &find_ids.written); - flecs_query_op_insert(&find_ids, ctx); + if (cache->group_by_callback) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* Next, iterate all tables for the ids. This fixes op.src */ - ecs_query_op_t and_op = {0}; - and_op.kind = EcsQueryAnd; - and_op.field_index = op->field_index; - and_op.first = op->first; - and_op.second = op->second; - and_op.src = op->src; - and_op.flags = op->flags | EcsQueryIsSelf; - and_op.second.var = tgt; - flecs_query_write_ctx(and_op.src.var, ctx, cond_write); - flecs_query_write(and_op.src.var, &and_op.written); - flecs_query_op_insert(&and_op, ctx); + match->group_id = flecs_query_cache_get_group_id(cache, table); + } else { + match->group_id = 0; + } } static -void flecs_query_insert_inheritance( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +ecs_query_cache_table_list_t* flecs_query_cache_get_group( + const ecs_query_cache_t *cache, + uint64_t group_id) { - /* Anonymous variable to store the resolved component ids */ - ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + return ecs_map_get_deref( + &cache->groups, ecs_query_cache_table_list_t, group_id); +} - flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); +static +ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( + ecs_query_cache_t *cache, + uint64_t id) +{ + ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, + ecs_query_cache_table_list_t, id); - ecs_query_op_t trav_op = {0}; - trav_op.kind = EcsQueryTrav; - trav_op.field_index = -1; - trav_op.first.entity = EcsIsA; - trav_op.second.entity = ECS_TERM_REF_ID(&term->first); - trav_op.src.var = tvar; - trav_op.flags = EcsQueryIsSelf; - trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); - trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - trav_op.written |= (1ull << tvar); - if (term->first.id & EcsSelf) { - trav_op.match_flags |= EcsTermReflexive; + if (!group) { + group = ecs_map_insert_alloc_t(&cache->groups, + ecs_query_cache_table_list_t, id); + ecs_os_zeromem(group); + if (cache->on_group_create) { + group->info.ctx = cache->on_group_create( + cache->query->world, id, cache->group_by_ctx); + } } - flecs_query_op_insert(&trav_op, ctx); - flecs_query_insert_each(tvar, evar, ctx, cond_write); - ecs_query_ref_t r = { .var = evar }; - op->first = r; - op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op->flags |= (EcsQueryIsVar << EcsQueryFirst); + return group; } -void flecs_query_compile_term_ref( - ecs_world_t *world, - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_term_ref_t *term_ref, - ecs_query_ref_t *ref, - ecs_flags8_t ref_kind, - ecs_var_kind_t kind, - ecs_query_compile_ctx_t *ctx, - bool create_wildcard_vars) +static +void flecs_query_cache_remove_group( + ecs_query_cache_t *cache, + uint64_t id) { - (void)world; - - if (!ecs_term_ref_is_set(term_ref)) { - return; - } - - if (term_ref->id & EcsIsVariable) { - op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); - const char *name = flecs_term_ref_var_name(term_ref); - if (name) { - ref->var = flecs_query_most_specific_var(query, name, kind, ctx); - ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - } else if (create_wildcard_vars) { - bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); - if (is_wildcard && (kind == EcsVarAny)) { - ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - } else { - ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - } - if (is_wildcard) { - flecs_set_var_label(&query->vars[ref->var], - ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); - } - ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + if (cache->on_group_delete) { + ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, + ecs_query_cache_table_list_t, id); + if (group) { + cache->on_group_delete(cache->query->world, id, + group->info.ctx, cache->group_by_ctx); } } - if (term_ref->id & EcsIsEntity) { - op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); - ref->entity = ECS_TERM_REF_ID(term_ref); + ecs_map_remove_free(&cache->groups, id); +} + +static +uint64_t flecs_query_cache_default_group_by( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)ctx; + + ecs_id_t match; + if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { + return ecs_pair_second(world, match); } + return 0; } +/* Find the last node of the group after which this group should be inserted */ static -int flecs_query_compile_ensure_vars( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - ecs_query_compile_ctx_t *ctx, - bool cond_write, - bool *written_out) +ecs_query_cache_table_match_t* flecs_query_cache_find_group_insertion_node( + ecs_query_cache_t *cache, + uint64_t group_id) { - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - bool written = false; + /* Grouping must be enabled */ + ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); - if (flags & EcsQueryIsVar) { - ecs_var_id_t var_id = ref->var; - ecs_query_var_t *var = &query->vars[var_id]; + ecs_map_iter_t it = ecs_map_iter(&cache->groups); + ecs_query_cache_table_list_t *list, *closest_list = NULL; + uint64_t id, closest_id = 0; + + bool desc = false; - if (var->kind == EcsVarEntity && - !flecs_query_is_written(var_id, ctx->written)) - { - /* If entity variable is not yet written but a table variant exists - * that has been written, insert each operation to translate from - * entity variable to table */ - ecs_var_id_t tvar = var->table_id; - if ((tvar != EcsVarNone) && - flecs_query_is_written(tvar, ctx->written)) - { - if (var->lookup) { - if (!flecs_query_is_written(tvar, ctx->written)) { - ecs_err("dependent variable of '$%s' is not written", - var->name); - return -1; - } + if (cache->cascade_by) { + desc = (cache->query->terms[ + cache->cascade_by - 1].src.id & EcsDesc) != 0; + } - if (!flecs_query_is_written(var->base_id, ctx->written)) { - flecs_query_insert_each( - tvar, var->base_id, ctx, cond_write); - } - } else { - flecs_query_insert_each(tvar, var_id, ctx, cond_write); - } + /* Find closest smaller group id */ + while (ecs_map_next(&it)) { + id = ecs_map_key(&it); - /* Variable was written, just not as entity */ - written = true; - } else if (var->lookup) { - if (!flecs_query_is_written(var->base_id, ctx->written)) { - ecs_err("dependent variable of '$%s' is not written", - var->name); - return -1; - } + if (!desc) { + if (id >= group_id) { + continue; + } + } else { + if (id <= group_id) { + continue; } } - written |= flecs_query_is_written(var_id, ctx->written); + list = ecs_map_ptr(&it); + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } + + bool comp; + if (!desc) { + comp = ((group_id - id) < (group_id - closest_id)); + } else { + comp = ((group_id - id) > (group_id - closest_id)); + } + + if (!closest_list || comp) { + closest_id = id; + closest_list = list; + } + } + + if (closest_list) { + return closest_list->last; } else { - /* If it's not a variable, it's always written */ - written = true; + return NULL; /* Group should be first in query */ } +} - if (written_out) { - *written_out = written; +/* Initialize group with first node */ +static +void flecs_query_cache_create_group( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) +{ + uint64_t group_id = match->group_id; + + /* If query has grouping enabled & this is a new/empty group, find + * the insertion point for the group */ + ecs_query_cache_table_match_t *insert_after = + flecs_query_cache_find_group_insertion_node(cache, group_id); + + if (!insert_after) { + /* This group should appear first in the query list */ + ecs_query_cache_table_match_t *query_first = cache->list.first; + if (query_first) { + /* If this is not the first match for the query, insert before it */ + match->next = query_first; + query_first->prev = match; + cache->list.first = match; + } else { + /* If this is the first match of the query, initialize its list */ + ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.first = match; + cache->list.last = match; + } + } else { + ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This group should appear after another group */ + ecs_query_cache_table_match_t *insert_before = insert_after->next; + match->prev = insert_after; + insert_after->next = match; + match->next = insert_before; + if (insert_before) { + insert_before->prev = match; + } else { + ecs_assert(cache->list.last == insert_after, + ECS_INTERNAL_ERROR, NULL); + + /* This group should appear last in the query list */ + cache->list.last = match; + } } - - return 0; } +/* Find the list the node should be part of */ static -bool flecs_query_compile_lookup( - ecs_query_impl_t *query, - ecs_var_id_t var_id, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_query_var_t *var = &query->vars[var_id]; - if (var->lookup) { - flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); - return true; + if (cache->group_by_callback) { + return flecs_query_cache_get_group(cache, match->group_id); } else { - return false; + return &cache->list; } } +/* Find or create the list the node should be part of */ static -void flecs_query_insert_contains( - ecs_query_impl_t *query, - ecs_var_id_t src_var, - ecs_var_id_t other_var, - ecs_query_compile_ctx_t *ctx) +ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_query_op_t contains = {0}; - if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { - contains.kind = EcsQueryContain; - contains.src.var = src_var; - contains.first.var = other_var; - contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | - (EcsQueryIsVar << EcsQueryFirst); - flecs_query_op_insert(&contains, ctx); + if (cache->group_by_callback) { + return flecs_query_cache_ensure_group(cache, match->group_id); + } else { + return &cache->list; } } +/* Remove node from list */ static -void flecs_query_insert_pair_eq( - int32_t field_index, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t contains = {0}; - contains.kind = EcsQueryPairEq; - contains.field_index = flecs_ito(int8_t, field_index); - flecs_query_op_insert(&contains, ctx); -} - -static -int flecs_query_compile_builtin_pred( - ecs_query_t *q, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_write_flags_t write_state) +void flecs_query_cache_remove_table_node( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_entity_t id = ECS_TERM_REF_ID(&term->first); + ecs_query_cache_table_match_t *prev = match->prev; + ecs_query_cache_table_match_t *next = match->next; - ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; - ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; - ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; - - ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); - if (id == EcsPredEq) { - if (term->second.id & EcsIsName) { - op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); - } else { - op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); - } - } else if (id == EcsPredMatch) { - op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); - } + ecs_query_cache_table_list_t *list = + flecs_query_cache_get_node_list(cache, match); - if (flags_2nd & EcsQueryIsVar) { - if (!(write_state & (1ull << op->second.var))) { - ecs_err("uninitialized variable '%s' on right-hand side of " - "equality operator", ecs_query_var_name(q, op->second.var)); - return -1; - } + if (!list || !list->first) { + /* If list contains no matches, the match must be empty */ + ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + return; } - ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); - (void)flags_src; + ecs_assert(prev != NULL || cache->list.first == match, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || cache->list.last == match, + ECS_INTERNAL_ERROR, NULL); - if (!(write_state & (1ull << op->src.var))) { - /* If this is an == operator with a right-hand side that resolves to a - * single entity, the left-hand side is allowed to be undefined, as the - * instruction will be evaluated as an assignment. */ - if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { - ecs_err("uninitialized variable '%s' on left-hand side of " - "equality operator", ecs_query_var_name(q, op->src.var)); - return -1; - } + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; } - return 0; -} + ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + list->info.table_count --; -static -int flecs_query_ensure_scope_var( - ecs_query_impl_t *query, - ecs_query_op_t *op, - ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - ecs_query_compile_ctx_t *ctx) -{ - ecs_var_id_t var = ref->var; + if (cache->group_by_callback) { + uint64_t group_id = match->group_id; - if (query->vars[var].kind == EcsVarEntity && - !flecs_query_is_written(var, ctx->written)) - { - ecs_var_id_t table_var = query->vars[var].table_id; - if (table_var != EcsVarNone && - flecs_query_is_written(table_var, ctx->written)) - { - if (flecs_query_compile_ensure_vars( - query, op, ref, ref_kind, ctx, false, NULL)) - { - goto error; - } + /* Make sure query.list is updated if this is the first or last group */ + if (cache->list.first == match) { + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.first = next; + prev = next; + } + if (cache->list.last == match) { + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + cache->list.last = prev; + next = prev; } - } - - return 0; -error: - return -1; -} -static -int flecs_query_ensure_scope_vars( - ecs_world_t *world, - ecs_query_impl_t *query, - ecs_query_compile_ctx_t *ctx, - ecs_term_t *term) -{ - /* If the scope uses variables as entity that have only been written as - * table, resolve them as entities before entering the scope. */ - ecs_term_t *cur = term; - while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { - /* Dummy operation to obtain variable information for term */ - ecs_query_op_t op = {0}; - flecs_query_compile_term_ref(world, query, &op, &cur->first, - &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); - flecs_query_compile_term_ref(world, query, &op, &cur->second, - &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); + ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + cache->list.info.table_count --; + list->info.match_count ++; - if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { - if (flecs_query_ensure_scope_var( - query, &op, &op.first, EcsQueryFirst, ctx)) - { - goto error; - } + /* Make sure group list only contains nodes that belong to the group */ + if (prev && prev->group_id != group_id) { + /* The previous node belonged to another group */ + prev = next; } - - if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (flecs_query_ensure_scope_var( - query, &op, &op.second, EcsQuerySecond, ctx)) - { - goto error; - } + if (next && next->group_id != group_id) { + /* The next node belonged to another group */ + next = prev; } - cur ++; + /* Do check again, in case both prev & next belonged to another group */ + if ((!prev && !next) || (prev && prev->group_id != group_id)) { + /* There are no more matches left in this group */ + flecs_query_cache_remove_group(cache, group_id); + list = NULL; + } } - return 0; -error: - return -1; -} - -static -void flecs_query_compile_push( - ecs_query_compile_ctx_t *ctx) -{ - ctx->cur = &ctx->ctrlflow[++ ctx->scope]; - ctx->cur->lbl_begin = -1; - ctx->cur->lbl_begin = -1; -} - -static -void flecs_query_compile_pop( - ecs_query_compile_ctx_t *ctx) -{ - ctx->cur = &ctx->ctrlflow[-- ctx->scope]; -} - -static -int flecs_query_compile_0_src( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) -{ - /* If the term has a 0 source, check if it's a scope open/close */ - if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { - if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { - goto error; - } - if (term->oper == EcsNot) { - ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); - flecs_query_begin_block(EcsQueryNot, ctx); - } else { - ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + if (list) { + if (list->first == match) { + list->first = next; } - flecs_query_compile_push(ctx); - } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { - flecs_query_compile_pop(ctx); - if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { - flecs_query_end_block(ctx, false); + if (list->last == match) { + list->last = prev; } - } else { - /* Noop */ } - return 0; -error: - return -1; + match->prev = NULL; + match->next = NULL; + + cache->match_count ++; } +/* Add node to list */ static -ecs_flags32_t flecs_query_to_table_flags( - const ecs_query_t *q) +void flecs_query_cache_insert_table_node( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *match) { - ecs_flags32_t query_flags = q->flags; - if (!(query_flags & EcsQueryMatchDisabled) || - !(query_flags & EcsQueryMatchPrefab)) - { - ecs_flags32_t table_flags = EcsTableNotQueryable; - if (!(query_flags & EcsQueryMatchDisabled)) { - table_flags |= EcsTableIsDisabled; - } - if (!(query_flags & EcsQueryMatchPrefab)) { - table_flags |= EcsTableIsPrefab; - } + /* Node should not be part of an existing list */ + ecs_assert(match->prev == NULL && match->next == NULL, + ECS_INTERNAL_ERROR, NULL); - return table_flags; + /* If this is the first match, activate system */ + if (!cache->list.first && cache->entity) { + ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); } - return 0; -} -static -bool flecs_query_select_all( - const ecs_query_t *q, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_var_id_t src_var, - ecs_query_compile_ctx_t *ctx) -{ - bool builtin_pred = flecs_term_is_builtin_pred(term); - bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; + flecs_query_cache_compute_group_id(cache, match); - if (term->oper == EcsNot || term->oper == EcsOptional || - term->oper == EcsNotFrom || pred_match) - { - ecs_query_op_t match_any = {0}; - match_any.kind = EcsAnd; - match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); - match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); - match_any.src = op->src; - match_any.field_index = -1; - if (!pred_match) { - match_any.first.entity = EcsAny; - } else { - /* If matching by name, instead of finding all tables, just find - * the ones with a name. */ - match_any.first.entity = ecs_id(EcsIdentifier); - match_any.second.entity = EcsName; - match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); - } - match_any.written = (1ull << src_var); - match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); - flecs_query_op_insert(&match_any, ctx); - flecs_query_write_ctx(op->src.var, ctx, false); + 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); + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - /* Update write administration */ - return true; - } - return false; -} + ecs_query_cache_table_match_t *last = list->last; + ecs_query_cache_table_match_t *last_next = last->next; -#ifdef FLECS_META -static -int flecs_query_compile_begin_member_term( - ecs_world_t *world, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_entity_t first_id) -{ - ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + match->prev = last; + match->next = last_next; + last->next = match; - first_id = ECS_TERM_REF_ID(&term->first); + if (last_next) { + last_next->prev = match; + } - /* First compile as if it's a regular term, to match the component */ - term->flags_ &= (uint16_t)~EcsTermIsMember; + list->last = match; - /* Replace term id with member parent (the component) */ - ecs_entity_t component = ecs_get_parent(world, first_id); - if (!component) { - ecs_err("member without parent in query"); - return -1; - } + if (cache->group_by_callback) { + /* Make sure to update query list if this is the last group */ + if (cache->list.last == last) { + cache->list.last = match; + } + } + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - if (!ecs_has(world, component, EcsComponent)) { - ecs_err("parent of member is not a component"); - return -1; + list->first = match; + list->last = match; + + if (cache->group_by_callback) { + /* Initialize group with its first node */ + flecs_query_cache_create_group(cache, match); + } } - bool second_wildcard = - (ECS_TERM_REF_ID(&term->second) == EcsWildcard || - ECS_TERM_REF_ID(&term->second) == EcsAny) && - (term->second.id & EcsIsVariable) && !term->second.name; + if (cache->group_by_callback) { + list->info.table_count ++; + list->info.match_count ++; + } - term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); - term->second.id = 0; - term->id = component; + cache->list.info.table_count ++; + cache->match_count ++; - ctx->oper = (ecs_oper_kind_t)term->oper; - if (term->oper == EcsNot && !second_wildcard) { - /* When matching a member term with not operator, we need to cover both - * the case where an entity doesn't have the component, and where it - * does have the component, but doesn't match the member. */ - term->oper = EcsOptional; - } + ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); - return 0; + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); } static -int flecs_query_compile_end_member_term( +ecs_query_cache_table_match_t* flecs_query_cache_cache_add( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_id_t term_id, - ecs_entity_t first_id, - ecs_entity_t second_id, - bool cond_write) + ecs_query_cache_table_t *elem) { - ecs_entity_t component = ECS_TERM_REF_ID(&term->first); - const EcsComponent *comp = ecs_get(world, component, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Restore term values */ - term->id = term_id; - term->first.id = first_id; - term->second.id = second_id; - term->flags_ |= EcsTermIsMember; - term->oper = flecs_ito(int16_t, ctx->oper); - - first_id = ECS_TERM_REF_ID(&term->first); - const EcsMember *member = ecs_get(world, first_id, EcsMember); - ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_var_t *var = &impl->vars[op->src.var]; - const char *var_name = flecs_term_ref_var_name(&term->src); - ecs_var_id_t evar = flecs_query_find_var_id( - impl, var_name, EcsVarEntity); - - bool second_wildcard = - (ECS_TERM_REF_ID(&term->second) == EcsWildcard || - ECS_TERM_REF_ID(&term->second) == EcsAny) && - (term->second.id & EcsIsVariable) && !term->second.name; + ecs_query_cache_table_match_t *result = + flecs_bcalloc(&world->allocators.query_table_match); - if (term->oper == EcsOptional) { - second_wildcard = true; + if (!elem->first) { + elem->first = result; + elem->last = result; + } else { + ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); + elem->last->next_match = result; + elem->last = result; } - ecs_query_op_t mbr_op = *op; - mbr_op.kind = EcsQueryMemberEq; - mbr_op.first.entity = /* Encode type size and member offset */ - flecs_ito(uint32_t, member->offset) | - (flecs_ito(uint64_t, comp->size) << 32); + return result; +} - /* If this is a term with a Not operator, conditionally evaluate member on - * whether term was set by previous operation (see begin_member_term). */ - if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { - if (second_wildcard && ctx->oper == EcsNot) { - /* A !(T.value, *) term doesn't need special operations */ - return 0; - } +/* The group by function for cascade computes the tree depth for the table type. + * This causes tables in the query cache to be ordered by depth, which ensures + * breadth-first iteration order. */ +static +uint64_t flecs_query_cache_group_by_cascade( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)id; + ecs_term_t *term = ctx; + ecs_entity_t rel = term->trav; + int32_t depth = flecs_relation_depth(world, rel, table); + return flecs_ito(uint64_t, depth); +} - /* Resolve to entity variable before entering if block, so that we - * don't have different branches of the query working with different - * versions of the same variable. */ - if (var->kind == EcsVarTable) { - flecs_query_insert_each(op->src.var, evar, ctx, cond_write); - var = &impl->vars[evar]; - } +static +ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *qt, + ecs_table_t *table) +{ + /* Add match for table. One table can have more than one match, if + * the query contains wildcards. */ + ecs_query_cache_table_match_t *qm = flecs_query_cache_cache_add( + cache->query->world, qt); + + qm->table = table; + 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); + } - ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); - if_op->other = term->field_index; + return qm; +} - if (ctx->oper == EcsNot) { - mbr_op.kind = EcsQueryMemberNeq; - } - } +static +void flecs_query_cache_set_table_match( + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *qm, + ecs_iter_t *it) +{ + ecs_query_t *query = cache->query; + int8_t i, field_count = query->field_count; - if (var->kind == EcsVarTable) { - /* If MemberEq is called on table variable, store it on .other member. - * This causes MemberEq to do double duty as 'each' instruction, - * which is faster than having to go back & forth between instructions - * while finding matching values. */ - mbr_op.other = flecs_itolbl(op->src.var + 1); + ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - /* Mark entity variable as written */ - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &mbr_op.written); - } + /* Reset resources in case this is an existing record */ + ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), + it->trs, ecs_table_record_t*, field_count); - flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, - &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); + /* Find out whether to store result-specific ids array or fixed array */ + ecs_id_t *ids = cache->query->ids; + for (i = 0; i < field_count; i ++) { + if (it->ids[i] != ids[i]) { + break; + } + } - if (second_wildcard) { - mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - mbr_op.second.entity = EcsWildcard; + if (i != field_count) { + if (qm->ids == ids || !qm->ids) { + qm->ids = flecs_balloc(&cache->allocators.ids); + } + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); } else { - flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, - &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); - - if (term->second.id & EcsIsVariable) { - if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, - EcsQuerySecond, ctx, cond_write, NULL)) - { - goto error; - } - - flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); - flecs_query_write(mbr_op.second.var, &mbr_op.written); + if (qm->ids != ids) { + flecs_bfree(&cache->allocators.ids, qm->ids); + qm->ids = ids; } } - flecs_query_op_insert(&mbr_op, ctx); + /* Find out whether to store result-specific sources array or fixed array */ + for (i = 0; i < field_count; i ++) { + if (it->sources[i]) { + break; + } + } - if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { - flecs_query_end_block(ctx, false); + if (i != field_count) { + if (qm->sources == cache->sources || !qm->sources) { + qm->sources = flecs_balloc(&cache->allocators.sources); + } + ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + } else { + if (qm->sources != cache->sources) { + flecs_bfree(&cache->allocators.sources, qm->sources); + qm->sources = cache->sources; + } } - return 0; -error: - return -1; -} -#else -static -int flecs_query_compile_begin_member_term( - ecs_world_t *world, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_entity_t first_id) -{ - (void)world; (void)term; (void)ctx; (void)first_id; - return 0; + qm->set_fields = it->set_fields; + qm->up_fields = it->up_fields; } static -int flecs_query_compile_end_member_term( +ecs_query_cache_table_t* flecs_query_cache_table_insert( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx, - ecs_id_t term_id, - ecs_entity_t first_id, - ecs_entity_t second_id, - bool cond_write) + ecs_query_cache_t *cache, + ecs_table_t *table) { - (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; - (void)first_id; (void)second_id; (void)cond_write; - return 0; -} -#endif + ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); + if (table) { + qt->table_id = table->id; + } else { + qt->table_id = 0; + } -static -void flecs_query_mark_last_or_op( - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); - op_ptr->next = FlecsRuleOrMarker; + 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); + } + + return qt; } +/** Populate query cache with tables */ static -void flecs_query_set_op_kind( - ecs_query_op_t *op, - ecs_term_t *term, - bool src_is_var) +void flecs_query_cache_match_tables( + ecs_world_t *world, + ecs_query_cache_t *cache) { - /* Default instruction for And operators. If the source is fixed (like for - * singletons or terms with an entity source), use With, which like And but - * just matches against a source (vs. finding a source). */ - op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; - - /* Ignore cascade flag */ - ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); - - /* Handle *From operators */ - if (term->oper == EcsAndFrom) { - op->kind = EcsQueryAndFrom; - } else if (term->oper == EcsOrFrom) { - op->kind = EcsQueryOrFrom; - } else if (term->oper == EcsNotFrom) { - op->kind = EcsQueryNotFrom; + ecs_table_t *table = NULL; + ecs_query_cache_table_t *qt = NULL; - /* If query is transitive, use Trav(ersal) instruction */ - } else if (term->flags_ & EcsTermTransitive) { - ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); - op->kind = EcsQueryTrav; + ecs_iter_t it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); + ECS_BIT_SET(it.flags, EcsIterTableOnly); - /* If term queries for union pair, use union instruction */ - } else if (term->flags_ & EcsTermIsUnion) { - if (op->kind == EcsQueryAnd) { - op->kind = EcsQueryUnionEq; - if (term->oper == EcsNot) { - if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { - term->oper = EcsAnd; - op->kind = EcsQueryUnionNeq; - } - } - } else { - op->kind = EcsQueryUnionEqWith; + while (ecs_query_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + /* New table matched, add record to cache */ + table = it.table; + qt = flecs_query_cache_table_insert(world, cache, table); } - if ((term->src.id & trav_flags) == EcsUp) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqUp; - } - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqSelfUp; - } - } - } else { - if ((term->src.id & trav_flags) == EcsUp) { - op->kind = EcsQueryUp; - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - op->kind = EcsQuerySelfUp; - } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - op->kind = EcsQueryAndAny; - } + ecs_query_cache_table_match_t *qm = + flecs_query_cache_add_table_match(cache, qt, table); + flecs_query_cache_set_table_match(cache, qm, &it); } } -int flecs_query_compile_term( +static +bool flecs_query_cache_match_table( ecs_world_t *world, - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) + ecs_query_cache_t *cache, + ecs_table_t *table) { - ecs_id_t term_id = term->id; - ecs_entity_t first_id = term->first.id; - ecs_entity_t second_id = term->second.id; - bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; - bool member_term = (term->flags_ & EcsTermIsMember) != 0; - if (member_term) { - flecs_query_compile_begin_member_term(world, term, ctx, first_id); + if (!ecs_map_is_init(&cache->cache.index)) { + return false; } - ecs_query_t *q = &query->pub; - bool first_term = term == q->terms; - bool first_is_var = term->first.id & EcsIsVariable; - bool second_is_var = term->second.id & EcsIsVariable; - bool src_is_var = term->src.id & EcsIsVariable; - bool src_is_wildcard = src_is_var && - (ECS_TERM_REF_ID(&term->src) == EcsWildcard || - ECS_TERM_REF_ID(&term->src) == EcsAny); - bool src_is_lookup = false; - bool builtin_pred = flecs_term_is_builtin_pred(term); - bool is_optional = (term->oper == EcsOptional); - bool is_or = flecs_term_is_or(q, term); - bool first_or = false, last_or = false; - bool cond_write = term->oper == EcsOptional || is_or; - ecs_query_op_t op = {0}; + ecs_query_cache_table_t *qt = NULL; + ecs_query_t *q = cache->query; - if (is_or) { - first_or = first_term || (term[-1].oper != EcsOr); - last_or = term->oper != EcsOr; - } + /* Iterate uncached query for table to check if it matches. If this is a + * wildcard query, a table can match multiple times. */ + ecs_iter_t it = flecs_query_iter(world, q); + it.flags |= EcsIterNoData; + ecs_iter_set_var_as_table(&it, 0, table); - if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || - term->oper == EcsNotFrom) - { - const ecs_type_t *type = ecs_get_type(world, term->id); - if (!type) { - /* Empty type for id in *From operation is a noop */ - ctx->skipped ++; - return 0; + while (ecs_query_next(&it)) { + ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); + if (qt == NULL) { + table = it.table; + qt = flecs_query_cache_table_insert(world, cache, table); } - int32_t i, count = type->count; - ecs_id_t *ti_ids = type->array; + ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( + cache, qt, table); + flecs_query_cache_set_table_match(cache, qm, &it); + } - for (i = 0; i < count; i ++) { - ecs_id_t ti_id = ti_ids[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - break; - } - } + return qt != NULL; +} - if (i == count) { - /* Type did not contain any ids to perform operation on */ - ctx->skipped ++; - return 0; +static +bool flecs_query_cache_has_refs( + ecs_query_cache_t *cache) +{ + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; + for (i = 0; i < count; i ++) { + if (terms[i].src.id & (EcsUp | EcsIsEntity)) { + return true; } } - /* !_ (don't match anything) terms always return nothing. */ - if (term->oper == EcsNot && term->id == EcsAny) { - op.kind = EcsQueryNothing; - flecs_query_op_insert(&op, ctx); - return 0; - } + return false; +} - if (first_or) { - ctx->ctrlflow->cond_written_or = ctx->cond_written; - ctx->ctrlflow->in_or = true; - } else if (is_or) { - ctx->written = ctx->ctrlflow->written_or; - } +static +void flecs_query_cache_for_each_component_monitor( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache, + void(*callback)( + ecs_world_t* world, + ecs_id_t id, + ecs_query_t *q)) +{ + ecs_query_t *q = &impl->pub; + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; - if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { - if (flecs_query_compile_0_src(world, query, term, ctx)) { - goto error; + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; + + if (src->id & EcsUp) { + callback(world, ecs_pair(term->trav, EcsWildcard), q); + if (term->trav != EcsIsA) { + callback(world, ecs_pair(EcsIsA, EcsWildcard), q); + } + callback(world, term->id, q); + + } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { + callback(world, term->id, q); } - return 0; } +} - if (builtin_pred) { - ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); - if (id_noflags == EcsWildcard || id_noflags == EcsAny) { - /* Noop */ - return 0; - } +static +bool flecs_query_cache_is_term_ref_supported( + ecs_term_ref_t *ref) +{ + if (!(ref->id & EcsIsVariable)) { + return true; } + if (ecs_id_is_wildcard(ref->id)) { + return true; + } + return false; +} - op.field_index = flecs_ito(int8_t, term->field_index); - op.term_index = flecs_ito(int8_t, term - q->terms); +static +int flecs_query_cache_process_signature( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; - flecs_query_set_op_kind(&op, term, src_is_var); + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *second = &term->second; - bool is_not = (term->oper == EcsNot) && !builtin_pred; + bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); + bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); + bool is_second_ok = flecs_query_cache_is_term_ref_supported(second); - /* Save write state at start of term so we can use it to reliably track - * variables got written by this term. */ - ecs_write_flags_t cond_write_state = ctx->cond_written; + (void)first; + (void)second; + (void)is_src_ok; + (void)is_first_ok; + (void)is_second_ok; - /* Resolve variables and entities for operation arguments */ - flecs_query_compile_term_ref(world, query, &op, &term->first, - &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); - flecs_query_compile_term_ref(world, query, &op, &term->second, - &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); - flecs_query_compile_term_ref(world, query, &op, &term->src, - &op.src, EcsQuerySrc, EcsVarAny, ctx, true); + /* Queries do not support named variables */ + ecs_check(is_src_ok || ecs_term_match_this(term), + ECS_UNSUPPORTED, NULL); + ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); + ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); + ecs_check(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, + "invalid usage of InOutFilter for query"); - bool src_written = true; - if (src_is_var) { - src_is_lookup = query->vars[op.src.var].lookup != NULL; - src_written = flecs_query_is_written(op.src.var, ctx->written); + if (src->id & EcsCascade) { + ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, + "query can only have one cascade term"); + cache->cascade_by = i + 1; + } } - /* Insert each instructions for table -> entity variable if needed */ - bool first_written, second_written; - if (flecs_query_compile_ensure_vars( - query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) - { - goto error; - } + impl->pub.flags |= + (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); - if (flecs_query_compile_ensure_vars( - query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) - { - goto error; - } + flecs_query_cache_for_each_component_monitor( + world, impl, cache, flecs_monitor_register); - /* Store write state of variables for first OR term in chain which will get - * restored for the other terms in the chain, so that all OR terms make the - * same assumptions about which variables were already written. */ - if (first_or) { - ctx->ctrlflow->written_or = ctx->written; - } + return 0; +error: + return -1; +} - /* If an optional or not term is inserted for a source that's not been - * written to yet, insert instruction that selects all entities so we have - * something to match the optional/not against. */ - if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { - src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); - } +/** 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); - /* A bit of special logic for OR expressions and equality predicates. If the - * left-hand of an equality operator is a table, and there are multiple - * operators in an Or expression, the Or chain should match all entities in - * the table that match the right hand sides of the operator expressions. - * For this to work, the src variable needs to be resolved as entity, as an - * Or chain would otherwise only yield the first match from a table. */ - if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { - if (query->vars[op.src.var].kind == EcsVarTable) { - flecs_query_compile_term_ref(world, query, &op, &term->src, - &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - ctx->ctrlflow->written_or |= (1llu << op.src.var); - } - } + 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; - if (flecs_query_compile_ensure_vars( - query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) - { - goto error; - } + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; - /* If source is Any (_) and first and/or second are unconstrained, insert an - * ids instruction instead of an And */ - if (term->flags_ & EcsTermMatchAnySrc) { - op.kind = EcsQueryIds; - /* Use up-to-date written values after potentially inserting each */ - if (!first_written || !second_written) { - if (!first_written) { - /* If first is unknown, traverse left: <- (*, t) */ - if (ECS_TERM_REF_ID(&term->first) != EcsAny) { - op.kind = EcsQueryIdsLeft; - } + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_remove_table_node(cache, cur); } else { - /* If second is wildcard, traverse right: (r, *) -> */ - if (ECS_TERM_REF_ID(&term->second) != EcsAny) { - op.kind = EcsQueryIdsRight; - } + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_insert_table_node(cache, cur); } - op.src.entity = 0; - src_is_var = false; - op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ - op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); + } + } + + ecs_assert(cur_count || cache->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); +} + +/* Remove table */ +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; + ecs_world_t *world = cache->query->world; + + for (cur = first; cur != NULL; cur = next) { + flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); + + if (cur->ids != cache->query->ids) { + flecs_bfree(&cache->allocators.ids, cur->ids); } - /* If source variable is not written and we're querying just for Any, insert - * a dedicated instruction that uses the Any record in the id index. Any - * queries that are evaluated against written sources can use Wildcard - * records, which is what the AndAny instruction does. */ - } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { - /* Lookup variables ($var.child_name) are always written */ - if (!src_is_lookup) { - op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ + if (cur->sources != cache->sources) { + flecs_bfree(&cache->allocators.sources, cur->sources); } - } - /* If this is a transitive term and both the target and source are unknown, - * find the targets for the relationship first. This clusters together - * tables for the same target, which allows for more efficient usage of the - * traversal caches. */ - if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { - if (!src_written && !second_written) { - flecs_query_insert_unconstrained_transitive( - query, &op, ctx, cond_write); + if (cur->monitor) { + flecs_bfree(&cache->allocators.monitors, cur->monitor); } - } - /* Check if this term has variables that have been conditionally written, - * like variables written by an optional term. */ - if (ctx->cond_written) { - if (!is_or || first_or) { - flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); + if (!elem->hdr.empty) { + flecs_query_cache_remove_table_node(cache, cur); } - } - /* If term can toggle and is Not, change operator to Optional as we - * have to match entities that have the component but disabled. */ - if (toggle_term && is_not) { - is_not = false; - is_optional = true; - } + next = cur->next_match; - /* Handle Not, Optional, Or operators */ - if (is_not) { - flecs_query_begin_block(EcsQueryNot, ctx); - } else if (is_optional) { - flecs_query_begin_block(EcsQueryOptional, ctx); - } else if (first_or) { - flecs_query_begin_block_or(&op, term, ctx); + flecs_bfree(&world->allocators.query_table_match, cur); } +} - /* If term has component inheritance enabled, insert instruction to walk - * down the relationship tree of the id. */ - if (term->flags_ & EcsTermIdInherited) { - flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); +static +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_bfree(&cache->query->world->allocators.query_table, elem); +} + +static +void flecs_query_cache_unmatch_table( + ecs_query_cache_t *cache, + ecs_table_t *table, + ecs_query_cache_table_t *elem) +{ + if (!elem) { + elem = ecs_table_cache_get(&cache->cache, table); } + if (elem) { + ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); + flecs_query_cache_table_free(cache, elem); + } +} - op.match_flags = term->flags_; +/* Rematch system with tables after a change happened to a watched entity */ +static +void flecs_query_cache_rematch_tables( + ecs_world_t *world, + ecs_query_impl_t *impl) +{ + ecs_iter_t it; + ecs_table_t *table = NULL; + ecs_query_cache_table_t *qt = NULL; + ecs_query_cache_table_match_t *qm = NULL; + ecs_query_cache_t *cache = impl->cache; - ecs_write_flags_t write_state = ctx->written; - if (first_is_var) { - op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op.flags |= (EcsQueryIsVar << EcsQueryFirst); + if (cache->monitor_generation == world->monitor_generation) { + return; } - if (term->src.id & EcsSelf) { - op.flags |= EcsQueryIsSelf; + ecs_os_perf_trace_push("flecs.query.rematch"); + + cache->monitor_generation = world->monitor_generation; + + it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); + + world->info.rematch_count_total ++; + int32_t rematch_count = ++ cache->rematch_count; + + ecs_time_t t = {0}; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_measure(&t); } - /* Insert instructions for lookup variables */ - if (first_is_var) { - if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { - write_state |= (1ull << op.first.var); // lookups are resolved inline + 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); + qm->next_match = NULL; + } + + table = it.table; + + qt = ecs_table_cache_get(&cache->cache, table); + if (!qt) { + qt = flecs_query_cache_table_insert(world, cache, table); + } + + ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + qt->rematch_count = rematch_count; + qm = NULL; } - } - if (src_is_var) { - if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { - write_state |= (1ull << op.src.var); // lookups are resolved inline + if (!qm) { + qm = qt->first; + } else { + qm = qm->next_match; } - } - if (second_is_var) { - if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { - write_state |= (1ull << op.second.var); // lookups are resolved inline + if (!qm) { + qm = flecs_query_cache_add_table_match(cache, qt, table); } - } - if (builtin_pred) { - if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { - goto error; + flecs_query_cache_set_table_match(cache, qm, &it); + + if (table && ecs_table_count(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); + flecs_query_cache_insert_table_node(cache, qm); + } } } - /* If we're writing the $this variable, filter out disabled/prefab entities - * unless the query explicitly matches them. - * This could've been done with regular With instructions, but since - * filtering out disabled/prefab entities is the default and this check is - * cheap to perform on table flags, it's worth special casing. */ - if (!src_written && op.src.var == 0) { - op.other = flecs_itolbl(flecs_query_to_table_flags(q)); + if (qm && qm->next_match) { + flecs_query_cache_table_match_free(cache, qt, qm->next_match); + qm->next_match = NULL; } - /* After evaluating a term, a used variable is always written */ - if (src_is_var) { - flecs_query_write(op.src.var, &op.written); - flecs_query_write_ctx(op.src.var, ctx, cond_write); - } - if (first_is_var) { - flecs_query_write(op.first.var, &op.written); - flecs_query_write_ctx(op.first.var, ctx, cond_write); - } - if (second_is_var) { - flecs_query_write(op.second.var, &op.written); - flecs_query_write_ctx(op.second.var, ctx, cond_write); + /* Iterate all tables in cache, remove ones that weren't just matched */ + ecs_table_cache_iter_t cache_it; + if (flecs_table_cache_all_iter(&cache->cache, &cache_it)) { + while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { + if (qt->rematch_count != rematch_count) { + flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); + } + } } - - flecs_query_op_insert(&op, ctx); - ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); - if (is_or && !member_term) { - flecs_query_mark_last_or_op(ctx); + if (world->flags & EcsWorldMeasureFrameTime) { + world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } - /* Handle self-references between src and first/second variables */ - if (src_is_var) { - if (first_is_var) { - flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); - } - if (second_is_var && op.first.var != op.second.var) { - flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); - } + ecs_os_perf_trace_pop("flecs.query.rematch"); +} + +/* -- Private API -- */ + +void flecs_query_cache_notify( + ecs_world_t *world, + ecs_query_t *q, + ecs_query_cache_event_t *event) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + flecs_query_cache_match_table(world, cache, event->table); + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + flecs_query_cache_unmatch_table(cache, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + flecs_query_cache_rematch_tables(world, impl); + break; } +} - /* Handle self references between first and second variables */ - if (!ecs_id_is_wildcard(first_id)) { - if (first_is_var && !first_written && (op.first.var == op.second.var)) { - flecs_query_insert_pair_eq(term->field_index, ctx); +static +int flecs_query_cache_order_by( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_entity_t order_by, + ecs_order_by_action_t order_by_callback, + ecs_sort_table_action_t action) +{ + ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_cache_t *cache = impl->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_id_is_wildcard(order_by), + ECS_INVALID_PARAMETER, NULL); + + /* Find order_by term & make sure it is queried for */ + const ecs_query_t *query = cache->query; + int32_t i, count = query->term_count; + int32_t order_by_term = -1; + + if (order_by) { + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &query->terms[i]; + + /* Only And terms are supported */ + if (term->id == order_by && term->oper == EcsAnd) { + order_by_term = i; + break; + } } - } - - /* Handle closing of Not, Optional and Or operators */ - if (is_not) { - flecs_query_end_block(ctx, true); - } else if (is_optional) { - flecs_query_end_block(ctx, true); - } - /* Now that the term is resolved, evaluate member of component */ - if (member_term) { - flecs_query_compile_end_member_term(world, query, &op, term, ctx, - term_id, first_id, second_id, cond_write); - if (is_or) { - flecs_query_mark_last_or_op(ctx); + if (order_by_term == -1) { + char *id_str = ecs_id_str(world, order_by); + ecs_err("order_by component '%s' is not queried for", id_str); + ecs_os_free(id_str); + goto error; } } - if (last_or) { - flecs_query_end_block_or(query, ctx); - } + cache->order_by = order_by; + cache->order_by_callback = order_by_callback; + cache->order_by_term = order_by_term; + cache->order_by_table_callback = action; - /* Handle closing of conditional evaluation */ - if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { - if (!is_or || last_or) { - flecs_query_end_block_cond_eval(ctx); - } - } + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); + flecs_query_cache_sort_tables(world, impl); - /* Ensure that term id is set after evaluating Not */ - if (term->flags_ & EcsTermIdInherited) { - if (is_not) { - ecs_query_op_t set_id = {0}; - set_id.kind = EcsQuerySetId; - set_id.first.entity = term->id; - set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); - set_id.field_index = flecs_ito(int8_t, term->field_index); - flecs_query_op_insert(&set_id, ctx); - } + if (!cache->table_slices.array) { + flecs_query_cache_build_sorted_tables(cache); } return 0; @@ -68486,7436 +68031,7897 @@ int flecs_query_compile_term( return -1; } -/** - * @file query/engine/cache.c - * @brief Cached query implementation. - */ +static +void flecs_query_cache_group_by( + ecs_query_cache_t *cache, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, + "query is already grouped"); + ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, + "query is already grouped"); + if (!group_by) { + /* Builtin function that groups by relationship */ + group_by = flecs_query_cache_default_group_by; + } -int32_t flecs_query_cache_table_count( - ecs_query_cache_t *cache) -{ - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - return cache->cache.tables.count; -} + cache->group_by = sort_component; + cache->group_by_callback = group_by; -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; + ecs_map_init_w_params(&cache->groups, + &cache->query->world->allocators.query_table_list); +error: + return; } -int32_t flecs_query_cache_entity_count( - const ecs_query_cache_t *cache) +static +void flecs_query_cache_on_event( + ecs_iter_t *it) { - ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); - - int32_t result = 0; - ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; - if (!last) { - return 0; - } - - for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { - result += ecs_table_count(cur->table); + /* Because this is the observer::run callback, checking if this is event is + * already handled is not done for us. */ + ecs_world_t *world = it->world; + ecs_observer_t *o = it->ctx; + ecs_observer_impl_t *o_impl = flecs_observer_impl(o); + if (o_impl->last_event_id) { + if (o_impl->last_event_id[0] == world->event_id) { + return; + } + o_impl->last_event_id[0] = world->event_id; } - return result; -} + ecs_query_impl_t *impl = o->ctx; + flecs_poly_assert(impl, ecs_query_t); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = it->table; + ecs_entity_t event = it->event; -static -uint64_t flecs_query_cache_get_group_id( - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - if (cache->group_by_callback) { - return cache->group_by_callback(cache->query->world, table, - cache->group_by, cache->group_by_ctx); - } else { - return 0; + if (event == EcsOnTableCreate) { + /* Creation of new table */ + if (flecs_query_cache_match_table(world, cache, table)) { + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); + } + } + return; } -} -static -void flecs_query_cache_compute_group_id( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (cache->group_by_callback) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /* The observer isn't doing the matching because the query can do it more + * efficiently by checking the table with the query cache. */ + if (ecs_table_cache_get(&cache->cache, table) == NULL) { + return; + } - match->group_id = flecs_query_cache_get_group_id(cache, table); - } else { - match->group_id = 0; + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); } -} -static -ecs_query_cache_table_list_t* flecs_query_cache_get_group( - const ecs_query_cache_t *cache, - uint64_t group_id) -{ - return ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); + 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) { + /* Deletion of table */ + flecs_query_cache_unmatch_table(cache, table, NULL); + return; + } } static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( - ecs_query_cache_t *cache, - uint64_t id) +void flecs_query_cache_table_cache_free( + ecs_query_cache_t *cache) { - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); + ecs_table_cache_iter_t it; + ecs_query_cache_table_t *qt; - if (!group) { - group = ecs_map_insert_alloc_t(&cache->groups, - ecs_query_cache_table_list_t, id); - ecs_os_zeromem(group); - if (cache->on_group_create) { - group->info.ctx = cache->on_group_create( - cache->query->world, id, cache->group_by_ctx); + if (flecs_table_cache_all_iter(&cache->cache, &it)) { + while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { + flecs_query_cache_table_free(cache, qt); } } - return group; + ecs_table_cache_fini(&cache->cache); } static -void flecs_query_cache_remove_group( - ecs_query_cache_t *cache, - uint64_t id) +void flecs_query_cache_allocators_init( + ecs_query_cache_t *cache) { - if (cache->on_group_delete) { - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); - if (group) { - cache->on_group_delete(cache->query->world, id, - group->info.ctx, cache->group_by_ctx); - } + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_init(&cache->allocators.trs, + field_count * ECS_SIZEOF(ecs_table_record_t*)); + flecs_ballocator_init(&cache->allocators.ids, + field_count * ECS_SIZEOF(ecs_id_t)); + flecs_ballocator_init(&cache->allocators.sources, + field_count * ECS_SIZEOF(ecs_entity_t)); + flecs_ballocator_init(&cache->allocators.monitors, + (1 + field_count) * ECS_SIZEOF(int32_t)); } - - ecs_map_remove_free(&cache->groups, id); } static -uint64_t flecs_query_cache_default_group_by( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) +void flecs_query_cache_allocators_fini( + ecs_query_cache_t *cache) { - (void)ctx; - - ecs_id_t match; - if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { - return ecs_pair_second(world, match); + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_fini(&cache->allocators.trs); + flecs_ballocator_fini(&cache->allocators.ids); + flecs_ballocator_fini(&cache->allocators.sources); + flecs_ballocator_fini(&cache->allocators.monitors); } - return 0; -} - -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_cache_table_match_t* flecs_query_cache_find_group_insertion_node( - ecs_query_cache_t *cache, - uint64_t group_id) +} + +void flecs_query_cache_fini( + ecs_query_impl_t *impl) { - /* Grouping must be enabled */ - ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = impl->pub.world; + ecs_stage_t *stage = impl->stage; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_iter_t it = ecs_map_iter(&cache->groups); - ecs_query_cache_table_list_t *list, *closest_list = NULL; - uint64_t id, closest_id = 0; - - bool desc = false; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (cache->cascade_by) { - desc = (cache->query->terms[ - cache->cascade_by - 1].src.id & EcsDesc) != 0; + if (cache->observer) { + flecs_observer_fini(cache->observer); } - /* Find closest smaller group id */ - while (ecs_map_next(&it)) { - id = ecs_map_key(&it); - - if (!desc) { - if (id >= group_id) { - continue; - } - } else { - if (id <= group_id) { - continue; - } + ecs_group_delete_action_t on_delete = cache->on_group_delete; + if (on_delete) { + ecs_map_iter_t it = ecs_map_iter(&cache->groups); + while (ecs_map_next(&it)) { + ecs_query_cache_table_list_t *group = ecs_map_ptr(&it); + uint64_t group_id = ecs_map_key(&it); + on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); } + cache->on_group_delete = NULL; + } - list = ecs_map_ptr(&it); - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; + if (cache->group_by_ctx_free) { + if (cache->group_by_ctx) { + cache->group_by_ctx_free(cache->group_by_ctx); } + } - bool comp; - if (!desc) { - comp = ((group_id - id) < (group_id - closest_id)); - } else { - comp = ((group_id - id) > (group_id - closest_id)); - } + flecs_query_cache_for_each_component_monitor(world, impl, cache, + flecs_monitor_unregister); + flecs_query_cache_table_cache_free(cache); - if (!closest_list || comp) { - closest_id = id; - closest_list = list; - } - } + ecs_map_fini(&cache->groups); - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); + + if (cache->query->term_count) { + flecs_bfree(&cache->allocators.sources, cache->sources); } + + flecs_query_cache_allocators_fini(cache); + ecs_query_fini(cache->query); + + flecs_bfree(&stage->allocators.query_cache, cache); } -/* Initialize group with first node */ -static -void flecs_query_cache_create_group( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) +/* -- Public API -- */ + +ecs_query_cache_t* flecs_query_cache_init( + ecs_query_impl_t *impl, + const ecs_query_desc_t *const_desc) { - uint64_t group_id = match->group_id; + ecs_world_t *world = impl->pub.real_world; + ecs_stage_t *stage = impl->stage; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_query_desc_t was not initialized to zero"); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot create query during world fini"); - /* If query has grouping enabled & this is a new/empty group, find - * the insertion point for the group */ - ecs_query_cache_table_match_t *insert_after = - flecs_query_cache_find_group_insertion_node(cache, group_id); + /* Create private version of desc to create the uncached query that will + * populate the query cache. */ + ecs_query_desc_t desc = *const_desc; + ecs_entity_t entity = desc.entity; + desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ + desc.group_by_callback = NULL; + desc.group_by = 0; + desc.order_by_callback = NULL; + desc.order_by = 0; + desc.entity = 0; - if (!insert_after) { - /* This group should appear first in the query list */ - ecs_query_cache_table_match_t *query_first = cache->list.first; - if (query_first) { - /* If this is not the first match for the query, insert before it */ - match->next = query_first; - query_first->prev = match; - cache->list.first = match; - } else { - /* If this is the first match of the query, initialize its list */ - ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = match; - cache->list.last = match; - } - } else { - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + /* Don't pass ctx/binding_ctx to uncached query */ + desc.ctx = NULL; + desc.binding_ctx = NULL; + desc.ctx_free = NULL; + desc.binding_ctx_free = NULL; - /* This group should appear after another group */ - ecs_query_cache_table_match_t *insert_before = insert_after->next; - match->prev = insert_after; - insert_after->next = match; - match->next = insert_before; - if (insert_before) { - insert_before->prev = match; - } else { - ecs_assert(cache->list.last == insert_after, - ECS_INTERNAL_ERROR, NULL); - - /* This group should appear last in the query list */ - cache->list.last = match; - } + ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); + result->entity = entity; + impl->cache = result; + + ecs_observer_desc_t observer_desc = { .query = desc }; + observer_desc.query.flags |= EcsQueryNested; + + ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; + desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; + + /* order_by is not compatible with matching empty tables, as it causes + * a query to return table slices, not entire tables. */ + if (const_desc->order_by_callback) { + query_flags &= ~EcsQueryMatchEmptyTables; } -} -/* Find the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_get_group(cache, match->group_id); - } else { - return &cache->list; + ecs_query_t *q = result->query = ecs_query_init(world, &desc); + if (!q) { + goto error; } -} -/* Find or create the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_ensure_group(cache, match->group_id); - } else { - return &cache->list; + /* The uncached query used to populate the cache always matches empty + * tables. This flag determines whether the empty tables are stored + * separately in the cache or are treated as regular tables. This is only + * enabled if the user requested that the query matches empty tables. */ + ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, + !!(query_flags & EcsQueryMatchEmptyTables)); + + flecs_query_cache_allocators_init(result); + + /* Zero'd out sources array that's used for results that only match $this. + * This reduces the amount of memory used by the cache, and improves CPU + * cache locality during iteration when doing source checks. */ + if (result->query->term_count) { + result->sources = flecs_bcalloc(&result->allocators.sources); } -} -/* Remove node from list */ -static -void flecs_query_cache_remove_table_node( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_query_cache_table_match_t *prev = match->prev; - ecs_query_cache_table_match_t *next = match->next; + if (q->term_count) { + observer_desc.run = flecs_query_cache_on_event; + observer_desc.ctx = impl; - ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); + int32_t event_index = 0; + if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { + observer_desc.events[event_index ++] = EcsOnTableEmpty; + observer_desc.events[event_index ++] = EcsOnTableFill; + } - ecs_query_cache_table_list_t *list = - flecs_query_cache_get_node_list(cache, match); + observer_desc.events[event_index ++] = EcsOnTableCreate; + observer_desc.events[event_index ++] = EcsOnTableDelete; + observer_desc.flags_ = EcsObserverBypassQuery; - if (!list || !list->first) { - /* If list contains no matches, the match must be empty */ - ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - return; + /* ecs_query_init could have moved away resources from the terms array + * in the descriptor, so use the terms array from the query. */ + ecs_os_memcpy_n(observer_desc.query.terms, q->terms, + ecs_term_t, FLECS_TERM_COUNT_MAX); + observer_desc.query.expr = NULL; /* Already parsed */ + + result->observer = flecs_observer_init(world, entity, &observer_desc); + if (!result->observer) { + goto error; + } } - ecs_assert(prev != NULL || cache->list.first == match, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || cache->list.last == match, - ECS_INTERNAL_ERROR, NULL); + result->prev_match_count = -1; - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; + if (ecs_should_log_1()) { + char *query_expr = ecs_query_str(result->query); + ecs_dbg_1("#[green]query#[normal] [%s] created", + query_expr ? query_expr : ""); + ecs_os_free(query_expr); } - ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - list->info.table_count --; + ecs_log_push_1(); - if (cache->group_by_callback) { - uint64_t group_id = match->group_id; + if (flecs_query_cache_process_signature(world, impl, result)) { + goto error; + } - /* Make sure query.list is updated if this is the first or last group */ - if (cache->list.first == match) { - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = next; - prev = next; - } - if (cache->list.last == match) { - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.last = prev; - next = prev; - } + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, + flecs_query_cache_group_by_cascade); + result->group_by_ctx = &result->query->terms[cascade_by - 1]; + } - ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - cache->list.info.table_count --; - list->info.match_count ++; + if (const_desc->group_by_callback || const_desc->group_by) { + ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, + "cannot mix cascade and group_by"); + flecs_query_cache_group_by(result, + const_desc->group_by, const_desc->group_by_callback); + result->group_by_ctx = const_desc->group_by_ctx; + result->on_group_create = const_desc->on_group_create; + result->on_group_delete = const_desc->on_group_delete; + result->group_by_ctx_free = const_desc->group_by_ctx_free; + } - /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->group_id != group_id) { - /* The previous node belonged to another group */ - prev = next; - } - if (next && next->group_id != group_id) { - /* The next node belonged to another group */ - next = prev; - } + /* 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); - /* Do check again, in case both prev & next belonged to another group */ - if ((!prev && !next) || (prev && prev->group_id != group_id)) { - /* There are no more matches left in this group */ - flecs_query_cache_remove_group(cache, group_id); - list = NULL; + if (const_desc->order_by_callback) { + if (flecs_query_cache_order_by(world, impl, + const_desc->order_by, const_desc->order_by_callback, + const_desc->order_by_table_callback)) + { + goto error; } } - if (list) { - if (list->first == match) { - list->first = next; - } - if (list->last == match) { - list->last = prev; + if (entity) { + if (!flecs_query_cache_table_count(result) && result->query->term_count){ + ecs_add_id(world, entity, EcsEmpty); } } - match->prev = NULL; - match->next = NULL; + ecs_log_pop_1(); - cache->match_count ++; + return result; +error: + return NULL; } -/* Add node to list */ -static -void flecs_query_cache_insert_table_node( +ecs_query_cache_table_t* flecs_query_cache_get_table( ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) + ecs_table_t *table) { - /* Node should not be part of an existing list */ - ecs_assert(match->prev == NULL && match->next == NULL, - ECS_INTERNAL_ERROR, NULL); - - /* If this is the first match, activate system */ - if (!cache->list.first && cache->entity) { - ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); - } - - flecs_query_cache_compute_group_id(cache, match); - - 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); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_query_cache_table_match_t *last = list->last; - ecs_query_cache_table_match_t *last_next = last->next; + return ecs_table_cache_get(&cache->cache, table); +} - match->prev = last; - match->next = last_next; - last->next = match; +void ecs_iter_set_group( + ecs_iter_t *it, + uint64_t group_id) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, + "cannot set group during iteration"); - if (last_next) { - last_next->prev = match; - } + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *q = flecs_query_impl(qit->query); + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(q, ecs_query_t); + ecs_query_cache_t *cache = q->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); - list->last = match; + ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( + cache, group_id); + if (!node) { + qit->node = NULL; + qit->last = NULL; + return; + } - if (cache->group_by_callback) { - /* Make sure to update query list if this is the last group */ - if (cache->list.last == last) { - cache->list.last = match; - } - } + ecs_query_cache_table_match_t *first = node->first; + if (first) { + qit->node = node->first; + qit->last = node->last; } else { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - - list->first = match; - list->last = match; + qit->node = NULL; + qit->last = NULL; + } + +error: + return; +} - if (cache->group_by_callback) { - /* Initialize group with its first node */ - flecs_query_cache_create_group(cache, match); - } +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( + flecs_query_impl(query)->cache, group_id); + if (!node) { + return NULL; } + + return &node->info; +} - if (cache->group_by_callback) { - list->info.table_count ++; - list->info.match_count ++; +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + const ecs_query_group_info_t *info = ecs_query_get_group_info( + query, group_id); + if (!info) { + return NULL; + } else { + return info->ctx; } +} - cache->list.info.table_count ++; - cache->match_count ++; +/** + * @file query/engine/cache_iter.c + * @brief Compile query term. + */ - ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); +static +void flecs_query_update_node_up_trs( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) +{ + ecs_termset_t fields = node->up_fields & node->set_fields; + if (fields) { + const ecs_query_impl_t *impl = ctx->query; + const ecs_query_t *q = &impl->pub; + ecs_query_cache_t *cache = impl->cache; + int32_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(fields & (1llu << i))) { + continue; + } + + ecs_entity_t src = node->sources[i]; + if (src) { + const ecs_table_record_t *tr = node->trs[i]; + ecs_record_t *r = flecs_entities_get(ctx->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + if (r->table != tr->hdr.table) { + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_assert(idr->id == q->ids[i], ECS_INTERNAL_ERROR, NULL); + tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); + if (cache->field_map) { + ctx->it->trs[cache->field_map[i]] = tr; + } + } + } + } + } } static -ecs_query_cache_table_match_t* flecs_query_cache_cache_add( - ecs_world_t *world, - ecs_query_cache_table_t *elem) +ecs_query_cache_table_match_t* flecs_query_cache_next( + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_match_t *result = - flecs_bcalloc(&world->allocators.query_table_match); - - if (!elem->first) { - elem->first = result; - elem->last = result; - } else { - ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); - elem->last->next_match = result; - elem->last = result; + 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; } - return result; + return NULL; } -/* The group by function for cascade computes the tree depth for the table type. - * This causes tables in the query cache to be ordered by depth, which ensures - * breadth-first iteration order. */ static -uint64_t flecs_query_cache_group_by_cascade( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) +ecs_query_cache_table_match_t* flecs_query_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - (void)id; - ecs_term_t *term = ctx; - ecs_entity_t rel = term->trav; - int32_t depth = flecs_relation_depth(world, rel, table); - return flecs_ito(uint64_t, depth); -} + ecs_iter_t *it = ctx->it; + if (!redo) { + ecs_var_t *var = &ctx->vars[0]; + ecs_table_t *table = var->range.table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); -static -ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *qt, - ecs_table_t *table) -{ - /* Add match for table. One table can have more than one match, if - * the query contains wildcards. */ - ecs_query_cache_table_match_t *qm = flecs_query_cache_cache_add( - cache->query->world, qt); - - qm->table = table; - qm->trs = flecs_balloc(&cache->allocators.trs); + ecs_query_cache_table_t *qt = flecs_query_cache_get_table( + ctx->query->cache, table); + if (!qt) { + return NULL; + } - /* 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); + ecs_query_iter_t *qit = &it->priv_.iter.query; + qit->prev = NULL; + qit->node = qt->first; + qit->last = qt->last; } - return qm; + return flecs_query_cache_next(ctx); } static -void flecs_query_cache_set_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *qm, - ecs_iter_t *it) +void flecs_query_cache_init_mapped_fields( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) { - ecs_query_t *query = cache->query; - int8_t i, field_count = query->field_count; - - ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - - /* Reset resources in case this is an existing record */ - ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), - it->trs, ecs_table_record_t*, field_count); + ecs_iter_t *it = ctx->it; + const ecs_query_impl_t *impl = ctx->query; + ecs_query_cache_t *cache = impl->cache; + int32_t i, field_count = cache->query->field_count; + int8_t *field_map = cache->field_map; - /* Find out whether to store result-specific ids array or fixed array */ - ecs_id_t *ids = cache->query->ids; for (i = 0; i < field_count; i ++) { - if (it->ids[i] != ids[i]) { - break; - } - } + int8_t field_index = field_map[i]; + it->trs[field_index] = node->trs[i]; - if (i != field_count) { - if (qm->ids == ids || !qm->ids) { - qm->ids = flecs_balloc(&cache->allocators.ids); - } - ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); - } else { - if (qm->ids != ids) { - flecs_bfree(&cache->allocators.ids, qm->ids); - qm->ids = ids; - } - } + it->ids[field_index] = node->ids[i]; + it->sources[field_index] = node->sources[i]; - /* Find out whether to store result-specific sources array or fixed array */ - for (i = 0; i < field_count; i ++) { - if (it->sources[i]) { - break; - } - } + ecs_termset_t bit = (ecs_termset_t)(1u << i); + ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); - if (i != field_count) { - if (qm->sources == cache->sources || !qm->sources) { - qm->sources = flecs_balloc(&cache->allocators.sources); - } - ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); - } else { - if (qm->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, qm->sources); - qm->sources = cache->sources; - } + ECS_TERMSET_COND(it->set_fields, field_bit, node->set_fields & bit); + ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); } - - qm->set_fields = it->set_fields; - qm->up_fields = it->up_fields; } -static -ecs_query_cache_table_t* flecs_query_cache_table_insert( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Iterate cache for query that's partially cached */ +bool flecs_query_cache_search( + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); - if (table) { - qt->table_id = table->id; - } else { - qt->table_id = 0; + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + if (!node) { + return false; } - 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); - } + flecs_query_cache_init_mapped_fields(ctx, node); + ctx->vars[0].range.count = node->count; + ctx->vars[0].range.offset = node->offset; - return qt; + flecs_query_update_node_up_trs(ctx, node); + + return true; } -/** Populate query cache with tables */ -static -void flecs_query_cache_match_tables( - ecs_world_t *world, - ecs_query_cache_t *cache) +/* Iterate cache for query that's entirely cached */ +bool flecs_query_is_cache_search( + const ecs_query_run_ctx_t *ctx) { - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; + ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); + if (!node) { + return false; + } - ecs_iter_t it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - ECS_BIT_SET(it.flags, EcsIterTableOnly); + ecs_iter_t *it = ctx->it; + it->trs = node->trs; + it->ids = node->ids; + it->sources = node->sources; + it->set_fields = node->set_fields; + it->up_fields = node->up_fields; - while (ecs_query_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - /* New table matched, add record to cache */ - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - } + flecs_query_update_node_up_trs(ctx, node); - ecs_query_cache_table_match_t *qm = - flecs_query_cache_add_table_match(cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); - } + return true; } -static -bool flecs_query_cache_match_table( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - if (!ecs_map_is_init(&cache->cache.index)) { + ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); + if (!node) { return false; } - ecs_query_cache_table_t *qt = NULL; - ecs_query_t *q = cache->query; - - /* Iterate uncached query for table to check if it matches. If this is a - * wildcard query, a table can match multiple times. */ - ecs_iter_t it = flecs_query_iter(world, q); - it.flags |= EcsIterNoData; - ecs_iter_set_var_as_table(&it, 0, table); + flecs_query_cache_init_mapped_fields(ctx, node); + flecs_query_update_node_up_trs(ctx, node); - while (ecs_query_next(&it)) { - ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); - if (qt == NULL) { - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - } + return true; +} - ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( - cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_is_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) +{ + ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); + if (!node) { + return false; } - return qt != NULL; + ecs_iter_t *it = ctx->it; + it->trs = node->trs; + it->ids = node->ids; + it->sources = node->sources; + + flecs_query_update_node_up_trs(ctx, node); + + return true; } -static -bool flecs_query_cache_has_refs( - ecs_query_cache_t *cache) -{ - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - for (i = 0; i < count; i ++) { - if (terms[i].src.id & (EcsUp | EcsIsEntity)) { - return true; - } - } +/** + * @file query/engine/cache_order_by.c + * @brief Order by implementation + */ + - return false; -} +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) static -void flecs_query_cache_for_each_component_monitor( +void flecs_query_cache_sort_table( ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_cache_t *cache, - void(*callback)( - ecs_world_t* world, - ecs_id_t id, - ecs_query_t *q)) + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare, + ecs_sort_table_action_t sort) { - ecs_query_t *q = &impl->pub; - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *src = &term->src; + int32_t count = ecs_table_count(table); + if (!count) { + /* Nothing to sort */ + return; + } + + if (count < 2) { + return; + } - if (src->id & EcsUp) { - callback(world, ecs_pair(term->trav, EcsWildcard), q); - if (term->trav != EcsIsA) { - callback(world, ecs_pair(EcsIsA, EcsWildcard), q); - } - callback(world, term->id, q); + ecs_entity_t *entities = table->data.entities; + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &table->data.columns[column_index]; + ecs_type_info_t *ti = column->ti; + size = ti->size; + ptr = column->data; + } - } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { - callback(world, term->id, q); - } + if (sort) { + sort(world, table, entities, ptr, size, 0, count - 1, compare); + } else { + flecs_query_cache_sort_table_generic( + world, table, entities, ptr, size, 0, count - 1, compare); } } +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_cache_table_match_t *match; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; + static -bool flecs_query_cache_is_term_ref_supported( - ecs_term_ref_t *ref) +const void* ptr_from_helper( + sort_helper_t *helper) { - if (!(ref->id & EcsIsVariable)) { - return true; - } - if (ecs_id_is_wildcard(ref->id)) { - return true; + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; + } else { + return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } - return false; } static -int flecs_query_cache_process_signature( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_cache_t *cache) +ecs_entity_t e_from_helper( + sort_helper_t *helper) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *src = &term->src; - ecs_term_ref_t *second = &term->second; - - bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); - bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); - bool is_second_ok = flecs_query_cache_is_term_ref_supported(second); - - (void)first; - (void)second; - (void)is_src_ok; - (void)is_first_ok; - (void)is_second_ok; - - /* Queries do not support named variables */ - ecs_check(is_src_ok || ecs_term_match_this(term), - ECS_UNSUPPORTED, NULL); - ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); - ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); - ecs_check(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, - "invalid usage of InOutFilter for query"); - - if (src->id & EcsCascade) { - ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, - "query can only have one cascade term"); - cache->cascade_by = i + 1; - } + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; } - - impl->pub.flags |= - (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); - - flecs_query_cache_for_each_component_monitor( - world, impl, cache, flecs_monitor_register); - - return 0; -error: - return -1; } -/** When a table becomes empty remove it from the query list, or vice versa. */ static -void flecs_query_cache_update_table( +void flecs_query_cache_build_sorted_table_range( ecs_query_cache_t *cache, - ecs_table_t *table, - bool empty) + ecs_query_cache_table_list_t *list) { - 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); + ecs_world_t *world = cache->query->world; + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, + "cannot sort query in multithreaded mode"); - 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; + ecs_entity_t id = cache->order_by; + ecs_order_by_action_t compare = cache->order_by_callback; + int32_t table_count = list->info.table_count; + if (!table_count) { + return; + } - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; + ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_match_t); + int32_t to_sort = 0; + int32_t order_by_term = cache->order_by_term; - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); + sort_helper_t *helper = flecs_alloc_n( + &world->allocator, sort_helper_t, table_count); + ecs_query_cache_table_match_t *cur, *end = list->last->next; + for (cur = list->first; cur != end; cur = cur->next) { + ecs_table_t *table = cur->table; - flecs_query_cache_remove_table_node(cache, cur); + if (ecs_table_count(table) == 0) { + continue; + } + + if (id) { + const ecs_term_t *term = &cache->query->terms[order_by_term]; + int32_t field = term->field_index; + ecs_size_t size = cache->query->sizes[field]; + ecs_entity_t src = cur->sources[field]; + if (src == 0) { + int32_t column_index = cur->trs[field]->column; + ecs_column_t *column = &table->data.columns[column_index]; + helper[to_sort].ptr = column->data; + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_query_cache_insert_table_node(cache, cur); + if (term->src.id & EcsUp) { + ecs_entity_t base = 0; + ecs_search_relation(world, r->table, 0, id, + EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); + if (base && base != src) { /* Component could be inherited */ + r = flecs_entities_get(world, base); + } + } + + helper[to_sort].ptr = ecs_table_get_id( + world, r->table, id, ECS_RECORD_TO_ROW(r->row)); + helper[to_sort].elem_size = size; + helper[to_sort].shared = true; } + ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; } - } - ecs_assert(cur_count || cache->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); -} + helper[to_sort].match = cur; + helper[to_sort].entities = table->data.entities; + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } -/* Remove table */ -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; - ecs_world_t *world = cache->query->world; + ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); - for (cur = first; cur != NULL; cur = next) { - flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); + bool proceed; + do { + int32_t j, min = 0; + proceed = true; - if (cur->ids != cache->query->ids) { - flecs_bfree(&cache->allocators.ids, cur->ids); + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } } - if (cur->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, cur->sources); + if (!proceed) { + break; } - if (cur->monitor) { - flecs_bfree(&cache->allocators.monitors, cur->monitor); + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } + + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); + + if (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + e1 = e_from_helper(&helper[min]); + } } - if (!elem->hdr.empty) { - flecs_query_cache_remove_table_node(cache, cur); + sort_helper_t *cur_helper = &helper[min]; + if (!cur || cur->trs != cur_helper->match->trs) { + cur = ecs_vec_append_t(NULL, &cache->table_slices, + ecs_query_cache_table_match_t); + *cur = *(cur_helper->match); + cur->offset = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; } - next = cur->next_match; + cur_helper->row ++; + } while (proceed); - flecs_bfree(&world->allocators.query_table_match, cur); + /* Iterate through the vector of slices to set the prev/next ptrs. This + * can't be done while building the vector, as reallocs may occur */ + int32_t i, count = ecs_vec_count(&cache->table_slices); + ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); + for (i = 0; i < count; i ++) { + nodes[i].prev = &nodes[i - 1]; + nodes[i].next = &nodes[i + 1]; } -} -static -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_bfree(&cache->query->world->allocators.query_table, elem); + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } -static -void flecs_query_cache_unmatch_table( - ecs_query_cache_t *cache, - ecs_table_t *table, - ecs_query_cache_table_t *elem) +void flecs_query_cache_build_sorted_tables( + ecs_query_cache_t *cache) { - if (!elem) { - elem = ecs_table_cache_get(&cache->cache, table); - } - if (elem) { - ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); - flecs_query_cache_table_free(cache, elem); + ecs_vec_clear(&cache->table_slices); + + if (cache->group_by_callback) { + /* Populate sorted node list in grouping order */ + ecs_query_cache_table_match_t *cur = cache->list.first; + if (cur) { + do { + /* Find list for current group */ + uint64_t group_id = cur->group_id; + ecs_query_cache_table_list_t *list = ecs_map_get_deref( + &cache->groups, ecs_query_cache_table_list_t, group_id); + ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Sort tables in current group */ + flecs_query_cache_build_sorted_table_range(cache, list); + + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } + } else { + flecs_query_cache_build_sorted_table_range(cache, &cache->list); } } -/* Rematch system with tables after a change happened to a watched entity */ -static -void flecs_query_cache_rematch_tables( +void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl) { - ecs_iter_t it; - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; - ecs_query_cache_table_match_t *qm = NULL; ecs_query_cache_t *cache = impl->cache; - - if (cache->monitor_generation == world->monitor_generation) { + ecs_order_by_action_t compare = cache->order_by_callback; + if (!compare) { return; } - ecs_os_perf_trace_push("flecs.query.rematch"); - - cache->monitor_generation = world->monitor_generation; - - it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - - world->info.rematch_count_total ++; - int32_t rematch_count = ++ cache->rematch_count; - - ecs_time_t t = {0}; - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_measure(&t); - } - - 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); - qm->next_match = NULL; - } + ecs_sort_table_action_t sort = cache->order_by_table_callback; + + ecs_entity_t order_by = cache->order_by; + int32_t order_by_term = cache->order_by_term; - table = it.table; + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ - qt = ecs_table_cache_get(&cache->cache, table); - if (!qt) { - qt = flecs_query_cache_table_insert(world, cache, table); - } + bool tables_sorted = false; - ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - qt->rematch_count = rematch_count; - qm = NULL; - } - if (!qm) { - qm = qt->first; - } else { - qm = qm->next_match; - } - if (!qm) { - qm = flecs_query_cache_add_table_match(cache, qt, table); - } + 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_query_cache_set_table_match(cache, qm, &it); + while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { + ecs_table_t *table = qt->hdr.table; + bool dirty = false; - if (table && ecs_table_count(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); - flecs_query_cache_insert_table_node(cache, qm); - } + if (flecs_query_check_table_monitor(impl, qt, 0)) { + tables_sorted = true; + dirty = true; } - } - - if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qt, qm->next_match); - qm->next_match = NULL; - } - /* Iterate all tables in cache, remove ones that weren't just matched */ - ecs_table_cache_iter_t cache_it; - if (flecs_table_cache_all_iter(&cache->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { - if (qt->rematch_count != rematch_count) { - flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); + int32_t column = -1; + if (order_by) { + if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { + dirty = true; } - } - } - - if (world->flags & EcsWorldMeasureFrameTime) { - world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } - - ecs_os_perf_trace_pop("flecs.query.rematch"); -} - -/* -- Private API -- */ - -void flecs_query_cache_notify( - ecs_world_t *world, - ecs_query_t *q, - ecs_query_cache_event_t *event) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_cache_t *cache = impl->cache; - - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - flecs_query_cache_match_table(world, cache, event->table); - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, event->table, NULL); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - flecs_query_cache_rematch_tables(world, impl); - break; - } -} -static -int flecs_query_cache_order_by( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_entity_t order_by, - ecs_order_by_action_t order_by_callback, - ecs_sort_table_action_t action) -{ - ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_query_cache_t *cache = impl->cache; - ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_id_is_wildcard(order_by), - ECS_INVALID_PARAMETER, NULL); + if (dirty) { + column = -1; - /* Find order_by term & make sure it is queried for */ - const ecs_query_t *query = cache->query; - int32_t i, count = query->term_count; - int32_t order_by_term = -1; + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr, table); + if (tr) { + column = tr->column; + } - if (order_by) { - for (i = 0; i < count; i ++) { - const ecs_term_t *term = &query->terms[i]; - - /* Only And terms are supported */ - if (term->id == order_by && term->oper == EcsAnd) { - order_by_term = i; - break; + if (column == -1) { + /* Component is shared, no sorting is needed */ + dirty = false; + } } } - if (order_by_term == -1) { - char *id_str = ecs_id_str(world, order_by); - ecs_err("order_by component '%s' is not queried for", id_str); - ecs_os_free(id_str); - goto error; + if (!dirty) { + continue; } - } - cache->order_by = order_by; - cache->order_by_callback = order_by_callback; - cache->order_by_term = order_by_term; - cache->order_by_table_callback = action; - - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - flecs_query_cache_sort_tables(world, impl); + /* Something has changed, sort the table. Prefers using + * flecs_query_cache_sort_table when available */ + flecs_query_cache_sort_table(world, table, column, compare, sort); + tables_sorted = true; + } - if (!cache->table_slices.array) { + if (tables_sorted || cache->match_count != cache->prev_match_count) { flecs_query_cache_build_sorted_tables(cache); + cache->match_count ++; /* Increase version if tables changed */ } - - return 0; -error: - return -1; } -static -void flecs_query_cache_group_by( - ecs_query_cache_t *cache, - ecs_entity_t sort_component, - ecs_group_by_action_t group_by) -{ - ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, - "query is already grouped"); - ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, - "query is already grouped"); +/** + * @file query/engine/change_detection.c + * @brief Compile query term. + */ - if (!group_by) { - /* Builtin function that groups by relationship */ - group_by = flecs_query_cache_default_group_by; - } - cache->group_by = sort_component; - cache->group_by_callback = group_by; +typedef struct { + ecs_table_t *table; + int32_t column; +} flecs_table_column_t; - ecs_map_init_w_params(&cache->groups, - &cache->query->world->allocators.query_table_list); -error: - return; +static +void flecs_query_get_column_for_field( + const ecs_query_t *q, + ecs_query_cache_table_match_t *match, + int32_t field, + flecs_table_column_t *out) +{ + ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); + (void)q; + + const ecs_table_record_t *tr = match->trs[field]; + ecs_table_t *table = tr->hdr.table; + int32_t column = tr->column; + + out->table = table; + out->column = column; } +/* Get match monitor. Monitors are used to keep track of whether components + * matched by the query in a table have changed. */ static -void flecs_query_cache_on_event( - ecs_iter_t *it) +bool flecs_query_get_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match) { - /* Because this is the observer::run callback, checking if this is event is - * already handled is not done for us. */ - ecs_world_t *world = it->world; - ecs_observer_t *o = it->ctx; - ecs_observer_impl_t *o_impl = flecs_observer_impl(o); - if (o_impl->last_event_id) { - if (o_impl->last_event_id[0] == world->event_id) { - return; - } - o_impl->last_event_id[0] = world->event_id; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + if (match->monitor) { + return false; } - ecs_query_impl_t *impl = o->ctx; - flecs_poly_assert(impl, ecs_query_t); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = it->table; - ecs_entity_t event = it->event; + int32_t *monitor = flecs_balloc(&cache->allocators.monitors); + monitor[0] = 0; - if (event == EcsOnTableCreate) { - /* Creation of new table */ - if (flecs_query_cache_match_table(world, cache, table)) { - if (ecs_should_log_3()) { - char *table_str = ecs_table_str(world, table); - ecs_dbg_3("query cache event: %s for [%s]", - ecs_get_name(world, event), - table_str); - ecs_os_free(table_str); + /* Mark terms that don't need to be monitored. This saves time when reading + * and/or updating the monitor. */ + const ecs_query_t *q = cache->query; + int32_t i, field = -1, term_count = q->term_count; + flecs_table_column_t tc; + + for (i = 0; i < term_count; i ++) { + if (field == q->terms[i].field_index) { + if (monitor[field + 1] != -1) { + continue; } } - return; - } - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + field = q->terms[i].field_index; + monitor[field + 1] = -1; - /* The observer isn't doing the matching because the query can do it more - * efficiently by checking the table with the query cache. */ - if (ecs_table_cache_get(&cache->cache, table) == NULL) { - return; - } + /* If term isn't read, don't monitor */ + if (q->terms[i].inout != EcsIn && + q->terms[i].inout != EcsInOut && + q->terms[i].inout != EcsInOutDefault) { + continue; + } - if (ecs_should_log_3()) { - char *table_str = ecs_table_str(world, table); - ecs_dbg_3("query cache event: %s for [%s]", - ecs_get_name(world, event), - table_str); - ecs_os_free(table_str); - } + /* Don't track fields that aren't set */ + if (!(match->set_fields & (1llu << field))) { + continue; + } - 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) { - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, table, NULL); - return; + flecs_query_get_column_for_field(q, match, field, &tc); + if (tc.column == -1) { + continue; /* Don't track terms that aren't stored */ + } + + monitor[field + 1] = 0; } -} -static -void flecs_query_cache_table_cache_free( - ecs_query_cache_t *cache) -{ - ecs_table_cache_iter_t it; - ecs_query_cache_table_t *qt; + match->monitor = monitor; - if (flecs_table_cache_all_iter(&cache->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - flecs_query_cache_table_free(cache, qt); - } - } + impl->pub.flags |= EcsQueryHasMonitor; - ecs_table_cache_fini(&cache->cache); + return true; } +/* Get monitor for fixed query terms. Fixed terms are handled separately as they + * don't require a query cache, and fixed terms aren't stored in the cache. */ static -void flecs_query_cache_allocators_init( - ecs_query_cache_t *cache) +bool flecs_query_get_fixed_monitor( + ecs_query_impl_t *impl, + bool check) { - int32_t field_count = cache->query->field_count; - if (field_count) { - flecs_ballocator_init(&cache->allocators.trs, - field_count * ECS_SIZEOF(ecs_table_record_t*)); - flecs_ballocator_init(&cache->allocators.ids, - field_count * ECS_SIZEOF(ecs_id_t)); - flecs_ballocator_init(&cache->allocators.sources, - field_count * ECS_SIZEOF(ecs_entity_t)); - flecs_ballocator_init(&cache->allocators.monitors, - (1 + field_count) * ECS_SIZEOF(int32_t)); - } -} + ecs_query_t *q = &impl->pub; + ecs_world_t *world = q->world; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; -static -void flecs_query_cache_allocators_fini( - ecs_query_cache_t *cache) -{ - int32_t field_count = cache->query->field_count; - if (field_count) { - flecs_ballocator_fini(&cache->allocators.trs); - flecs_ballocator_fini(&cache->allocators.ids); - flecs_ballocator_fini(&cache->allocators.sources); - flecs_ballocator_fini(&cache->allocators.monitors); + if (!impl->monitor) { + impl->monitor = flecs_alloc_n(&impl->stage->allocator, + int32_t, q->field_count); + check = false; /* If the monitor is new, initialize it with dirty state */ } -} -void flecs_query_cache_fini( - ecs_query_impl_t *impl) -{ - ecs_world_t *world = impl->pub.world; - ecs_stage_t *stage = impl->stage; - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int16_t field_index = term->field_index; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { + continue; /* If term doesn't read data there's nothing to track */ + } - if (cache->observer) { - flecs_observer_fini(cache->observer); - } + if (!(term->src.id & EcsIsEntity)) { + continue; /* Not a term with a fixed source */ + } - ecs_group_delete_action_t on_delete = cache->on_group_delete; - if (on_delete) { - ecs_map_iter_t it = ecs_map_iter(&cache->groups); - while (ecs_map_next(&it)) { - ecs_query_cache_table_list_t *group = ecs_map_ptr(&it); - uint64_t group_id = ecs_map_key(&it); - on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); + ecs_entity_t src = ECS_TERM_REF_ID(&term->src); + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !r->table) { + continue; /* Entity is empty, nothing to track */ } - cache->on_group_delete = NULL; - } - if (cache->group_by_ctx_free) { - if (cache->group_by_ctx) { - cache->group_by_ctx_free(cache->group_by_ctx); + ecs_id_record_t *idr = flecs_id_record_get(world, term->id); + if (!idr) { + continue; /* If id doesn't exist, entity can't have it */ } - } - flecs_query_cache_for_each_component_monitor(world, impl, cache, - flecs_monitor_unregister); - flecs_query_cache_table_cache_free(cache); + ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); + if (!tr) { + continue; /* Entity doesn't have the component */ + } - ecs_map_fini(&cache->groups); + /* Copy/check column dirty state from table */ + int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - - if (cache->query->term_count) { - flecs_bfree(&cache->allocators.sources, cache->sources); + if (!check) { + impl->monitor[field_index] = dirty_state[tr->column + 1]; + } else { + if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { + return true; + } + } } - flecs_query_cache_allocators_fini(cache); - ecs_query_fini(cache->query); - - flecs_bfree(&stage->allocators.query_cache, cache); + return !check; } -/* -- Public API -- */ - -ecs_query_cache_t* flecs_query_cache_init( - ecs_query_impl_t *impl, - const ecs_query_desc_t *const_desc) +bool flecs_query_update_fixed_monitor( + ecs_query_impl_t *impl) { - ecs_world_t *world = impl->pub.real_world; - ecs_stage_t *stage = impl->stage; - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_query_desc_t was not initialized to zero"); - ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, - "cannot create query during world fini"); - - /* Create private version of desc to create the uncached query that will - * populate the query cache. */ - ecs_query_desc_t desc = *const_desc; - ecs_entity_t entity = desc.entity; - desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ - desc.group_by_callback = NULL; - desc.group_by = 0; - desc.order_by_callback = NULL; - desc.order_by = 0; - desc.entity = 0; - - /* Don't pass ctx/binding_ctx to uncached query */ - desc.ctx = NULL; - desc.binding_ctx = NULL; - desc.ctx_free = NULL; - desc.binding_ctx_free = NULL; + return flecs_query_get_fixed_monitor(impl, false); +} - ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); - result->entity = entity; - impl->cache = result; +bool flecs_query_check_fixed_monitor( + ecs_query_impl_t *impl) +{ + return flecs_query_get_fixed_monitor(impl, true); +} - ecs_observer_desc_t observer_desc = { .query = desc }; - observer_desc.query.flags |= EcsQueryNested; - ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; - desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; +/* Check if single match term has changed */ +static +bool flecs_query_check_match_monitor_term( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match, + int32_t field) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - /* order_by is not compatible with matching empty tables, as it causes - * a query to return table slices, not entire tables. */ - if (const_desc->order_by_callback) { - query_flags &= ~EcsQueryMatchEmptyTables; + if (flecs_query_get_match_monitor(impl, match)) { + return true; } - ecs_query_t *q = result->query = ecs_query_init(world, &desc); - if (!q) { - goto error; + int32_t *monitor = match->monitor; + int32_t state = monitor[field]; + if (state == -1) { + return false; } - /* The uncached query used to populate the cache always matches empty - * tables. This flag determines whether the empty tables are stored - * separately in the cache or are treated as regular tables. This is only - * enabled if the user requested that the query matches empty tables. */ - ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, - !!(query_flags & EcsQueryMatchEmptyTables)); - - flecs_query_cache_allocators_init(result); - - /* Zero'd out sources array that's used for results that only match $this. - * This reduces the amount of memory used by the cache, and improves CPU - * cache locality during iteration when doing source checks. */ - if (result->query->term_count) { - result->sources = flecs_bcalloc(&result->allocators.sources); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (!field) { + return monitor[0] != dirty_state[0]; + } + } else if (!field) { + return false; } - if (q->term_count) { - observer_desc.run = flecs_query_cache_on_event; - observer_desc.ctx = impl; + flecs_table_column_t cur; + flecs_query_get_column_for_field( + &impl->pub, match, field - 1, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - int32_t event_index = 0; - if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { - observer_desc.events[event_index ++] = EcsOnTableEmpty; - observer_desc.events[event_index ++] = EcsOnTableFill; - } + return monitor[field] != flecs_table_get_dirty_state( + cache->query->world, cur.table)[cur.column + 1]; +} - observer_desc.events[event_index ++] = EcsOnTableCreate; - observer_desc.events[event_index ++] = EcsOnTableDelete; - observer_desc.flags_ = EcsObserverBypassQuery; +static +bool flecs_query_check_cache_monitor( + ecs_query_impl_t *impl) +{ + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - /* ecs_query_init could have moved away resources from the terms array - * in the descriptor, so use the terms array from the query. */ - ecs_os_memcpy_n(observer_desc.query.terms, q->terms, - ecs_term_t, FLECS_TERM_COUNT_MAX); - observer_desc.query.expr = NULL; /* Already parsed */ + /* If the match count changed, tables got matched/unmatched for the + * cache, so return that the query has changed. */ + if (cache->match_count != cache->prev_match_count) { + return true; + } - result->observer = flecs_observer_init(world, entity, &observer_desc); - if (!result->observer) { - goto error; + ecs_table_cache_iter_t it; + if (flecs_table_cache_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)) { + return true; + } } } - result->prev_match_count = -1; + return false; +} - if (ecs_should_log_1()) { - char *query_expr = ecs_query_str(result->query); - ecs_dbg_1("#[green]query#[normal] [%s] created", - query_expr ? query_expr : ""); - ecs_os_free(query_expr); +static +void flecs_query_init_query_monitors( + ecs_query_impl_t *impl) +{ + /* Change monitor for cache */ + ecs_query_cache_t *cache = impl->cache; + if (cache) { + ecs_query_cache_table_match_t *cur = cache->list.first; + + /* Ensure each match has a monitor */ + for (; cur != NULL; cur = cur->next) { + ecs_query_cache_table_match_t *match = + (ecs_query_cache_table_match_t*)cur; + flecs_query_get_match_monitor(impl, match); + } } +} - ecs_log_push_1(); +static +bool flecs_query_check_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match, + const ecs_iter_t *it) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_query_cache_process_signature(world, impl, result)) { - goto error; + if (flecs_query_get_match_monitor(impl, match)) { + return true; } - /* Group before matching so we won't have to move tables around later */ - int32_t cascade_by = result->cascade_by; - if (cascade_by) { - flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, - flecs_query_cache_group_by_cascade); - result->group_by_ctx = &result->query->terms[cascade_by - 1]; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = NULL; + if (table) { + dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (monitor[0] != dirty_state[0]) { + return true; + } } - if (const_desc->group_by_callback || const_desc->group_by) { - ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, - "cannot mix cascade and group_by"); - flecs_query_cache_group_by(result, - const_desc->group_by, const_desc->group_by_callback); - result->group_by_ctx = const_desc->group_by_ctx; - result->on_group_create = const_desc->on_group_create; - result->on_group_delete = const_desc->on_group_delete; - result->group_by_ctx_free = const_desc->group_by_ctx_free; - } + const ecs_query_t *query = cache->query; + ecs_world_t *world = query->world; + int32_t i, field_count = query->field_count; + ecs_entity_t *sources = match->sources; + const ecs_table_record_t **trs = it ? it->trs : match->trs; + ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; + + ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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); + for (i = 0; i < field_count; i ++) { + int32_t mon = monitor[i + 1]; + if (mon == -1) { + continue; + } - if (const_desc->order_by_callback) { - if (flecs_query_cache_order_by(world, impl, - const_desc->order_by, const_desc->order_by_callback, - const_desc->order_by_table_callback)) - { - goto error; + if (!(set_fields & (1llu << i))) { + continue; } - } - if (entity) { - if (!flecs_query_cache_table_count(result) && result->query->term_count){ - ecs_add_id(world, entity, EcsEmpty); + int32_t column = trs[i]->column; + ecs_entity_t src = sources[i]; + if (!src) { + if (column >= 0) { + /* owned component */ + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (mon != dirty_state[column + 1]) { + return true; + } + continue; + } else if (column == -1) { + continue; /* owned but not a component */ + } } - } - ecs_log_pop_1(); + /* Component from non-this source */ + ecs_entity_t fixed_src = match->sources[i]; + ecs_table_t *src_table = ecs_get_table(world, fixed_src); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } - return result; -error: - return NULL; + return false; } -ecs_query_cache_table_t* flecs_query_cache_get_table( - ecs_query_cache_t *cache, - ecs_table_t *table) +/* Check if any term for matched table has changed */ +bool flecs_query_check_table_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_t *table, + int32_t field) { - return ecs_table_cache_get(&cache->cache, table); + ecs_query_cache_table_match_t *cur, *end = table->last->next; + + for (cur = table->first; cur != end; cur = cur->next) { + ecs_query_cache_table_match_t *match = + (ecs_query_cache_table_match_t*)cur; + if (field == -1) { + if (flecs_query_check_match_monitor(impl, match, NULL)) { + return true; + } + } else { + if (flecs_query_check_match_monitor_term(impl, match, field)) { + return true; + } + } + } + + return false; } -void ecs_iter_set_group( - ecs_iter_t *it, - uint64_t group_id) +void flecs_query_mark_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, - "cannot set group during iteration"); - - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *q = flecs_query_impl(qit->query); - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_poly_assert(q, ecs_query_t); - ecs_query_cache_t *cache = q->cache; - ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_t *q = &impl->pub; - ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( - cache, group_id); - if (!node) { - qit->node = NULL; - qit->last = NULL; + /* Evaluate all writeable non-fixed fields, set fields */ + ecs_termset_t write_fields = + (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); + if (!write_fields || (it->flags & EcsIterNoData)) { return; } - ecs_query_cache_table_match_t *first = node->first; - if (first) { - qit->node = node->first; - qit->last = node->last; - } else { - qit->node = NULL; - qit->last = NULL; - } - -error: - return; -} + ecs_world_t *world = q->world; + int16_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << i); + if (!(write_fields & field_bit)) { + continue; /* If term doesn't write data there's nothing to track */ + } -const ecs_query_group_info_t* ecs_query_get_group_info( - const ecs_query_t *query, - uint64_t group_id) -{ - flecs_poly_assert(query, ecs_query_t); - ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( - flecs_query_impl(query)->cache, group_id); - if (!node) { - return NULL; + ecs_entity_t src = it->sources[i]; + ecs_table_t *table; + if (!src) { + table = it->table; + } else { + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !(table = r->table)) { + continue; + } + + if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { + /* Shared fields that aren't marked explicitly as out/inout + * default to readonly */ + continue; + } + } + + int32_t type_index = it->trs[i]->index; + ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; + } + + ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[type_index]; + dirty_state[column + 1] ++; } - - return &node->info; } -void* ecs_query_get_group_ctx( - const ecs_query_t *query, - uint64_t group_id) +void flecs_query_mark_fixed_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - flecs_poly_assert(query, ecs_query_t); - const ecs_query_group_info_t *info = ecs_query_get_group_info( - query, group_id); - if (!info) { - return NULL; - } else { - return info->ctx; + /* This function marks fields dirty for terms with fixed sources. */ + ecs_query_t *q = &impl->pub; + ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; + if (!fixed_write_fields) { + return; } -} - -/** - * @file query/engine/cache_iter.c - * @brief Compile query term. - */ + ecs_world_t *world = q->world; + int32_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { + continue; /* If term doesn't write data there's nothing to track */ + } -static -void flecs_query_update_node_up_trs( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) -{ - ecs_termset_t fields = node->up_fields & node->set_fields; - if (fields) { - const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; - ecs_query_cache_t *cache = impl->cache; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fields & (1llu << i))) { - continue; - } + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* If the field is optional, it's possible that it didn't match */ + continue; + } - ecs_entity_t src = node->sources[i]; - if (src) { - const ecs_table_record_t *tr = node->trs[i]; - ecs_record_t *r = flecs_entities_get(ctx->world, src); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - if (r->table != tr->hdr.table) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->id == q->ids[i], ECS_INTERNAL_ERROR, NULL); - tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); - if (cache->field_map) { - ctx->it->trs[cache->field_map[i]] = tr; - } - } - } + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; } + + ecs_assert(it->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[it->trs[i]->column]; + dirty_state[column + 1] ++; } } -static -ecs_query_cache_table_match_t* flecs_query_cache_next( - const ecs_query_run_ctx_t *ctx) +/* Synchronize match monitor with table dirty state */ +void flecs_query_sync_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *match) { - 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; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - 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; + if (!match->monitor) { + if (impl->pub.flags & EcsQueryHasMonitor) { + flecs_query_get_match_monitor(impl, match); + } else { + return; + } } - return NULL; -} + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + } -static -ecs_query_cache_table_match_t* flecs_query_test( - const ecs_query_run_ctx_t *ctx, - bool redo) -{ - ecs_iter_t *it = ctx->it; - if (!redo) { - ecs_var_t *var = &ctx->vars[0]; - ecs_table_t *table = var->range.table; - ecs_assert(table != NULL, ECS_INVALID_OPERATION, - "the variable set on the iterator is missing a table"); + ecs_query_t *q = cache->query; + { + flecs_table_column_t tc; + int32_t t, term_count = q->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = q->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } - ecs_query_cache_table_t *qt = flecs_query_cache_get_table( - ctx->query->cache, table); - if (!qt) { - return NULL; - } + flecs_query_get_column_for_field(q, match, field, &tc); - ecs_query_iter_t *qit = &it->priv_.iter.query; - qit->prev = NULL; - qit->node = qt->first; - qit->last = qt->last; + monitor[field + 1] = flecs_table_get_dirty_state( + q->world, tc.table)[tc.column + 1]; + } } - return flecs_query_cache_next(ctx); + cache->prev_match_count = cache->match_count; } -static -void flecs_query_cache_init_mapped_fields( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) +bool ecs_query_changed( + ecs_query_t *q) { - ecs_iter_t *it = ctx->it; - const ecs_query_impl_t *impl = ctx->query; - ecs_query_cache_t *cache = impl->cache; - int32_t i, field_count = cache->query->field_count; - int8_t *field_map = cache->field_map; - - for (i = 0; i < field_count; i ++) { - int8_t field_index = field_map[i]; - it->trs[field_index] = node->trs[i]; - - it->ids[field_index] = node->ids[i]; - it->sources[field_index] = node->sources[i]; + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_termset_t bit = (ecs_termset_t)(1u << i); - ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); + ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, + "change detection is only supported on cached queries"); - ECS_TERMSET_COND(it->set_fields, field_bit, node->set_fields & bit); - ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); + /* If query reads terms with fixed sources, check those first as that's + * cheaper than checking entries in the cache. */ + if (impl->monitor) { + if (flecs_query_check_fixed_monitor(impl)) { + return true; + } } -} -/* Iterate cache for query that's partially cached */ -bool flecs_query_cache_search( - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); - if (!node) { - return false; - } + /* Check cache for changes. We can't detect changes for terms that are not + * 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); - flecs_query_cache_init_mapped_fields(ctx, node); - ctx->vars[0].range.count = node->count; - ctx->vars[0].range.offset = node->offset; + if (!(impl->pub.flags & EcsQueryHasMonitor)) { + flecs_query_init_query_monitors(impl); + } - flecs_query_update_node_up_trs(ctx, node); + /* Check cache entries for changes */ + return flecs_query_check_cache_monitor(impl); + } - return true; + return false; } -/* Iterate cache for query that's entirely cached */ -bool flecs_query_is_cache_search( - const ecs_query_run_ctx_t *ctx) +bool ecs_iter_changed( + ecs_iter_t *it) { - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - it->set_fields = node->set_fields; - it->up_fields = node->up_fields; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); - flecs_query_update_node_up_trs(ctx, node); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = flecs_query_impl(qit->query); + ecs_query_t *q = &impl->pub; - return true; -} + /* First check for changes for terms with fixed sources, if query has any */ + if (q->read_fields & q->fixed_fields) { + /* Detecting changes for uncached terms is costly, so only do it once + * per iteration. */ + if (!(it->flags & EcsIterFixedInChangeComputed)) { + it->flags |= EcsIterFixedInChangeComputed; + ECS_BIT_COND(it->flags, EcsIterFixedInChanged, + flecs_query_check_fixed_monitor(impl)); + } -/* Test if query that is entirely cached matches constrained $this */ -bool flecs_query_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo) -{ - ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); - if (!node) { - return false; + if (it->flags & EcsIterFixedInChanged) { + return true; + } } - flecs_query_cache_init_mapped_fields(ctx, node); - flecs_query_update_node_up_trs(ctx, node); + /* If query has a cache, check for changes in current matched result */ + if (impl->cache) { + ecs_query_cache_table_match_t *qm = + (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; + ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_query_check_match_monitor(impl, qm, it); + } - return true; +error: + return false; } -/* Test if query that is entirely cached matches constrained $this */ -bool flecs_query_is_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo) +void ecs_iter_skip( + ecs_iter_t *it) { - ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - - flecs_query_update_node_up_trs(ctx, node); - - return true; + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + it->flags |= EcsIterSkip; } /** - * @file query/engine/cache_order_by.c - * @brief Order by implementation + * @file query/engine/eval.c + * @brief Query engine implementation. */ -ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) +// #define FLECS_QUERY_TRACE + +#ifdef FLECS_QUERY_TRACE +static int flecs_query_trace_indent = 0; +#endif static -void flecs_query_cache_sort_table( - ecs_world_t *world, - ecs_table_t *table, - int32_t column_index, - ecs_order_by_action_t compare, - ecs_sort_table_action_t sort) +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_select_w_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_id_t id, + ecs_flags32_t filter_mask) { - int32_t count = ecs_table_count(table); - if (!count) { - /* Nothing to sort */ - return; - } - - if (count < 2) { - return; - } + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + ecs_table_record_t *tr; + ecs_table_t *table; - ecs_entity_t *entities = table->data.entities; - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_column_t *column = &table->data.columns[column_index]; - ecs_type_info_t *ti = column->ti; - size = ti->size; - ptr = column->data; - } + if (!redo) { + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } - if (sort) { - sort(world, table, entities, ptr, size, 0, count - 1, compare); - } else { - flecs_query_cache_sort_table_generic( - world, table, entities, ptr, size, 0, count - 1, compare); + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } } -} -/* Helper struct for building sorted table ranges */ -typedef struct sort_helper_t { - ecs_query_cache_table_match_t *match; - ecs_entity_t *entities; - const void *ptr; - int32_t row; - int32_t elem_size; - int32_t count; - bool shared; -} sort_helper_t; +repeat: + if (!redo || !op_ctx->remaining) { + tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } -static -const void* ptr_from_helper( - sort_helper_t *helper) -{ - ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); - if (helper->shared) { - return helper->ptr; + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); + table = tr->hdr.table; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } else { - return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); + tr = (ecs_table_record_t*)op_ctx->it.cur; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + table = tr->hdr.table; + op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + op_ctx->remaining --; + } + + if (flecs_query_table_filter(table, op->other, filter_mask)) { + goto repeat; } + + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; } -static -ecs_entity_t e_from_helper( - sort_helper_t *helper) +bool flecs_query_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; + ecs_id_t id = 0; + if (!redo) { + id = flecs_query_op_get_id(op, ctx); } + return flecs_query_select_w_id(op, redo, ctx, id, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } -static -void flecs_query_cache_build_sorted_table_range( - ecs_query_cache_t *cache, - ecs_query_cache_table_list_t *list) +bool flecs_query_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_world_t *world = cache->query->world; - ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, - "cannot sort query in multithreaded mode"); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + ecs_table_record_t *tr; - ecs_entity_t id = cache->order_by; - ecs_order_by_action_t compare = cache->order_by_callback; - int32_t table_count = list->info.table_count; - if (!table_count) { - return; + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + if (!table) { + return false; } - ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_match_t); - int32_t to_sort = 0; - int32_t order_by_term = cache->order_by_term; - - sort_helper_t *helper = flecs_alloc_n( - &world->allocator, sort_helper_t, table_count); - ecs_query_cache_table_match_t *cur, *end = list->last->next; - for (cur = list->first; cur != end; cur = cur->next) { - ecs_table_t *table = cur->table; - - if (ecs_table_count(table) == 0) { - continue; + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } } - if (id) { - const ecs_term_t *term = &cache->query->terms[order_by_term]; - int32_t field = term->field_index; - ecs_size_t size = cache->query->sizes[field]; - ecs_entity_t src = cur->sources[field]; - if (src == 0) { - int32_t column_index = cur->trs[field]->column; - ecs_column_t *column = &table->data.columns[column_index]; - helper[to_sort].ptr = column->data; - helper[to_sort].elem_size = size; - helper[to_sort].shared = false; - } else { - ecs_record_t *r = flecs_entities_get(world, src); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (term->src.id & EcsUp) { - ecs_entity_t base = 0; - ecs_search_relation(world, r->table, 0, id, - EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); - if (base && base != src) { /* Component could be inherited */ - r = flecs_entities_get(world, base); - } - } + tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - helper[to_sort].ptr = ecs_table_get_id( - world, r->table, id, ECS_RECORD_TO_ROW(r->row)); - helper[to_sort].elem_size = size; - helper[to_sort].shared = true; - } - ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); - } else { - helper[to_sort].ptr = NULL; - helper[to_sort].elem_size = 0; - helper[to_sort].shared = false; + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count); + op_ctx->it.cur = &tr->hdr; + } else { + ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); + if (--op_ctx->remaining <= 0) { + return false; } - helper[to_sort].match = cur; - helper[to_sort].entities = table->data.entities; - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; + op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); - - bool proceed; - do { - int32_t j, min = 0; - proceed = true; - - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; - } - } - - if (!proceed) { - break; - } + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; +} - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } +static +bool flecs_query_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_with(op, redo, ctx); + } else { + return flecs_query_select(op, redo, ctx); + } +} - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); +bool flecs_query_select_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_filter) +{ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); + if (!redo) { + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; } } - sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->trs != cur_helper->match->trs) { - cur = ecs_vec_append_t(NULL, &cache->table_slices, - ecs_query_cache_table_match_t); - *cur = *(cur_helper->match); - cur->offset = cur_helper->row; - cur->count = 1; + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } } else { - cur->count ++; + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } } - - cur_helper->row ++; - } while (proceed); - - /* Iterate through the vector of slices to set the prev/next ptrs. This - * can't be done while building the vector, as reallocs may occur */ - int32_t i, count = ecs_vec_count(&cache->table_slices); - ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); - for (i = 0; i < count; i ++) { - nodes[i].prev = &nodes[i - 1]; - nodes[i].next = &nodes[i + 1]; } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; - - flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); -} - -void flecs_query_cache_build_sorted_tables( - ecs_query_cache_t *cache) -{ - ecs_vec_clear(&cache->table_slices); - - if (cache->group_by_callback) { - /* Populate sorted node list in grouping order */ - ecs_query_cache_table_match_t *cur = cache->list.first; - if (cur) { - do { - /* Find list for current group */ - uint64_t group_id = cur->group_id; - ecs_query_cache_table_list_t *list = ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); - ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Sort tables in current group */ - flecs_query_cache_build_sorted_table_range(cache, list); +repeat: {} + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - flecs_query_cache_build_sorted_table_range(cache, &cache->list); + ecs_table_t *table = tr->hdr.table; + if (flecs_query_table_filter(table, op->other, table_filter)) { + goto repeat; } + + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + flecs_query_it_set_tr(it, field, tr); + return true; } -void flecs_query_cache_sort_tables( - ecs_world_t *world, - ecs_query_impl_t *impl) +bool flecs_query_with_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_t *cache = impl->cache; - ecs_order_by_action_t compare = cache->order_by_callback; - if (!compare) { - return; + if (redo) { + return false; } - ecs_sort_table_action_t sort = cache->order_by_table_callback; - - ecs_entity_t order_by = cache->order_by; - int32_t order_by_term = cache->order_by_term; - - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ - - bool tables_sorted = false; - - 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); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + if (!table) { + return false; + } - if (flecs_query_check_table_monitor(impl, qt, 0)) { - tables_sorted = true; - dirty = true; + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; } + } - int32_t column = -1; - if (order_by) { - if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { - dirty = true; - } + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - if (dirty) { - column = -1; + flecs_query_it_set_tr(it, field, tr); + return true; +} - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); - if (tr) { - column = tr->column; - } +static +bool flecs_query_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_up_with(op, redo, ctx); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); + } +} - if (column == -1) { - /* Component is shared, no sorting is needed */ - dirty = false; - } - } - } +static +bool flecs_query_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_self_up_with(op, redo, ctx, false); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); + } +} - if (!dirty) { - continue; +static +bool flecs_query_and_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t match_flags = op->match_flags; + if (redo) { + if (match_flags & EcsTermMatchAnySrc) { + return false; } + } - /* Something has changed, sort the table. Prefers using - * flecs_query_cache_sort_table when available */ - flecs_query_cache_sort_table(world, table, column, compare, sort); - tables_sorted = true; + uint64_t written = ctx->written[ctx->op_index]; + int32_t remaining = 1; + bool result; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + result = flecs_query_with(op, redo, ctx); + } else { + result = flecs_query_select(op, redo, ctx); + remaining = 0; } - if (tables_sorted || cache->match_count != cache->prev_match_count) { - flecs_query_cache_build_sorted_tables(cache); - cache->match_count ++; /* Increase version if tables changed */ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + + if (match_flags & EcsTermMatchAny && op_ctx->remaining) { + op_ctx->remaining = flecs_ito(int16_t, remaining); } -} -/** - * @file query/engine/change_detection.c - * @brief Compile query term. - */ + int32_t field = op->field_index; + if (field != -1) { + ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); + } + ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; -typedef struct { - ecs_table_t *table; - int32_t column; -} flecs_table_column_t; + return result; +} static -void flecs_query_get_column_for_field( - const ecs_query_t *q, - ecs_query_cache_table_match_t *match, - int32_t field, - flecs_table_column_t *out) +bool flecs_query_only_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); - (void)q; - - const ecs_table_record_t *tr = match->trs[field]; - ecs_table_t *table = tr->hdr.table; - int32_t column = tr->column; + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_and_any(op, redo, ctx); + } else { + return flecs_query_select_w_id(op, redo, ctx, EcsAny, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } +} - out->table = table; - out->column = column; +static +bool flecs_query_triv( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + ecs_flags64_t termset = op->src.entity; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + flecs_query_set_iter_this(ctx->it, ctx); + return flecs_query_trivial_test(ctx, redo, termset); + } else { + return flecs_query_trivial_search(ctx, op_ctx, redo, termset); + } } -/* Get match monitor. Monitors are used to keep track of whether components - * matched by the query in a table have changed. */ static -bool flecs_query_get_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) +bool flecs_query_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (match->monitor) { - return false; + (void)op; + (void)redo; + + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_cache_test(ctx, redo); + } else { + return flecs_query_cache_search(ctx); } +} - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = flecs_balloc(&cache->allocators.monitors); - monitor[0] = 0; +static +bool flecs_query_is_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + (void)op; - /* Mark terms that don't need to be monitored. This saves time when reading - * and/or updating the monitor. */ - const ecs_query_t *q = cache->query; - int32_t i, field = -1, term_count = q->term_count; - flecs_table_column_t tc; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_is_cache_test(ctx, redo); + } else { + return flecs_query_is_cache_search(ctx); + } +} - for (i = 0; i < term_count; i ++) { - if (field == q->terms[i].field_index) { - if (monitor[field + 1] != -1) { - continue; - } +static +int32_t flecs_query_next_inheritable_id( + ecs_world_t *world, + ecs_type_t *type, + int32_t index) +{ + int32_t i; + for (i = index; i < type->count; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); + if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + return i; } + } + return -1; +} - field = q->terms[i].field_index; - monitor[field + 1] = -1; +static +bool flecs_query_x_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_oper_kind_t oper) +{ + ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); + ecs_world_t *world = ctx->world; + ecs_type_t *type; + int32_t i; - /* If term isn't read, don't monitor */ - if (q->terms[i].inout != EcsIn && - q->terms[i].inout != EcsInOut && - q->terms[i].inout != EcsInOutDefault) { - continue; + if (!redo) { + /* Find entity that acts as the template from which we match the ids */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, id); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* Nothing to match */ + return false; } - /* Don't track fields that aren't set */ - if (!(match->set_fields & (1llu << field))) { - continue; - } + /* Find first id to test against. Skip ids with DontInherit flag. */ + type = op_ctx->type = &table->type; + op_ctx->first_id_index = flecs_query_next_inheritable_id( + world, type, 0); + op_ctx->cur_id_index = op_ctx->first_id_index; - flecs_query_get_column_for_field(q, match, field, &tc); - if (tc.column == -1) { - continue; /* Don't track terms that aren't stored */ + if (op_ctx->cur_id_index == -1) { + return false; /* No ids to filter on */ } - - monitor[field + 1] = 0; + } else { + type = op_ctx->type; } - match->monitor = monitor; + ecs_id_t *ids = type->array; - impl->pub.flags |= EcsQueryHasMonitor; + /* Check if source is variable, and if it's already written */ + bool src_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + src_written = written & (1ull << op->src.var); + } - return true; -} + do { + int32_t id_index = op_ctx->cur_id_index; -/* Get monitor for fixed query terms. Fixed terms are handled separately as they - * don't require a query cache, and fixed terms aren't stored in the cache. */ -static -bool flecs_query_get_fixed_monitor( - ecs_query_impl_t *impl, - bool check) -{ - ecs_query_t *q = &impl->pub; - ecs_world_t *world = q->world; - ecs_term_t *terms = q->terms; - int32_t i, term_count = q->term_count; + /* If source is not yet written, find tables with first id */ + if (!src_written) { + ecs_entity_t first_id = ids[id_index]; - if (!impl->monitor) { - impl->monitor = flecs_alloc_n(&impl->stage->allocator, - int32_t, q->field_count); - check = false; /* If the monitor is new, initialize it with dirty state */ - } + if (!flecs_query_select_w_id(op, redo, ctx, + first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + if (oper == EcsOrFrom) { + id_index = flecs_query_next_inheritable_id( + world, type, id_index + 1); + if (id_index != -1) { + op_ctx->cur_id_index = id_index; + redo = false; + continue; + } + } - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int16_t field_index = term->field_index; + return false; + } - if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { - continue; /* If term doesn't read data there's nothing to track */ + id_index ++; /* First id got matched */ + } else if (redo && src_written) { + return false; } - if (!(term->src.id & EcsIsEntity)) { - continue; /* Not a term with a fixed source */ + ecs_table_t *src_table = flecs_query_get_table( + op, &op->src, EcsQuerySrc, ctx); + if (!src_table) { + continue; } + + redo = true; - ecs_entity_t src = ECS_TERM_REF_ID(&term->src); - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + if (!src_written && oper == EcsOrFrom) { + /* Eliminate duplicate matches from tables that have multiple + * components from the type list */ + if (op_ctx->cur_id_index != op_ctx->first_id_index) { + for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); + if (!idr) { + continue; + } - ecs_record_t *r = flecs_entities_get(world, src); - if (!r || !r->table) { - continue; /* Entity is empty, nothing to track */ + if (idr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + + if (flecs_id_record_get_table(idr, src_table) != NULL) { + /* Already matched */ + break; + } + } + if (i != op_ctx->cur_id_index) { + continue; + } + } + return true; } - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (!idr) { - continue; /* If id doesn't exist, entity can't have it */ - } + if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { + for (i = id_index; i < type->count; i ++) { + ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); + if (!idr) { + if (oper == EcsAndFrom) { + return false; + } else { + continue; + } + } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); - if (!tr) { - continue; /* Entity doesn't have the component */ - } + if (idr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } - /* Copy/check column dirty state from table */ - int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_id_record_get_table(idr, src_table) == NULL) { + if (oper == EcsAndFrom) { + break; /* Must have all ids */ + } + } else { + if (oper == EcsNotFrom) { + break; /* Must have none of the ids */ + } else if (oper == EcsOrFrom) { + return true; /* Single match is enough */ + } + } + } - if (!check) { - impl->monitor[field_index] = dirty_state[tr->column + 1]; - } else { - if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { - return true; + if (i == type->count) { + if (oper == EcsAndFrom || oper == EcsNotFrom) { + break; /* All ids matched */ + } } } - } + } while (true); - return !check; + return true; } -bool flecs_query_update_fixed_monitor( - ecs_query_impl_t *impl) +static +bool flecs_query_and_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - return flecs_query_get_fixed_monitor(impl, false); + return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } -bool flecs_query_check_fixed_monitor( - ecs_query_impl_t *impl) +static +bool flecs_query_not_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - return flecs_query_get_fixed_monitor(impl, true); + return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } - -/* Check if single match term has changed */ static -bool flecs_query_check_match_monitor_term( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - int32_t field) +bool flecs_query_or_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } + return flecs_query_x_from(op, redo, ctx, EcsOrFrom); +} - int32_t *monitor = match->monitor; - int32_t state = monitor[field]; - if (state == -1) { +static +bool flecs_query_ids( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (redo) { return false; } - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = match->table; - if (table) { - int32_t *dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (!field) { - return monitor[0] != dirty_state[0]; + ecs_id_record_t *cur; + ecs_id_t id = flecs_query_op_get_id(op, ctx); + + { + cur = flecs_id_record_get(ctx->world, id); + if (!cur || !cur->cache.tables.count) { + return false; } - } else if (!field) { - return false; } - flecs_table_column_t cur; - flecs_query_get_column_for_field( - &impl->pub, match, field - 1, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - - return monitor[field] != flecs_table_get_dirty_state( - cache->query->world, cur.table)[cur.column + 1]; + flecs_query_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->trs[op->field_index] = NULL; /* Mark field as set */ + } + + return true; } static -bool flecs_query_check_cache_monitor( - ecs_query_impl_t *impl) +bool flecs_query_idsright( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; - /* If the match count changed, tables got matched/unmatched for the - * cache, so return that the query has changed. */ - if (cache->match_count != cache->prev_match_count) { - return true; - } + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); + return true; + } - ecs_table_cache_iter_t it; - if (flecs_table_cache_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)) { - return true; - } + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; } } - return false; -} + do { + cur = op_ctx->cur = op_ctx->cur->first.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ -static -void flecs_query_init_query_monitors( - ecs_query_impl_t *impl) -{ - /* Change monitor for cache */ - ecs_query_cache_t *cache = impl->cache; - if (cache) { - ecs_query_cache_table_match_t *cur = cache->list.first; + if (!cur) { + return false; + } - /* Ensure each match has a monitor */ - for (; cur != NULL; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - flecs_query_get_match_monitor(impl, match); - } + flecs_query_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } + + return true; } static -bool flecs_query_check_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - const ecs_iter_t *it) +bool flecs_query_idsleft( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = NULL; - if (table) { - dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (monitor[0] != dirty_state[0]) { + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); return true; } - } - - const ecs_query_t *query = cache->query; - ecs_world_t *world = query->world; - int32_t i, field_count = query->field_count; - ecs_entity_t *sources = match->sources; - const ecs_table_record_t **trs = it ? it->trs : match->trs; - ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; - - ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < field_count; i ++) { - int32_t mon = monitor[i + 1]; - if (mon == -1) { - continue; + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; } - - if (!(set_fields & (1llu << i))) { - continue; + } else { + if (!op_ctx->cur) { + return false; } + } - int32_t column = trs[i]->column; - ecs_entity_t src = sources[i]; - if (!src) { - if (column >= 0) { - /* owned component */ - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (mon != dirty_state[column + 1]) { - return true; - } - continue; - } else if (column == -1) { - continue; /* owned but not a component */ - } - } + do { + cur = op_ctx->cur = op_ctx->cur->second.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - /* Component from non-this source */ - ecs_entity_t fixed_src = match->sources[i]; - ecs_table_t *src_table = ecs_get_table(world, fixed_src); - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *src_dirty_state = flecs_table_get_dirty_state( - world, src_table); - if (mon != src_dirty_state[column + 1]) { - return true; - } + if (!cur) { + return false; } - return false; -} - -/* Check if any term for matched table has changed */ -bool flecs_query_check_table_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_t *table, - int32_t field) -{ - ecs_query_cache_table_match_t *cur, *end = table->last->next; + flecs_query_set_vars(op, cur->id, ctx); - for (cur = table->first; cur != end; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - if (field == -1) { - if (flecs_query_check_match_monitor(impl, match, NULL)) { - return true; - } - } else { - if (flecs_query_check_match_monitor_term(impl, match, field)) { - return true; - } - } + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } - return false; + return true; } -void flecs_query_mark_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) +static +bool flecs_query_each( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_query_t *q = &impl->pub; + ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); + int32_t row; - /* Evaluate all writeable non-fixed fields, set fields */ - ecs_termset_t write_fields = - (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); - if (!write_fields || (it->flags & EcsIterNoData)) { - return; + ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); + ecs_table_t *table = range.table; + if (!table) { + return false; } - ecs_world_t *world = q->world; - int16_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - ecs_termset_t field_bit = (ecs_termset_t)(1u << i); - if (!(write_fields & field_bit)) { - continue; /* If term doesn't write data there's nothing to track */ + if (!redo) { + if (!ecs_table_count(table)) { + return false; } - - ecs_entity_t src = it->sources[i]; - ecs_table_t *table; - if (!src) { - table = it->table; + row = op_ctx->row = range.offset; + } else { + int32_t end = range.count; + if (end) { + end += range.offset; } else { - ecs_record_t *r = flecs_entities_get(world, src); - if (!r || !(table = r->table)) { - continue; - } - - if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { - /* Shared fields that aren't marked explicitly as out/inout - * default to readonly */ - continue; - } - } - - int32_t type_index = it->trs[i]->index; - ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); - - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *dirty_state = table->dirty_state; - if (!dirty_state) { - continue; + end = ecs_table_count(table); + } + row = ++ op_ctx->row; + if (op_ctx->row >= end) { + return false; } - - ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); - int32_t column = table->column_map[type_index]; - dirty_state[column + 1] ++; } + + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); + + return true; } -void flecs_query_mark_fixed_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) +static +bool flecs_query_store( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - /* This function marks fields dirty for terms with fixed sources. */ - ecs_query_t *q = &impl->pub; - ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; - if (!fixed_write_fields) { - return; + if (!redo) { + flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); + return true; + } else { + return false; } +} - ecs_world_t *world = q->world; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { - continue; /* If term doesn't write data there's nothing to track */ - } +static +bool flecs_query_reset( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (!redo) { + return true; + } else { + flecs_query_var_reset(op->src.var, ctx); + return false; + } +} - ecs_entity_t src = it->sources[i]; - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = flecs_entities_get(world, src); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* If the field is optional, it's possible that it didn't match */ - continue; - } +static +bool flecs_query_lookup( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } - int32_t *dirty_state = table->dirty_state; - if (!dirty_state) { - continue; - } + const ecs_query_impl_t *query = ctx->query; + ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); + ecs_query_var_t *var = &query->vars[op->src.var]; - ecs_assert(it->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t column = table->column_map[it->trs[i]->column]; - dirty_state[column + 1] ++; + ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, + NULL, NULL, false); + if (!result) { + flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); + return false; } + + flecs_query_var_set_entity(op, op->src.var, result, ctx); + + return true; } -/* Synchronize match monitor with table dirty state */ -void flecs_query_sync_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) +static +bool flecs_query_setvars( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + (void)op; - if (!match->monitor) { - if (impl->pub.flags & EcsQueryHasMonitor) { - flecs_query_get_match_monitor(impl, match); - } else { - return; - } - } + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_var_id_t *src_vars = query->src_vars; + ecs_iter_t *it = ctx->it; - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - if (table) { - int32_t *dirty_state = flecs_table_get_dirty_state( - cache->query->world, table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + if (redo) { + return false; } - ecs_query_t *q = cache->query; - { - flecs_table_column_t tc; - int32_t t, term_count = q->term_count; - for (t = 0; t < term_count; t ++) { - int32_t field = q->terms[t].field_index; - if (monitor[field + 1] == -1) { - continue; - } - - flecs_query_get_column_for_field(q, match, field, &tc); + int32_t i; + ecs_flags32_t up_fields = it->up_fields; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = src_vars[i]; + if (!var_id) { + continue; + } - monitor[field + 1] = flecs_table_get_dirty_state( - q->world, tc.table)[tc.column + 1]; + if (up_fields & (1u << i)) { + continue; } + + it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } - cache->prev_match_count = cache->match_count; + return true; } -bool ecs_query_changed( - ecs_query_t *q) +static +bool flecs_query_setthis( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); + ecs_var_t *vars = ctx->vars; + ecs_var_t *this_var = &vars[op->first.var]; - ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, - "change detection is only supported on cached queries"); + if (!redo) { + /* Save values so we can restore them later */ + op_ctx->range = vars[0].range; - /* If query reads terms with fixed sources, check those first as that's - * cheaper than checking entries in the cache. */ - if (impl->monitor) { - if (flecs_query_check_fixed_monitor(impl)) { - return true; - } + /* Constrain This table variable to a single entity from the table */ + vars[0].range = flecs_range_from_entity(this_var->entity, ctx); + vars[0].entity = this_var->entity; + return true; + } else { + /* Restore previous values, so that instructions that are operating on + * the table variable use all the entities in the table. */ + vars[0].range = op_ctx->range; + vars[0].entity = 0; + return false; } +} - /* Check cache for changes. We can't detect changes for terms that are not - * 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); +static +bool flecs_query_setfixed( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - if (!(impl->pub.flags & EcsQueryHasMonitor)) { - flecs_query_init_query_monitors(impl); - } + if (redo) { + return false; + } - /* Check cache entries for changes */ - return flecs_query_check_cache_monitor(impl); + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + const ecs_term_ref_t *src = &term->src; + if (src->id & EcsIsEntity) { + it->sources[term->field_index] = ECS_TERM_REF_ID(src); + } } - return false; + return true; } -bool ecs_iter_changed( - ecs_iter_t *it) +bool flecs_query_setids( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); - ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); - - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = flecs_query_impl(qit->query); - ecs_query_t *q = &impl->pub; - - /* First check for changes for terms with fixed sources, if query has any */ - if (q->read_fields & q->fixed_fields) { - /* Detecting changes for uncached terms is costly, so only do it once - * per iteration. */ - if (!(it->flags & EcsIterFixedInChangeComputed)) { - it->flags |= EcsIterFixedInChangeComputed; - ECS_BIT_COND(it->flags, EcsIterFixedInChanged, - flecs_query_check_fixed_monitor(impl)); - } + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - if (it->flags & EcsIterFixedInChanged) { - return true; - } + if (redo) { + return false; } - /* If query has a cache, check for changes in current matched result */ - if (impl->cache) { - ecs_query_cache_table_match_t *qm = - (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; - ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_query_check_match_monitor(impl, qm, it); + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + it->ids[term->field_index] = term->id; } -error: - return false; + return true; } -void ecs_iter_skip( - ecs_iter_t *it) +static +bool flecs_query_setid( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); - it->flags |= EcsIterSkip; + if (redo) { + return false; + } + + ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); + ctx->it->ids[op->field_index] = op->first.entity; + return true; } -/** - * @file query/engine/eval.c - * @brief Query engine implementation. - */ +/* Check if entity is stored in table */ +static +bool flecs_query_contain( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + ecs_var_id_t src_id = op->src.var; + ecs_var_id_t first_id = op->first.var; -// #define FLECS_QUERY_TRACE + ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); -#ifdef FLECS_QUERY_TRACE -static int flecs_query_trace_indent = 0; -#endif + ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); + return table == ecs_get_table(ctx->world, e); +} +/* Check if first and second id of pair from last operation are the same */ static -bool flecs_query_dispatch( +bool flecs_query_pair_eq( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx); + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } -bool flecs_query_select_w_id( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_id_t id, - ecs_flags32_t filter_mask) + ecs_iter_t *it = ctx->it; + ecs_id_t id = it->ids[op->field_index]; + return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); +} + +static +void flecs_query_reset_after_block( + const ecs_query_op_t *start_op, + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx, + bool result) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; - ecs_table_t *table; + ecs_query_lbl_t op_index = start_op->next; + const ecs_query_op_t *op = &ctx->qit->ops[op_index]; - if (!redo) { - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + int32_t field = op->field_index; + if (field == -1) { + goto done; + } - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } + /* Set/unset field */ + ecs_iter_t *it = ctx->it; + if (result) { + ECS_TERMSET_SET(it->set_fields, 1u << field); + return; } -repeat: - if (!redo || !op_ctx->remaining) { - tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } + /* Reset state after a field was not matched */ + ctx->written[op_index] = ctx->written[ctx->op_index]; + ctx->op_index = op_index; + ECS_TERMSET_CLEAR(it->set_fields, 1u << field); - op_ctx->column = flecs_ito(int16_t, tr->index); - op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); - table = tr->hdr.table; - flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); - } else { - tr = (ecs_table_record_t*)op_ctx->it.cur; - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - table = tr->hdr.table; - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); - op_ctx->remaining --; - } + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - if (flecs_query_table_filter(table, op->other, filter_mask)) { - goto repeat; + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; } - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; -} + it->trs[field] = NULL; -bool flecs_query_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_id_t id = 0; - if (!redo) { - id = flecs_query_op_get_id(op, ctx); + /* Reset variables */ + if (flags_1st & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ + flecs_query_var_reset(op->first.var, ctx); + } } - return flecs_query_select_w_id(op, redo, ctx, id, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + if (flags_2nd & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ + flecs_query_var_reset(op->second.var, ctx); + } + } + + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { + it->sources[field] = op->src.entity; + } + +done: + op_ctx->op_index = op_index; } -bool flecs_query_with( - const ecs_query_op_t *op, +static +bool flecs_query_run_block( bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { + if (!redo) { + op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); + } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { return false; } - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; - tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; + bool result = flecs_query_run_until( + redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); - op_ctx->column = flecs_ito(int16_t, tr->index); - op_ctx->remaining = flecs_ito(int16_t, tr->count); - op_ctx->it.cur = &tr->hdr; - } else { - ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); - if (--op_ctx->remaining <= 0) { - return false; - } + op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); + return result; +} - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); - ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); - } +static +ecs_query_lbl_t flecs_query_last_op_for_or_cond( + const ecs_query_op_t *ops, + ecs_query_lbl_t cur, + ecs_query_lbl_t last) +{ + const ecs_query_op_t *cur_op, *last_op = &ops[last]; - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; + do { + cur_op = &ops[cur]; + cur ++; + } while (cur_op->next != last && cur_op != last_op); + + return cur; } static -bool flecs_query_and( - const ecs_query_op_t *op, +bool flecs_query_run_until_for_select_or( bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) { - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_with(op, redo, ctx); - } else { - return flecs_query_select(op, redo, ctx); + ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( + ops, cur, flecs_itolbl(last)); + if (redo) { + /* If redoing, start from the last instruction of the last executed + * sequence */ + cur = flecs_itolbl(last_for_cur - 1); } + + flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); +#ifdef FLECS_QUERY_TRACE + printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", + ctx->op_index == last ? "true" : "false"); +#endif + return ctx->op_index == last; } -bool flecs_query_select_id( +static +bool flecs_query_select_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_flags32_t table_filter) + ecs_query_run_ctx_t *ctx) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; - int8_t field = op->field_index; - ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); if (!redo) { - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } + op_ctx->op_index = first; + } - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; + const ecs_query_op_t *ops = qit->ops; + const ecs_query_op_t *first_op = &ops[first - 1]; + ecs_query_lbl_t last = first_op->next; + const ecs_query_op_t *last_op = &ops[last]; + const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; + bool result = false; + + do { + ecs_query_lbl_t cur = op_ctx->op_index; + ctx->op_index = cur; + ctx->written[cur] = op->written; + + result = flecs_query_run_until_for_select_or( + redo, ctx, ops, flecs_itolbl(first - 1), cur, last); + + if (result) { + if (first == cur) { + break; } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; + + /* If a previous operation in the OR chain returned a result for the + * same matched source, skip it so we don't yield for each matching + * element in the chain. */ + + /* Copy written status so that the variables we just wrote will show + * up as written for the test. This ensures that the instructions + * match against the result we already found, vs. starting a new + * search (the difference between select & with). */ + ecs_query_lbl_t prev = first; + bool dup_found = false; + + /* While terms of an OR chain always operate on the same source, it + * is possible that a table variable is resolved to an entity + * variable. When checking for duplicates, copy the entity variable + * to the table, to ensure we're only testing the found entity. */ + const ecs_query_op_t *prev_op = &ops[cur - 1]; + ecs_var_t old_table_var; + ecs_os_memset_t(&old_table_var, 0, ecs_var_t); + bool restore_table_var = false; + + if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (first_op->src.var != prev_op->src.var) { + restore_table_var = true; + old_table_var = ctx->vars[first_op->src.var]; + ctx->vars[first_op->src.var] = + ctx->vars[prev_op->src.var]; + } } - } - } -repeat: {} - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } + int16_t field_index = op->field_index; + ecs_id_t prev_id = it->ids[field_index]; + const ecs_table_record_t *prev_tr = it->trs[field_index]; - ecs_table_t *table = tr->hdr.table; - if (flecs_query_table_filter(table, op->other, table_filter)) { - goto repeat; - } + do { + ctx->written[prev] = ctx->written[last]; - flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); - flecs_query_it_set_tr(it, field, tr); - return true; -} + flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), + prev, cur); -bool flecs_query_with_id( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } + if (ctx->op_index == last) { + /* Duplicate match was found, find next result */ + redo = true; + dup_found = true; + break; + } - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_iter_t *it = ctx->it; - int8_t field = op->field_index; - ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + break; + } while (true); - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { - return false; - } + /* Restore table variable to full range for next result */ + if (restore_table_var) { + ctx->vars[first_op->src.var] = old_table_var; + } - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; + if (dup_found) { + continue; + } + + /* Restore id in case op set it */ + it->ids[field_index] = prev_id; + it->trs[field_index] = prev_tr; + break; } - } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + /* No result was found, go to next operation in chain */ + op_ctx->op_index = flecs_query_last_op_for_or_cond( + ops, op_ctx->op_index, last); + cur_op = &qit->ops[op_ctx->op_index]; - flecs_query_it_set_tr(it, field, tr); - return true; -} + redo = false; + } while (cur_op != last_op); -static -bool flecs_query_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_up_with(op, redo, ctx); - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); - } + return result; } static -bool flecs_query_self_up( +bool flecs_query_with_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_self_up_with(op, redo, ctx, false); - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + bool result = flecs_query_run_block(redo, ctx, op_ctx); + if (result) { + /* If a match was found, no need to keep searching for this source */ + op_ctx->op_index = op->next; } + + return result; } static -bool flecs_query_and_any( +bool flecs_query_or( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - ecs_flags16_t match_flags = op->match_flags; - if (redo) { - if (match_flags & EcsTermMatchAnySrc) { - return false; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + if (!(written & (1ull << op->src.var))) { + return flecs_query_select_or(op, redo, ctx); } } - uint64_t written = ctx->written[ctx->op_index]; - int32_t remaining = 1; - bool result; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - result = flecs_query_with(op, redo, ctx); - } else { - result = flecs_query_select(op, redo, ctx); - remaining = 0; - } - - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - - if (match_flags & EcsTermMatchAny && op_ctx->remaining) { - op_ctx->remaining = flecs_ito(int16_t, remaining); - } - - int32_t field = op->field_index; - if (field != -1) { - ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); - } - - ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; - - return result; + return flecs_query_with_or(op, redo, ctx); } static -bool flecs_query_only_any( +bool flecs_query_run_block_w_reset( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_and_any(op, redo, ctx); - } else { - return flecs_query_select_w_id(op, redo, ctx, EcsAny, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + bool result = flecs_query_run_block(redo, ctx, op_ctx); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; } static -bool flecs_query_triv( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); - ecs_flags64_t termset = op->src.entity; - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - flecs_query_set_iter_this(ctx->it, ctx); - return flecs_query_trivial_test(ctx, redo, termset); - } else { - return flecs_query_trivial_search(ctx, op_ctx, redo, termset); +bool flecs_query_not( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; } + + return !flecs_query_run_block_w_reset(op, redo, ctx); } static -bool flecs_query_cache( +bool flecs_query_optional( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - (void)redo; - - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - return flecs_query_cache_test(ctx, redo); + ecs_query_run_ctx_t *ctx) +{ + bool result = flecs_query_run_block_w_reset(op, redo, ctx); + if (!redo) { + return true; /* Return at least once */ } else { - return flecs_query_cache_search(ctx); + return result; } } static -bool flecs_query_is_cache( +bool flecs_query_eval_if( const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind) { - (void)op; - - uint64_t written = ctx->written[ctx->op_index]; - ctx->written[ctx->op_index + 1] |= 1ull; - if (written & 1ull) { - return flecs_query_is_cache_test(ctx, redo); - } else { - return flecs_query_is_cache_search(ctx); + bool result = true; + if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { + result = ctx->vars[ref->var].entity != EcsWildcard; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; } + return true; } static -int32_t flecs_query_next_inheritable_id( - ecs_world_t *world, - ecs_type_t *type, - int32_t index) +bool flecs_query_if_var( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - int32_t i; - for (i = index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - return i; + if (!redo) { + if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || + !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || + !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) + { + return true; } } - return -1; + + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + return flecs_query_run_block(redo, ctx, op_ctx); } static -bool flecs_query_x_from( +bool flecs_query_if_set( const ecs_query_op_t *op, bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_oper_kind_t oper) + ecs_query_run_ctx_t *ctx) { - ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); - ecs_world_t *world = ctx->world; - ecs_type_t *type; - int32_t i; + ecs_iter_t *it = ctx->it; + int8_t field_index = flecs_ito(int8_t, op->other); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); if (!redo) { - /* Find entity that acts as the template from which we match the ids */ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = flecs_entities_get(world, id); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* Nothing to match */ - return false; - } - - /* Find first id to test against. Skip ids with DontInherit flag. */ - type = op_ctx->type = &table->type; - op_ctx->first_id_index = flecs_query_next_inheritable_id( - world, type, 0); - op_ctx->cur_id_index = op_ctx->first_id_index; - - if (op_ctx->cur_id_index == -1) { - return false; /* No ids to filter on */ - } - } else { - type = op_ctx->type; + op_ctx->is_set = ecs_field_is_set(it, field_index); } - ecs_id_t *ids = type->array; - - /* Check if source is variable, and if it's already written */ - bool src_written = true; - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - uint64_t written = ctx->written[ctx->op_index]; - src_written = written & (1ull << op->src.var); + if (!op_ctx->is_set) { + return !redo; } - do { - int32_t id_index = op_ctx->cur_id_index; - - /* If source is not yet written, find tables with first id */ - if (!src_written) { - ecs_entity_t first_id = ids[id_index]; - - if (!flecs_query_select_w_id(op, redo, ctx, - first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - if (oper == EcsOrFrom) { - id_index = flecs_query_next_inheritable_id( - world, type, id_index + 1); - if (id_index != -1) { - op_ctx->cur_id_index = id_index; - redo = false; - continue; - } - } + return flecs_query_run_block(redo, ctx, op_ctx); +} - return false; - } +static +bool flecs_query_end( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; (void)ctx; + return !redo; +} - id_index ++; /* First id got matched */ - } else if (redo && src_written) { - return false; - } +static +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + switch(op->kind) { + case EcsQueryAnd: return flecs_query_and(op, redo, ctx); + case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); + case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); + case EcsQueryCache: return flecs_query_cache(op, redo, ctx); + case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); + case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); + case EcsQueryUp: return flecs_query_up(op, redo, ctx); + case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); + case EcsQueryWith: return flecs_query_with(op, redo, ctx); + case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); + case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); + case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); + case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); + case EcsQueryIds: return flecs_query_ids(op, redo, ctx); + case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); + case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); + case EcsQueryEach: return flecs_query_each(op, redo, ctx); + case EcsQueryStore: return flecs_query_store(op, redo, ctx); + case EcsQueryReset: return flecs_query_reset(op, redo, ctx); + case EcsQueryOr: return flecs_query_or(op, redo, ctx); + case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); + case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); + case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); + case EcsQueryEnd: return flecs_query_end(op, redo, ctx); + case EcsQueryNot: return flecs_query_not(op, redo, ctx); + case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); + case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); + case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); + case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); + case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); + case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); + case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); + case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); + case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); + case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); + case EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); + case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); + case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); + case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); + case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(op, redo, ctx); + case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); + case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); + case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); + case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); + case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); + case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); + case EcsQueryContain: return flecs_query_contain(op, redo, ctx); + case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); + case EcsQueryYield: return false; + case EcsQueryNothing: return false; + } + return false; +} - ecs_table_t *src_table = flecs_query_get_table( - op, &op->src, EcsQuerySrc, ctx); - if (!src_table) { - continue; - } - - redo = true; +bool flecs_query_run_until( + bool redo, + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) +{ + ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); - if (!src_written && oper == EcsOrFrom) { - /* Eliminate duplicate matches from tables that have multiple - * components from the type list */ - if (op_ctx->cur_id_index != op_ctx->first_id_index) { - for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - continue; - } + ctx->op_index = cur; + const ecs_query_op_t *op = &ops[ctx->op_index]; + const ecs_query_op_t *last_op = &ops[last]; + ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - - if (flecs_id_record_get_table(idr, src_table) != NULL) { - /* Already matched */ - break; - } - } - if (i != op_ctx->cur_id_index) { - continue; - } - } - return true; - } +#ifdef FLECS_QUERY_TRACE + printf("%*sblock:\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent ++; +#endif - if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { - for (i = id_index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - if (oper == EcsAndFrom) { - return false; - } else { - continue; - } - } + do { + #ifdef FLECS_DEBUG + ctx->qit->profile[ctx->op_index].count[redo] ++; + #endif - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } +#ifdef FLECS_QUERY_TRACE + printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", + ctx->op_index, flecs_query_op_str(op->kind)); +#endif - if (flecs_id_record_get_table(idr, src_table) == NULL) { - if (oper == EcsAndFrom) { - break; /* Must have all ids */ - } - } else { - if (oper == EcsNotFrom) { - break; /* Must have none of the ids */ - } else if (oper == EcsOrFrom) { - return true; /* Single match is enough */ - } - } - } + bool result = flecs_query_dispatch(op, redo, ctx); + cur = (&op->prev)[result]; + redo = cur < ctx->op_index; - if (i == type->count) { - if (oper == EcsAndFrom || oper == EcsNotFrom) { - break; /* All ids matched */ - } - } + if (!redo) { + ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; } - } while (true); + + ctx->op_index = cur; + op = &ops[ctx->op_index]; + + if (cur <= first) { +#ifdef FLECS_QUERY_TRACE + printf("%*sfalse\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif + return false; + } + } while (op < last_op); + +#ifdef FLECS_QUERY_TRACE + printf("%*strue\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif return true; } -static -bool flecs_query_and_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsAndFrom); -} +/** + * @file query/engine/eval_iter.c + * @brief Query iterator. + */ -static -bool flecs_query_not_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsNotFrom); -} static -bool flecs_query_or_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_run_ctx_init( + ecs_iter_t *it, + ecs_query_run_ctx_t *ctx) { - return flecs_query_x_from(op, redo, ctx, EcsOrFrom); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); + ctx->world = it->real_world; + ctx->query = impl; + ctx->it = it; + ctx->vars = qit->vars; + ctx->query_vars = qit->query_vars; + ctx->written = qit->written; + ctx->op_ctx = qit->op_ctx; + ctx->qit = qit; } -static -bool flecs_query_ids( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_constrain( + ecs_iter_t *it) { - if (redo) { - return false; - } + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur; - ecs_id_t id = flecs_query_op_get_id(op, ctx); + const ecs_query_impl_t *query = ctx.query; + const ecs_query_t *q = &query->pub; + ecs_flags64_t it_written = it->constrained_vars; + ctx.written[0] = it_written; + if (it_written && ctx.query->src_vars) { + /* If variables were constrained, check if there are any table + * variables that have a constrained entity variable. */ + ecs_var_t *vars = ctx.vars; + int32_t i, count = q->field_count; + for (i = 0; i < count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + ecs_query_var_t *var = &query->vars[var_id]; - { - cur = flecs_id_record_get(ctx->world, id); - if (!cur || !cur->cache.tables.count) { - return false; + if (!(it_written & (1ull << var_id)) || + (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) + { + continue; + } + + /* Initialize table variable with constrained entity variable */ + ecs_var_t *tvar = &vars[var->table_id]; + tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); + ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } } - flecs_query_set_vars(op, cur->id, ctx); + /* This function can be called multiple times when setting variables, so + * reset flags before setting them. */ + it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| + EcsIterTrivialSearch); - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - it->trs[op->field_index] = NULL; /* Mark field as set */ - } + /* Figure out whether this query can utilize specialized iterator modes for + * improved performance. */ + ecs_flags32_t flags = q->flags; + ecs_query_cache_t *cache = query->cache; + if (flags & EcsQueryIsTrivial) { + if ((flags & EcsQueryMatchOnlySelf)) { + if (it_written) { + /* When we're testing against an entity or table, set the $this + * variable in advance since it won't change later on. This + * initializes it.count, it.entities and it.table. */ + flecs_query_set_iter_this(it, &ctx); - return true; + if (!cache) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialTest; + } + } else if (flags & EcsQueryIsCacheable) { + it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; + } + } else { + if (!cache) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialSearch; + } + } else if (flags & EcsQueryIsCacheable) { + if (!cache->order_by_callback) { + it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; + } + } + } + + /* If we're using a specialized iterator mode, make sure to + * initialize static component ids. Usually this is the first + * instruction of a query plan, but because we're not running the + * query plan when using a specialized iterator mode, manually call + * the operation on iterator init. */ + flecs_query_setids(NULL, false, &ctx); + } + } } -static -bool flecs_query_idsright( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +bool ecs_query_next( + ecs_iter_t *it) { - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!ecs_id_is_wildcard(id)) { - /* If id is not a wildcard, we can directly return it. This can - * happen if a variable was constrained by an iterator. */ - op_ctx->cur = NULL; - flecs_query_set_vars(op, id, ctx); - return true; + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + const ecs_query_op_t *ops = qit->ops; + + bool redo = it->flags & EcsIterIsValid; + if (redo) { + /* Change detection */ + if (!(it->flags & EcsIterSkip)) { + /* Mark table columns that are written to dirty */ + flecs_query_mark_fields_dirty(impl, it); + if (qit->prev) { + if (ctx.query->pub.flags & EcsQueryHasMonitor) { + /* If this query uses change detection, synchronize the + * monitor for the iterated table with the query */ + flecs_query_sync_match_monitor(impl, qit->prev); + } + } } + } - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; + it->flags &= ~(EcsIterSkip); + it->flags |= EcsIterIsValid; + it->frame_offset += it->count; + + /* Specialized iterator modes. When a query doesn't use any advanced + * features, it can call specialized iterator functions directly instead of + * going through the dispatcher of the query engine. + * The iterator mode is set during iterator initialization. Besides being + * determined by the query, there are different modes for searching and + * testing, where searching returns all matches for a query, whereas testing + * tests a single table or table range against the query. */ + + if (it->flags & EcsIterTrivialCached) { + /* Cached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + if (flecs_query_is_cache_search(&ctx)) { + goto trivial_search_yield; + } + } else if (it->flags & EcsIterTrivialTest) { + if (flecs_query_is_cache_test(&ctx, redo)) { + goto yield; + } } } else { - if (!op_ctx->cur) { - return false; + /* Uncached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { + goto yield; + } + } else if (it->flags & EcsIterTrivialTest) { + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (flecs_query_trivial_test(&ctx, redo, mask)) { + goto yield; + } + } else { + /* Default iterator mode. This enters the query VM dispatch loop. */ + if (flecs_query_run_until( + redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) + { + ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, + ECS_INTERNAL_ERROR, NULL); + flecs_query_set_iter_this(it, &ctx); + ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); + qit->op = flecs_itolbl(ctx.op_index - 1); + goto yield; + } } } - do { - cur = op_ctx->cur = op_ctx->cur->first.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - - if (!cur) { - return false; + /* Done iterating */ + flecs_query_mark_fixed_fields_dirty(impl, it); + if (ctx.query->monitor) { + flecs_query_update_fixed_monitor( + ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } - flecs_query_set_vars(op, cur->id, ctx); + ecs_iter_fini(it); + return false; - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); - } +trivial_search_yield: + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = ecs_table_entities(it->table); +yield: return true; } static -bool flecs_query_idsleft( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_iter_fini_ctx( + ecs_iter_t *it, + ecs_query_iter_t *qit) { - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; - - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!ecs_id_is_wildcard(id)) { - /* If id is not a wildcard, we can directly return it. This can - * happen if a variable was constrained by an iterator. */ - op_ctx->cur = NULL; - flecs_query_set_vars(op, id, ctx); - return true; - } + const ecs_query_impl_t *query = flecs_query_impl(qit->query); + int32_t i, count = query->op_count; + ecs_query_op_t *ops = query->ops; + ecs_query_op_ctx_t *ctx = qit->op_ctx; + ecs_allocator_t *a = flecs_query_get_allocator(it); - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; + for (i = 0; i < count; i ++) { + ecs_query_op_t *op = &ops[i]; + switch(op->kind) { + case EcsQueryTrav: + flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); + break; + case EcsQueryUp: + case EcsQuerySelfUp: + case EcsQueryUnionEqUp: + case EcsQueryUnionEqSelfUp: { + ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; + if (cache->dir == EcsTravDown) { + flecs_query_down_cache_fini(a, cache); + } else { + flecs_query_up_cache_fini(cache); + } + break; } - } else { - if (!op_ctx->cur) { - return false; + default: + break; } } +} - do { - cur = op_ctx->cur = op_ctx->cur->second.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ +static +void flecs_query_iter_fini( + ecs_iter_t *it) +{ + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(qit->query, ecs_query_t); + int32_t op_count = flecs_query_impl(qit->query)->op_count; + int32_t var_count = flecs_query_impl(qit->query)->var_count; - if (!cur) { - return false; +#ifdef FLECS_DEBUG + if (it->flags & EcsIterProfile) { + char *str = ecs_query_plan_w_profile(qit->query, it); + printf("%s\n", str); + ecs_os_free(str); } - flecs_query_set_vars(op, cur->id, ctx); + flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); +#endif - if (op->field_index != -1) { - ecs_iter_t *it = ctx->it; - ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); - it->ids[op->field_index] = id; - it->sources[op->field_index] = EcsWildcard; - ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); - } + flecs_query_iter_fini_ctx(it, qit); + flecs_iter_free_n(qit->vars, ecs_var_t, var_count); + flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); + flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); - return true; + qit->vars = NULL; + qit->written = NULL; + qit->op_ctx = NULL; + qit->query = NULL; } -static -bool flecs_query_each( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_iter_t flecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q) { - ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); - int32_t row; + ecs_iter_t it = {0}; + ecs_query_iter_t *qit = &it.priv_.iter.query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); - ecs_table_t *table = range.table; - if (!table) { - return false; - } + int32_t i, var_count = impl->var_count; + int32_t op_count = impl->op_count ? impl->op_count : 1; + it.world = ECS_CONST_CAST(ecs_world_t*, world); - if (!redo) { - if (!ecs_table_count(table)) { - return false; - } - row = op_ctx->row = range.offset; - } else { - int32_t end = range.count; - if (end) { - end += range.offset; - } else { - end = ecs_table_count(table); - } - row = ++ op_ctx->row; - if (op_ctx->row >= end) { - return false; - } + /* If world passed to iterator is the real world, but query was created from + * a stage, stage takes precedence. */ + if (flecs_poly_is(it.world, ecs_world_t) && + flecs_poly_is(q->world, ecs_stage_t)) + { + it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } - ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - const ecs_entity_t *entities = ecs_table_entities(table); - flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); + it.real_world = q->real_world; + ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || + it.world != it.real_world, ECS_INVALID_PARAMETER, + "create iterator for stage when world is in multithreaded mode"); - return true; -} + it.query = q; + it.system = q->entity; + it.next = ecs_query_next; + it.fini = flecs_query_iter_fini; + it.field_count = q->field_count; + it.sizes = q->sizes; + it.set_fields = q->set_fields; + it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; + it.row_fields = q->row_fields; + it.up_fields = 0; + flecs_query_apply_iter_flags(&it, q); -static -bool flecs_query_store( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); - return true; - } else { - return false; - } -} + flecs_iter_init(it.world, &it, + flecs_iter_cache_ids | + flecs_iter_cache_trs | + flecs_iter_cache_sources | + flecs_iter_cache_ptrs); -static -bool flecs_query_reset( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - return true; - } else { - flecs_query_var_reset(op->src.var, ctx); - return false; + qit->query = q; + qit->query_vars = impl->vars; + qit->ops = impl->ops; + + ecs_query_cache_t *cache = impl->cache; + if (cache) { + qit->node = cache->list.first; + qit->last = cache->list.last; + + 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); + } + + cache->prev_match_count = cache->match_count; } -} -static -bool flecs_query_lookup( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; + if (var_count) { + qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } - const ecs_query_impl_t *query = ctx->query; - ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); - ecs_query_var_t *var = &query->vars[op->src.var]; + if (op_count) { + qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); + qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); + } - ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, - NULL, NULL, false); - if (!result) { - flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); - return false; +#ifdef FLECS_DEBUG + qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); +#endif + + for (i = 1; i < var_count; i ++) { + qit->vars[i].entity = EcsWildcard; } - flecs_query_var_set_entity(op, op->src.var, result, ctx); + it.variables = qit->vars; + it.variable_count = impl->pub.var_count; + it.variable_names = impl->pub.vars; - return true; + /* Set flags for unconstrained query iteration. Can be reinitialized when + * variables are constrained on iterator. */ + flecs_query_iter_constrain(&it); +error: + return it; } -static -bool flecs_query_setvars( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q) { - (void)op; - - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_var_id_t *src_vars = query->src_vars; - ecs_iter_t *it = ctx->it; - - if (redo) { - return false; + 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); } - int32_t i; - ecs_flags32_t up_fields = it->up_fields; - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = src_vars[i]; - if (!var_id) { - continue; - } + /* Ok, only for stats */ + ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); - if (up_fields & (1u << i)) { - continue; + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + if (cache) { + /* If monitors changed, do query rematching */ + ecs_flags32_t flags = q->flags; + if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(q->world); } - - it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } - return true; + return flecs_query_iter(world, q); } +/** + * @file query/engine/eval_member.c + * @brief Component member evaluation. + */ + + static -bool flecs_query_setthis( +bool flecs_query_member_cmp( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + bool neq) { - ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); - ecs_var_t *vars = ctx->vars; - ecs_var_t *this_var = &vars[op->first.var]; - - if (!redo) { - /* Save values so we can restore them later */ - op_ctx->range = vars[0].range; - - /* Constrain This table variable to a single entity from the table */ - vars[0].range = flecs_range_from_entity(this_var->entity, ctx); - vars[0].entity = this_var->entity; - return true; + ecs_table_range_t range; + if (op->other) { + ecs_var_id_t table_var = flecs_itovar(op->other - 1); + range = flecs_query_var_get_range(table_var, ctx); } else { - /* Restore previous values, so that instructions that are operating on - * the table variable use all the entities in the table. */ - vars[0].range = op_ctx->range; - vars[0].entity = 0; + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + } + + ecs_table_t *table = range.table; + if (!table) { return false; } -} -static -bool flecs_query_setfixed( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; + ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - if (redo) { - return false; + if (!range.count) { + range.count = ecs_table_count(range.table); } - int32_t i; - for (i = 0; i < q->term_count; i ++) { - const ecs_term_t *term = &q->terms[i]; - const ecs_term_ref_t *src = &term->src; - if (src->id & EcsIsEntity) { - it->sources[term->field_index] = ECS_TERM_REF_ID(src); + int32_t row, end = range.count; + if (end) { + end += range.offset; + } else { + end = ecs_table_count(range.table); + } + + void *data; + if (!redo) { + row = op_ctx->each.row = range.offset; + + /* Get data ptr starting from offset 0 so we can use row to index */ + range.offset = 0; + + /* Populate data field so we have the array we can compare the member + * value against. */ + data = op_ctx->data = + ecs_table_get_column(range.table, it->trs[field_index]->column, 0); + + it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; + } else { + row = ++ op_ctx->each.row; + if (op_ctx->each.row >= end) { + return false; } + + data = op_ctx->data; } - return true; -} + int32_t offset = (int32_t)op->first.entity; + int32_t size = (int32_t)(op->first.entity >> 32); + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_entity_t e = 0; + ecs_entity_t *val; -bool flecs_query_setids( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_iter_t *it = ctx->it; + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + + bool second_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { + uint64_t written = ctx->written[ctx->op_index]; + second_written = written & (1ull << op->second.var); + } + + if (second_written) { + ecs_flags16_t second_flags = flecs_query_ref_flags( + op->flags, EcsQuerySecond); + ecs_entity_t second = flecs_get_ref_entity( + &op->second, second_flags, ctx); + + do { + e = entities[row]; + + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + if (val[0] == second || second == EcsWildcard) { + if (!neq) { + goto match; + } + } else { + if (neq) { + goto match; + } + } + + row ++; + } while (row < end); - if (redo) { return false; + } else { + e = entities[row]; + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } - int32_t i; - for (i = 0; i < q->term_count; i ++) { - const ecs_term_t *term = &q->terms[i]; - it->ids[term->field_index] = term->id; +match: + if (op->other) { + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + flecs_query_var_set_entity(op, op->src.var, e, ctx); } + ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); + it->ids[field_index] = ecs_pair(mbr, val[0]); + + op_ctx->each.row = row; + return true; } -static -bool flecs_query_setid( +bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); - ctx->it->ids[op->field_index] = op->first.entity; - return true; + return flecs_query_member_cmp(op, redo, ctx, false); } -/* Check if entity is stored in table */ -static -bool flecs_query_contain( +bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_var_id_t src_id = op->src.var; - ecs_var_id_t first_id = op->first.var; + return flecs_query_member_cmp(op, redo, ctx, true); +} - ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); +/** + * @file query/engine/eval_pred.c + * @brief Equality predicate evaluation. + */ - ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); - return table == ecs_get_table(ctx->world, e); -} -/* Check if first and second id of pair from last operation are the same */ static -bool flecs_query_pair_eq( +const char* flecs_query_name_arg( const ecs_query_op_t *op, - bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; - } - - ecs_iter_t *it = ctx->it; - ecs_id_t id = it->ids[op->field_index]; - return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); + int8_t term_index = op->term_index; + const ecs_term_t *term = &ctx->query->pub.terms[term_index]; + return term->second.name; } static -void flecs_query_reset_after_block( - const ecs_query_op_t *start_op, - ecs_query_run_ctx_t *ctx, - ecs_query_ctrl_ctx_t *op_ctx, - bool result) +bool flecs_query_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) { - ecs_query_lbl_t op_index = start_op->next; - const ecs_query_op_t *op = &ctx->qit->ops[op_index]; - - int32_t field = op->field_index; - if (field == -1) { - goto done; - } - - /* Set/unset field */ - ecs_iter_t *it = ctx->it; - if (result) { - ECS_TERMSET_SET(it->set_fields, 1u << field); - return; - } - - /* Reset state after a field was not matched */ - ctx->written[op_index] = ctx->written[ctx->op_index]; - ctx->op_index = op_index; - ECS_TERMSET_CLEAR(it->set_fields, 1u << field); - - /* Ignore variables written by Not operation */ - uint64_t *written = ctx->written; - uint64_t written_cur = written[op->prev + 1]; - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; + if (l->table != r->table) { + return false; } - it->trs[field] = NULL; - - /* Reset variables */ - if (flags_1st & EcsQueryIsVar) { - if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ - flecs_query_var_reset(op->first.var, ctx); + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; } - } - if (flags_2nd & EcsQueryIsVar) { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ - flecs_query_var_reset(op->second.var, ctx); + if (r_end > l_end) { + return false; } + } else { + /* Entire table is matched */ } - /* If term has entity src, set it because no other instruction might */ - if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { - it->sources[field] = op->src.entity; - } - -done: - op_ctx->op_index = op_index; + return true; } static -bool flecs_query_run_block( +bool flecs_query_pred_eq_w_range( + const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, - ecs_query_ctrl_ctx_t *op_ctx) + ecs_table_range_t r) { - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - - if (!redo) { - op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); - } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { + if (redo) { return false; } - ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t src_var = op->src.var; + if (!(written & (1ull << src_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = src_var; + ctx->vars[l].range = r; + if (r.count == 1) { + ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; + } + return true; + } else { + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; - bool result = flecs_query_run_until( - redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); + if (!flecs_query_compare_range(&l, &r)) { + return false; + } - op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); - return result; + ctx->vars[src_var].range.offset = r.offset; + ctx->vars[src_var].range.count = r.count; + return true; + } } -static -ecs_query_lbl_t flecs_query_last_op_for_or_cond( - const ecs_query_op_t *ops, - ecs_query_lbl_t cur, - ecs_query_lbl_t last) +bool flecs_query_pred_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - const ecs_query_op_t *cur_op, *last_op = &ops[last]; - - do { - cur_op = &ops[cur]; - cur ++; - } while (cur_op->next != last && cur_op != last_op); + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); - return cur; + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -static -bool flecs_query_run_until_for_select_or( +bool flecs_query_pred_eq_name( + const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - const ecs_query_op_t *ops, - ecs_query_lbl_t first, - ecs_query_lbl_t cur, - int32_t last) + ecs_query_run_ctx_t *ctx) { - ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( - ops, cur, flecs_itolbl(last)); - if (redo) { - /* If redoing, start from the last instruction of the last executed - * sequence */ - cur = flecs_itolbl(last_for_cur - 1); + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; } - flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); -#ifdef FLECS_QUERY_TRACE - printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", - ctx->op_index == last ? "true" : "false"); -#endif - return ctx->op_index == last; + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -static -bool flecs_query_select_or( +bool flecs_query_pred_neq_w_range( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_table_range_t r) { - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t src_var = op->src.var; + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); - if (!redo) { - op_ctx->op_index = first; + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; } - const ecs_query_op_t *ops = qit->ops; - const ecs_query_op_t *first_op = &ops[first - 1]; - ecs_query_lbl_t last = first_op->next; - const ecs_query_op_t *last_op = &ops[last]; - const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; - bool result = false; + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } - do { - ecs_query_lbl_t cur = op_ctx->op_index; - ctx->op_index = cur; - ctx->written[cur] = op->written; + l_offset = l.offset; + l_count = l.count; - result = flecs_query_run_until_for_select_or( - redo, ctx, ops, flecs_itolbl(first - 1), cur, last); + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } - if (result) { - if (first == cur) { - break; - } + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[src_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } - /* If a previous operation in the OR chain returned a result for the - * same matched source, skip it so we don't yield for each matching - * element in the chain. */ + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; - /* Copy written status so that the variables we just wrote will show - * up as written for the test. This ensures that the instructions - * match against the result we already found, vs. starting a new - * search (the difference between select & with). */ - ecs_query_lbl_t prev = first; - bool dup_found = false; + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; + return false; + } +} - /* While terms of an OR chain always operate on the same source, it - * is possible that a table variable is resolved to an entity - * variable. When checking for duplicates, copy the entity variable - * to the table, to ensure we're only testing the found entity. */ - const ecs_query_op_t *prev_op = &ops[cur - 1]; - ecs_var_t old_table_var; - ecs_os_memset_t(&old_table_var, 0, ecs_var_t); - bool restore_table_var = false; +static +bool flecs_query_pred_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + bool is_neq) +{ + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; - if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (first_op->src.var != prev_op->src.var) { - restore_table_var = true; - old_table_var = ctx->vars[first_op->src.var]; - ctx->vars[first_op->src.var] = - ctx->vars[prev_op->src.var]; - } - } + ecs_var_id_t src_var = op->src.var; + const char *match = flecs_query_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + if (!l.table) { + return false; + } - int16_t field_index = op->field_index; - ecs_id_t prev_id = it->ids[field_index]; - const ecs_table_record_t *prev_tr = it->trs[field_index]; + if (!l.count) { + l.count = ecs_table_count(l.table); + } - do { - ctx->written[prev] = ctx->written[last]; + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_type_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->column_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } - flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), - prev, cur); + l = op_ctx->range; + } - if (ctx->op_index == last) { - /* Duplicate match was found, find next result */ - redo = true; - dup_found = true; - break; - } + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + if (!result) { + if (offset != -1) { break; - } while (true); - - /* Restore table variable to full range for next result */ - if (restore_table_var) { - ctx->vars[first_op->src.var] = old_table_var; } - - if (dup_found) { - continue; + } else { + if (offset == -1) { + offset = op_ctx->index; } - - /* Restore id in case op set it */ - it->ids[field_index] = prev_id; - it->trs[field_index] = prev_tr; - break; } + } - /* No result was found, go to next operation in chain */ - op_ctx->op_index = flecs_query_last_op_for_or_cond( - ops, op_ctx->op_index, last); - cur_op = &qit->ops[op_ctx->op_index]; - - redo = false; - } while (cur_op != last_op); + if (offset == -1) { + ctx->vars[src_var].range = op_ctx->range; + return false; + } - return result; + ctx->vars[src_var].range.offset = offset; + ctx->vars[src_var].range.count = (op_ctx->index - offset); + return true; } -static -bool flecs_query_with_or( +bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - - bool result = flecs_query_run_block(redo, ctx, op_ctx); - if (result) { - /* If a match was found, no need to keep searching for this source */ - op_ctx->op_index = op->next; - } - - return result; + return flecs_query_pred_match(op, redo, ctx, false); } -static -bool flecs_query_or( +bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - uint64_t written = ctx->written[ctx->op_index]; - if (!(written & (1ull << op->src.var))) { - return flecs_query_select_or(op, redo, ctx); - } - } - - return flecs_query_with_or(op, redo, ctx); + return flecs_query_pred_match(op, redo, ctx, true); } -static -bool flecs_query_run_block_w_reset( +bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); - bool result = flecs_query_run_block(redo, ctx, op_ctx); - flecs_query_reset_after_block(op, ctx, op_ctx, result); - return result; + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); } -static -bool flecs_query_not( +bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; } - return !flecs_query_run_block_w_reset(op, redo, ctx); + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); } -static -bool flecs_query_optional( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - bool result = flecs_query_run_block_w_reset(op, redo, ctx); - if (!redo) { - return true; /* Return at least once */ - } else { - return result; - } -} +/** + * @file query/engine/eval_toggle.c + * @brief Bitset toggle evaluation. + */ + + +typedef struct { + ecs_flags64_t mask; + bool has_bitset; +} flecs_query_row_mask_t; static -bool flecs_query_eval_if( - const ecs_query_op_t *op, - ecs_query_run_ctx_t *ctx, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind) +flecs_query_row_mask_t flecs_query_get_row_mask( + ecs_iter_t *it, + ecs_table_t *table, + int32_t block_index, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields, + ecs_query_toggle_ctx_t *op_ctx) { - bool result = true; - if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { - result = ctx->vars[ref->var].entity != EcsWildcard; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - flecs_query_reset_after_block(op, ctx, op_ctx, result); - return result; - } - return true; -} + ecs_flags64_t mask = UINT64_MAX; + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = and_fields | not_fields; + bool has_bitset = false; + + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } + + if (not_fields & field_bit) { + it->set_fields &= (ecs_termset_t)~field_bit; + } else if (and_fields & field_bit) { + ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ecs_id_t id = it->ids[i]; + ecs_bitset_t *bs = flecs_table_get_toggle(table, id); + if (!bs) { + if (not_fields & field_bit) { + if (op_ctx->prev_set_fields & field_bit) { + has_bitset = false; + break; + } + } + continue; + } -static -bool flecs_query_if_var( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || - !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || - !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) - { - return true; + ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); + ecs_flags64_t block = bs->data[block_index]; + + if (not_fields & field_bit) { + block = ~block; } + mask &= block; + has_bitset = true; } - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - return flecs_query_run_block(redo, ctx, op_ctx); + return (flecs_query_row_mask_t){ mask, has_bitset }; } static -bool flecs_query_if_set( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +bool flecs_query_toggle_for_up( + ecs_iter_t *it, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - ecs_iter_t *it = ctx->it; - int8_t field_index = flecs_ito(int8_t, op->other); + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; - ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - if (!redo) { - op_ctx->is_set = ecs_field_is_set(it, field_index); - } + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } - if (!op_ctx->is_set) { - return !redo; + bool match = false; + if ((it->set_fields & field_bit)) { + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + match = ecs_is_enabled_id(it->world, src, it->ids[i]); + } + + if (field_bit & not_fields) { + match = !match; + } + + if (!match) { + return false; + } } - return flecs_query_run_block(redo, ctx, op_ctx); + return true; } static -bool flecs_query_end( +bool flecs_query_toggle_cmp( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + ecs_query_run_ctx_t *ctx, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - (void)op; (void)ctx; - return !redo; -} + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + ecs_table_t *table = range.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -static -bool flecs_query_dispatch( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - switch(op->kind) { - case EcsQueryAnd: return flecs_query_and(op, redo, ctx); - case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); - case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); - case EcsQueryCache: return flecs_query_cache(op, redo, ctx); - case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); - case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); - case EcsQueryUp: return flecs_query_up(op, redo, ctx); - case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); - case EcsQueryWith: return flecs_query_with(op, redo, ctx); - case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); - case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); - case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); - case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); - case EcsQueryIds: return flecs_query_ids(op, redo, ctx); - case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); - case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); - case EcsQueryEach: return flecs_query_each(op, redo, ctx); - case EcsQueryStore: return flecs_query_store(op, redo, ctx); - case EcsQueryReset: return flecs_query_reset(op, redo, ctx); - case EcsQueryOr: return flecs_query_or(op, redo, ctx); - case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); - case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); - case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); - case EcsQueryEnd: return flecs_query_end(op, redo, ctx); - case EcsQueryNot: return flecs_query_not(op, redo, ctx); - case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); - case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); - case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); - case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); - case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); - case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); - case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); - case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); - case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); - case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); - case EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); - case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); - case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); - case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); - case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(op, redo, ctx); - case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); - case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); - case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); - case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); - case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); - case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); - case EcsQueryContain: return flecs_query_contain(op, redo, ctx); - case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); - case EcsQueryYield: return false; - case EcsQueryNothing: return false; + if ((and_fields & op_ctx->prev_set_fields) != and_fields) { + /* If not all fields matching and toggles are set, table can't match */ + return false; } - return false; -} -bool flecs_query_run_until( - bool redo, - ecs_query_run_ctx_t *ctx, - const ecs_query_op_t *ops, - ecs_query_lbl_t first, - ecs_query_lbl_t cur, - int32_t last) -{ - ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); + ecs_flags32_t up_fields = it->up_fields; + if (!redo) { + if (up_fields & (and_fields|not_fields)) { + /* If there are toggle fields that were matched with query + * traversal, evaluate those separately. */ + if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { + return false; + } - ctx->op_index = cur; - const ecs_query_op_t *op = &ops[ctx->op_index]; - const ecs_query_op_t *last_op = &ops[last]; - ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); + it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); + } + } -#ifdef FLECS_QUERY_TRACE - printf("%*sblock:\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent ++; -#endif + /* Shared fields are evaluated, can be ignored from now on */ + // and_fields &= ~up_fields; + not_fields &= ~up_fields; - do { - #ifdef FLECS_DEBUG - ctx->qit->profile[ctx->op_index].count[redo] ++; - #endif + if (!(table->flags & EcsTableHasToggle)) { + if (not_fields) { + /* If any of the toggle fields with a not operator are for fields + * that are set, without a bitset those fields can't match. */ + return false; + } else { + /* If table doesn't have toggles but query matched toggleable + * components, all entities match. */ + if (!redo) { + return true; + } else { + return false; + } + } + } -#ifdef FLECS_QUERY_TRACE - printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", - ctx->op_index, flecs_query_op_str(op->kind)); -#endif + if (table && !range.count) { + range.count = ecs_table_count(table); + if (!range.count) { + return false; + } + } - bool result = flecs_query_dispatch(op, redo, ctx); - cur = (&op->prev)[result]; - redo = cur < ctx->op_index; + int32_t i, j; + int32_t first, last, block_index, cur; + uint64_t block = 0; + if (!redo) { + op_ctx->range = range; + cur = op_ctx->cur = range.offset; + block_index = op_ctx->block_index = -1; + first = range.offset; + last = range.offset + range.count; + } else { + if (!op_ctx->has_bitset) { + goto done; + } - if (!redo) { - ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; + last = op_ctx->range.offset + op_ctx->range.count; + cur = op_ctx->cur; + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); + if (cur == last) { + goto done; } - ctx->op_index = cur; - op = &ops[ctx->op_index]; + first = cur; + block_index = op_ctx->block_index; + block = op_ctx->block; + } - if (cur <= first) { -#ifdef FLECS_QUERY_TRACE - printf("%*sfalse\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent --; -#endif - return false; - } - } while (op < last_op); - -#ifdef FLECS_QUERY_TRACE - printf("%*strue\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent --; -#endif + /* If end of last iteration is start of new block, compute new block */ + int32_t new_block_index = cur / 64, row = first; + if (new_block_index != block_index) { +compute_block: + block_index = op_ctx->block_index = new_block_index; - return true; -} + flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( + it, table, block_index, and_fields, not_fields, op_ctx); -/** - * @file query/engine/eval_iter.c - * @brief Query iterator. - */ + /* If table doesn't have bitset columns, all columns match */ + if (!(op_ctx->has_bitset = row_mask.has_bitset)) { + if (!not_fields) { + return true; + } else { + goto done; + } + } + /* No enabled bits */ + block = row_mask.mask; + if (!block) { +next_block: + new_block_index ++; + cur = new_block_index * 64; + if (cur >= last) { + /* No more rows */ + goto done; + } -static -void flecs_query_iter_run_ctx_init( - ecs_iter_t *it, - ecs_query_run_ctx_t *ctx) -{ - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); - ctx->world = it->real_world; - ctx->query = impl; - ctx->it = it; - ctx->vars = qit->vars; - ctx->query_vars = qit->query_vars; - ctx->written = qit->written; - ctx->op_ctx = qit->op_ctx; - ctx->qit = qit; -} + op_ctx->cur = cur; + goto compute_block; + } -void flecs_query_iter_constrain( - ecs_iter_t *it) -{ - ecs_query_run_ctx_t ctx; - flecs_query_iter_run_ctx_init(it, &ctx); - ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); + op_ctx->block = block; + } - const ecs_query_impl_t *query = ctx.query; - const ecs_query_t *q = &query->pub; - ecs_flags64_t it_written = it->constrained_vars; - ctx.written[0] = it_written; - if (it_written && ctx.query->src_vars) { - /* If variables were constrained, check if there are any table - * variables that have a constrained entity variable. */ - ecs_var_t *vars = ctx.vars; - int32_t i, count = q->field_count; - for (i = 0; i < count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - ecs_query_var_t *var = &query->vars[var_id]; + /* Find first enabled bit (TODO: use faster bitmagic) */ + int32_t first_bit = cur - (block_index * 64); + int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); + ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); - if (!(it_written & (1ull << var_id)) || - (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) - { - continue; + for (i = first_bit; i < last_bit; i ++) { + uint64_t bit = (1ull << i); + bool cond = 0 != (block & bit); + if (cond) { + /* Find last enabled bit */ + for (j = i; j < last_bit; j ++) { + bit = (1ull << j); + cond = !(block & bit); + if (cond) { + break; + } } - /* Initialize table variable with constrained entity variable */ - ecs_var_t *tvar = &vars[var->table_id]; - tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); - ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ + row = i + (block_index * 64); + cur = j + (block_index * 64); + break; } } - /* This function can be called multiple times when setting variables, so - * reset flags before setting them. */ - it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| - EcsIterTrivialSearch); + if (i == last_bit) { + goto next_block; + } - /* Figure out whether this query can utilize specialized iterator modes for - * improved performance. */ - ecs_flags32_t flags = q->flags; - ecs_query_cache_t *cache = query->cache; - if (flags & EcsQueryIsTrivial) { - if ((flags & EcsQueryMatchOnlySelf)) { - if (it_written) { - /* When we're testing against an entity or table, set the $this - * variable in advance since it won't change later on. This - * initializes it.count, it.entities and it.table. */ - flecs_query_set_iter_this(it, &ctx); + ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); - if (!cache) { - if (!(flags & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialTest; - } - } else if (flags & EcsQueryIsCacheable) { - it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; - } - } else { - if (!cache) { - if (!(flags & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialSearch; - } - } else if (flags & EcsQueryIsCacheable) { - if (!cache->order_by_callback) { - it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; - } - } - } + if (!(cur - row)) { + goto done; + } - /* If we're using a specialized iterator mode, make sure to - * initialize static component ids. Usually this is the first - * instruction of a query plan, but because we're not running the - * query plan when using a specialized iterator mode, manually call - * the operation on iterator init. */ - flecs_query_setids(NULL, false, &ctx); - } + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); } -} + op_ctx->cur = cur; -bool ecs_query_next( - ecs_iter_t *it) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + return true; - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); - ecs_query_run_ctx_t ctx; - flecs_query_iter_run_ctx_init(it, &ctx); - const ecs_query_op_t *ops = qit->ops; +done: + /* Restore range & set fields */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, + table, op_ctx->range.offset, op_ctx->range.count, ctx); + } - bool redo = it->flags & EcsIterIsValid; - if (redo) { - /* Change detection */ - if (!(it->flags & EcsIterSkip)) { - /* Mark table columns that are written to dirty */ - flecs_query_mark_fields_dirty(impl, it); - if (qit->prev) { - if (ctx.query->pub.flags & EcsQueryHasMonitor) { - /* If this query uses change detection, synchronize the - * monitor for the iterated table with the query */ - flecs_query_sync_match_monitor(impl, qit->prev); - } - } - } + it->set_fields = op_ctx->prev_set_fields; + return false; +} + +bool flecs_query_toggle( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; } - it->flags &= ~(EcsIterSkip); - it->flags |= EcsIterIsValid; - it->frame_offset += it->count; + ecs_flags64_t and_fields = op->first.entity; + ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; - /* Specialized iterator modes. When a query doesn't use any advanced - * features, it can call specialized iterator functions directly instead of - * going through the dispatcher of the query engine. - * The iterator mode is set during iterator initialization. Besides being - * determined by the query, there are different modes for searching and - * testing, where searching returns all matches for a query, whereas testing - * tests a single table or table range against the query. */ + return flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); +} - if (it->flags & EcsIterTrivialCached) { - /* Cached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - if (flecs_query_is_cache_search(&ctx)) { - goto trivial_search_yield; - } - } else if (it->flags & EcsIterTrivialTest) { - if (flecs_query_is_cache_test(&ctx, redo)) { - goto yield; - } - } +bool flecs_query_toggle_option( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; + op_ctx->optional_not = false; + op_ctx->has_bitset = false; + } + +repeat: {} + ecs_flags64_t and_fields = 0, not_fields = 0; + if (op_ctx->optional_not) { + not_fields = op->first.entity & op_ctx->prev_set_fields; } else { - /* Uncached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; - if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { - goto yield; - } - } else if (it->flags & EcsIterTrivialTest) { - int32_t fields = ctx.query->pub.term_count; - ecs_flags64_t mask = (2llu << (fields - 1)) - 1; - if (flecs_query_trivial_test(&ctx, redo, mask)) { - goto yield; - } - } else { - /* Default iterator mode. This enters the query VM dispatch loop. */ - if (flecs_query_run_until( - redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) - { - ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, - ECS_INTERNAL_ERROR, NULL); - flecs_query_set_iter_this(it, &ctx); - ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); - qit->op = flecs_itolbl(ctx.op_index - 1); - goto yield; - } - } + and_fields = op->first.entity; } - /* Done iterating */ - flecs_query_mark_fixed_fields_dirty(impl, it); - if (ctx.query->monitor) { - flecs_query_update_fixed_monitor( - ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); + bool result = flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); + if (!result) { + if (!op_ctx->optional_not) { + /* Run the not-branch of optional fields */ + op_ctx->optional_not = true; + it->set_fields = op_ctx->prev_set_fields; + redo = false; + goto repeat; + } } - ecs_iter_fini(it); - return false; + return result; +} -trivial_search_yield: - it->table = ctx.vars[0].range.table; - it->count = ecs_table_count(it->table); - it->entities = ecs_table_entities(it->table); -yield: - return true; -} +/** + * @file query/engine/eval_trav.c + * @brief Transitive/reflexive relationship traversal. + */ + static -void flecs_query_iter_fini_ctx( - ecs_iter_t *it, - ecs_query_iter_t *qit) +bool flecs_query_trav_fixed_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav, + ecs_entity_t second) { - const ecs_query_impl_t *query = flecs_query_impl(qit->query); - int32_t i, count = query->op_count; - ecs_query_op_t *ops = query->ops; - ecs_query_op_ctx_t *ctx = qit->op_ctx; - ecs_allocator_t *a = flecs_query_get_allocator(it); + ecs_table_t *table = range->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t count = range->count; + if (!count) { + count = ecs_table_count(table); + } - for (i = 0; i < count; i ++) { - ecs_query_op_t *op = &ops[i]; - switch(op->kind) { - case EcsQueryTrav: - flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); - break; - case EcsQueryUp: - case EcsQuerySelfUp: - case EcsQueryUnionEqUp: - case EcsQueryUnionEqSelfUp: { - ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; - if (cache->dir == EcsTravDown) { - flecs_query_down_cache_fini(a, cache); - } else { - flecs_query_up_cache_fini(cache); - } - break; - } - default: + int32_t i = range->offset, end = i + count; + for (; i < end; i ++) { + if (entities[i] == second) { + /* Even though table doesn't have the specific relationship + * pair, the relationship is reflexive and the target entity + * is stored in the table. */ break; } } + if (i == end) { + /* Table didn't contain target entity */ + return false; + } + if (count > 1) { + /* If the range contains more than one entity, set the range to + * return only the entity matched by the reflexive property. */ + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[op->src.var]; + ecs_table_range_t *var_range = &var->range; + var_range->offset = i; + var_range->count = 1; + var->entity = entities[i]; + } + + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; } static -void flecs_query_iter_fini( - ecs_iter_t *it) +bool flecs_query_trav_unknown_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t trav, + ecs_entity_t second) { - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_poly_assert(qit->query, ecs_query_t); - int32_t op_count = flecs_query_impl(qit->query)->op_count; - int32_t var_count = flecs_query_impl(qit->query)->var_count; + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_id_t src_var = op->src.var; + flecs_query_var_set_entity(op, src_var, second, ctx); + flecs_query_var_get_table(src_var, ctx); -#ifdef FLECS_DEBUG - if (it->flags & EcsIterProfile) { - char *str = ecs_query_plan_w_profile(qit->query, it); - printf("%s\n", str); - ecs_os_free(str); + ecs_table_t *table = ctx->vars[src_var].range.table; + if (table) { + if (flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return false; + } } - flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); -#endif - - flecs_query_iter_fini_ctx(it, qit); - flecs_iter_free_n(qit->vars, ecs_var_t, var_count); - flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); - flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); - - qit->vars = NULL; - qit->written = NULL; - qit->op_ctx = NULL; - qit->query = NULL; + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; } -ecs_iter_t flecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *q) +static +bool flecs_query_trav_fixed_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t it = {0}; - ecs_query_iter_t *qit = &it.priv_.iter.query; - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); + if (redo) { + return false; /* If everything's fixed, can only have a single result */ + } - int32_t i, var_count = impl->var_count; - int32_t op_count = impl->op_count ? impl->op_count : 1; - it.world = ECS_CONST_CAST(ecs_world_t*, world); + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; - /* If world passed to iterator is the real world, but query was created from - * a stage, stage takes precedence. */ - if (flecs_poly_is(it.world, ecs_world_t) && - flecs_poly_is(q->world, ecs_stage_t)) - { - it.world = ECS_CONST_CAST(ecs_world_t*, q->world); + /* Check if table has transitive relationship by traversing upwards */ + ecs_table_record_t *tr = NULL; + ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); + + if (!tr) { + if (op->match_flags & EcsTermReflexive) { + return flecs_query_trav_fixed_src_reflexive(op, ctx, + &range, trav, second); + } else { + return false; + } } - it.real_world = q->real_world; - ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || - it.world != it.real_world, ECS_INVALID_PARAMETER, - "create iterator for stage when world is in multithreaded mode"); + flecs_query_set_trav_match(op, tr, trav, second, ctx); + return true; +} - it.query = q; - it.system = q->entity; - it.next = ecs_query_next; - it.fini = flecs_query_iter_fini; - it.field_count = q->field_count; - it.sizes = q->sizes; - it.set_fields = q->set_fields; - it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; - it.row_fields = q->row_fields; - it.up_fields = 0; - flecs_query_apply_iter_flags(&it, q); +static +bool flecs_query_trav_unknown_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - flecs_iter_init(it.world, &it, - flecs_iter_cache_ids | - flecs_iter_cache_trs | - flecs_iter_cache_sources | - flecs_iter_cache_ptrs); + if (!redo) { + ecs_record_t *r_second = flecs_entities_get(ctx->world, second); + bool traversable = r_second && r_second->row & EcsEntityIsTraversable; + bool reflexive = op->match_flags & EcsTermReflexive; + if (!traversable && !reflexive) { + trav_ctx->cache.id = 0; - qit->query = q; - qit->query_vars = impl->vars; - qit->ops = impl->ops; + /* If there's no record for the entity, it can't have a subtree so + * forward operation to a regular select. */ + return flecs_query_select(op, redo, ctx); + } - ecs_query_cache_t *cache = impl->cache; - if (cache) { - qit->node = cache->list.first; - qit->last = cache->list.last; + /* Entity is traversable, which means it could have a subtree */ + flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); + trav_ctx->index = 0; - 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 (op->match_flags & EcsTermReflexive) { + trav_ctx->index = -1; + if(flecs_query_trav_unknown_src_reflexive( + op, ctx, trav, second)) + { + /* It's possible that we couldn't return the entity required for + * reflexive matching, like when it's a prefab or disabled. */ + return true; + } + } + } else { + if (!trav_ctx->cache.id) { + /* No traversal cache, which means this is a regular select */ + return flecs_query_select(op, redo, ctx); } - - cache->prev_match_count = cache->match_count; } - if (var_count) { - qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); + if (trav_ctx->index == -1) { + redo = false; /* First result after handling reflexive relationship */ + trav_ctx->index = 0; } - if (op_count) { - qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); - qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); + /* Forward to select */ + int32_t count = ecs_vec_count(&trav_ctx->cache.entities); + ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); + for (; trav_ctx->index < count; trav_ctx->index ++) { + ecs_trav_elem_t *el = &elems[trav_ctx->index]; + trav_ctx->and.idr = el->idr; /* prevents lookup by select */ + if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return true; + } + + redo = false; } -#ifdef FLECS_DEBUG - qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); -#endif + return false; +} - for (i = 1; i < var_count; i ++) { - qit->vars[i].entity = EcsWildcard; +static +bool flecs_query_trav_yield_reflexive_src( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav) +{ + ecs_var_t *vars = ctx->vars; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + int32_t offset = trav_ctx->offset, count = trav_ctx->count; + bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); + + if (trav_ctx->index >= (offset + count)) { + /* Restore previous offset, count */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + vars[src_var].range.offset = offset; + vars[src_var].range.count = count; + vars[src_var].entity = 0; + } + return false; } - it.variables = qit->vars; - it.variable_count = impl->pub.var_count; - it.variable_names = impl->pub.vars; + ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; + flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - /* Set flags for unconstrained query iteration. Can be reinitialized when - * variables are constrained on iterator. */ - flecs_query_iter_constrain(&it); -error: - return it; + /* Hijack existing variable to return one result at a time */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + ecs_table_t *table = vars[src_var].range.table; + ecs_assert(!table || table == ecs_get_table(ctx->world, entity), + ECS_INTERNAL_ERROR, NULL); + (void)table; + vars[src_var].entity = entity; + vars[src_var].range = flecs_range_from_entity(entity, ctx); + } + + return true; } -ecs_iter_t ecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *q) +static +bool flecs_query_trav_fixed_src_up_unknown_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - 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); - } + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - /* Ok, only for stats */ - ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); + if (!redo) { + flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); + trav_ctx->index = 0; + if (op->match_flags & EcsTermReflexive) { + trav_ctx->yield_reflexive = true; + trav_ctx->index = range.offset; + trav_ctx->offset = range.offset; + trav_ctx->count = range.count ? range.count : ecs_table_count(table); + } + } else { + trav_ctx->index ++; + } - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_cache_t *cache = impl->cache; - if (cache) { - /* If monitors changed, do query rematching */ - ecs_flags32_t flags = q->flags; - if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { - flecs_eval_component_monitors(q->world); + if (trav_ctx->yield_reflexive) { + if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { + return true; } + trav_ctx->yield_reflexive = false; + trav_ctx->index = 0; } - return flecs_query_iter(world, q); + if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { + return false; + } + + ecs_trav_elem_t *el = ecs_vec_get_t( + &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); + flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); + return true; +} + +bool flecs_query_trav( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + + if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + /* This can't happen, src or second should have been resolved */ + ecs_abort(ECS_INTERNAL_ERROR, + "invalid instruction sequence: unconstrained traversal"); + } else { + return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); + } + } else { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); + } else { + return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); + } + } } /** - * @file query/engine/eval_member.c - * @brief Component member evaluation. + * @file query/engine/eval_union.c + * @brief Union relationship evaluation. */ static -bool flecs_query_member_cmp( +bool flecs_query_union_with_wildcard( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, bool neq) { + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; + ecs_table_range_t range; - if (op->other) { - ecs_var_id_t table_var = flecs_itovar(op->other - 1); - range = flecs_query_var_get_range(table_var, ctx); - } else { + ecs_table_t *table; + if (!redo) { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + table = range.table; + if (!range.count) { + range.count = ecs_table_count(table); + } + + op_ctx->range = range; + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { + return neq; + } + + if (neq) { + if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { + /* If table has (R, Union) none match !(R, _) */ + return false; + } else { + /* If table doesn't have (R, Union) all match !(R, _) */ + return true; + } + } + + op_ctx->row = 0; + } else { + if (neq) { + /* !(R, _) terms only can have a single result */ + return false; + } + + range = op_ctx->range; + table = range.table; + op_ctx->row ++; } - ecs_table_t *table = range.table; - if (!table) { +next_row: + if (op_ctx->row >= range.count) { + /* Restore range */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + op_ctx->range.offset, op_ctx->range.count, ctx); + } return false; } - ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - if (!range.count) { - range.count = ecs_table_count(range.table); + ecs_entity_t e = ecs_table_entities(range.table) + [range.offset + op_ctx->row]; + ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + if (!tgt) { + op_ctx->row ++; + goto next_row; } - int32_t row, end = range.count; - if (end) { - end += range.offset; - } else { - end = ecs_table_count(range.table); + it->ids[field_index] = ecs_pair(rel, tgt); + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + range.offset + op_ctx->row, 1, ctx); } + flecs_query_set_vars(op, it->ids[field_index], ctx); - void *data; - if (!redo) { - row = op_ctx->each.row = range.offset; + return true; +} - /* Get data ptr starting from offset 0 so we can use row to index */ - range.offset = 0; +static +bool flecs_query_union_with_tgt( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, + ecs_entity_t tgt, + bool neq) +{ + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - /* Populate data field so we have the array we can compare the member - * value against. */ - data = op_ctx->data = - ecs_table_get_column(range.table, it->trs[field_index]->column, 0); + ecs_table_range_t range; + ecs_table_t *table; + if (!redo) { + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + table = range.table; + if (!range.count) { + range.count = ecs_table_count(table); + } - it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; - } else { - row = ++ op_ctx->each.row; - if (op_ctx->each.row >= end) { + op_ctx->range = range; + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { return false; } - data = op_ctx->data; + op_ctx->row = 0; + } else { + range = op_ctx->range; + table = range.table; + op_ctx->row ++; } - int32_t offset = (int32_t)op->first.entity; - int32_t size = (int32_t)(op->first.entity >> 32); - const ecs_entity_t *entities = ecs_table_entities(table); - ecs_entity_t e = 0; - ecs_entity_t *val; - - ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - - bool second_written = true; - if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { - uint64_t written = ctx->written[ctx->op_index]; - second_written = written & (1ull << op->second.var); +next_row: + if (op_ctx->row >= range.count) { + /* Restore range */ + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + op_ctx->range.offset, op_ctx->range.count, ctx); + } + return false; } - if (second_written) { - ecs_flags16_t second_flags = flecs_query_ref_flags( - op->flags, EcsQuerySecond); - ecs_entity_t second = flecs_get_ref_entity( - &op->second, second_flags, ctx); - - do { - e = entities[row]; - - val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); - if (val[0] == second || second == EcsWildcard) { - if (!neq) { - goto match; - } - } else { - if (neq) { - goto match; - } - } - - row ++; - } while (row < end); - - return false; - } else { - e = entities[row]; - val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); - flecs_query_var_set_entity(op, op->second.var, val[0], ctx); + ecs_entity_t e = ecs_table_entities(range.table) + [range.offset + op_ctx->row]; + ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + bool match = e_tgt == tgt; + if (neq) { + match = !match; } -match: - if (op->other) { - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - flecs_query_var_set_entity(op, op->src.var, e, ctx); + if (!match) { + op_ctx->row ++; + goto next_row; } - ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); - it->ids[field_index] = ecs_pair(mbr, val[0]); + it->ids[field_index] = ecs_pair(rel, tgt); + + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_var_narrow_range(op->src.var, table, + range.offset + op_ctx->row, 1, ctx); + } - op_ctx->each.row = row; + flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } -bool flecs_query_member_eq( +bool flecs_query_union_with( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + bool neq) { - return flecs_query_member_cmp(op, redo, ctx, false); -} + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_pair_second(ctx->world, id); -bool flecs_query_member_neq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_member_cmp(op, redo, ctx, true); + if (tgt == EcsWildcard) { + return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); + } else { + return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); + } } -/** - * @file query/engine/eval_pred.c - * @brief Equality predicate evaluation. - */ - - static -const char* flecs_query_name_arg( +bool flecs_query_union_select_tgt( const ecs_query_op_t *op, - ecs_query_run_ctx_t *ctx) -{ - int8_t term_index = op->term_index; - const ecs_term_t *term = &ctx->query->pub.terms[term_index]; - return term->second.name; -} - -static -bool flecs_query_compare_range( - const ecs_table_range_t *l, - const ecs_table_range_t *r) + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel, + ecs_entity_t tgt) { - if (l->table != r->table) { - return false; - } + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - if (l->count) { - int32_t l_end = l->offset + l->count; - int32_t r_end = r->offset + r->count; - if (r->offset < l->offset) { - return false; - } - if (r_end > l_end) { + if (!redo) { + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { return false; } + + op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); } else { - /* Entire table is matched */ + op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + } + + if (!op_ctx->cur) { + return false; } + ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + flecs_query_set_vars(op, it->ids[field_index], ctx); + + it->ids[field_index] = ecs_pair(rel, tgt); + return true; } static -bool flecs_query_pred_eq_w_range( +bool flecs_query_union_select_wildcard( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - ecs_table_range_t r) + const ecs_query_run_ctx_t *ctx, + ecs_entity_t rel) { - if (redo) { - return false; - } + ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - uint64_t written = ctx->written[ctx->op_index]; - ecs_var_id_t src_var = op->src.var; - if (!(written & (1ull << src_var))) { - /* left = unknown, right = known. Assign right-hand value to left */ - ecs_var_id_t l = src_var; - ctx->vars[l].range = r; - if (r.count == 1) { - ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; + if (!redo) { + op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->idr) { + return false; } - return true; - } else { - ecs_table_range_t l = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - if (!flecs_query_compare_range(&l, &r)) { + op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); + op_ctx->tgt = 0; + } + +next_tgt: + if (!op_ctx->tgt) { + if (!ecs_map_next(&op_ctx->tgt_iter)) { return false; } - ctx->vars[src_var].range.offset = r.offset; - ctx->vars[src_var].range.count = r.count; - return true; + op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); + op_ctx->cur = 0; + it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); + } + + if (!op_ctx->cur) { + op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); + } else { + op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + } + + if (!op_ctx->cur) { + op_ctx->tgt = 0; + goto next_tgt; } + + ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + flecs_query_set_vars(op, it->ids[field_index], ctx); + + return true; } -bool flecs_query_pred_eq( +bool flecs_query_union_select( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; (void)written; - ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized eq operand"); + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - ecs_table_range_t r = flecs_query_get_range( - op, &op->second, EcsQuerySecond, ctx); - return flecs_query_pred_eq_w_range(op, redo, ctx, r); + if (tgt == EcsWildcard) { + return flecs_query_union_select_wildcard(op, redo, ctx, rel); + } else { + return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); + } +} + +bool flecs_query_union( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_union_with(op, redo, ctx, false); + } else { + return flecs_query_union_select(op, redo, ctx); + } } -bool flecs_query_pred_eq_name( +bool flecs_query_union_neq( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx) { - const char *name = flecs_query_name_arg(op, ctx); - ecs_entity_t e = ecs_lookup(ctx->world, name); - if (!e) { - /* Entity doesn't exist */ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_union_with(op, redo, ctx, true); + } else { return false; } - - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -bool flecs_query_pred_neq_w_range( +static +void flecs_query_union_set_shared( const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx, - ecs_table_range_t r) + const ecs_query_run_ctx_t *ctx) { - ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); - ecs_var_id_t src_var = op->src.var; - ecs_table_range_t l = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_id_record_t *idr = op_ctx->idr_with; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - /* If tables don't match, neq always returns once */ - if (l.table != r.table) { - return true && !redo; - } + ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); + idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t l_offset; - int32_t l_count; - if (!redo) { - /* Make sure we're working with the correct table count */ - if (!l.count && l.table) { - l.count = ecs_table_count(l.table); - } + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; + ecs_entity_t src = it->sources[field_index]; + ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); + + it->ids[field_index] = ecs_pair(rel, tgt); +} - l_offset = l.offset; - l_count = l.count; +bool flecs_query_union_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!redo) { + if (!flecs_query_up_with(op, redo, ctx)) { + return false; + } - /* Cache old value */ - op_ctx->range = l; + flecs_query_union_set_shared(op, ctx); + return true; + } else { + return false; + } } else { - l_offset = op_ctx->range.offset; - l_count = op_ctx->range.count; + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); } +} - /* If the table matches, a Neq returns twice: once for the slice before the - * excluded slice, once for the slice after the excluded slice. If the right - * hand range starts & overlaps with the left hand range, there is only - * one slice. */ - ecs_var_t *var = &ctx->vars[src_var]; - if (!redo && r.offset > l_offset) { - int32_t end = r.offset; - if (end > l_count) { - end = l_count; +bool flecs_query_union_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (redo) { + goto next_for_union; } - /* Return first slice */ - var->range.table = l.table; - var->range.offset = l_offset; - var->range.count = end - l_offset; - op_ctx->redo = false; - return true; - } else if (!op_ctx->redo) { - int32_t l_end = op_ctx->range.offset + l_count; - int32_t r_end = r.offset + r.count; - - if (l_end <= r_end) { - /* If end of existing range falls inside the excluded range, there's - * nothing more to return */ - var->range = l; +next_for_self_up_with: + if (!flecs_query_self_up_with(op, redo, ctx, false)) { return false; } - /* Return second slice */ - var->range.table = l.table; - var->range.offset = r_end; - var->range.count = l_end - r_end; + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; + if (it->sources[field_index]) { + flecs_query_union_set_shared(op, ctx); + return true; + } + +next_for_union: + if (!flecs_query_union_with(op, redo, ctx, false)) { + goto next_for_self_up_with; + } - /* Flag so we know we're done the next redo */ - op_ctx->redo = true; return true; } else { - /* Restore previous value */ - var->range = l; - return false; + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); } } +/** + * @file query/engine/eval.c + * @brief Query engine implementation. + */ + + +/* Find tables with requested component that has traversable entities. */ static -bool flecs_query_pred_match( +bool flecs_query_up_select_table( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - bool is_neq) + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) { - ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); - uint64_t written = ctx->written[ctx->op_index]; - ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized match operand"); - (void)written; - - ecs_var_id_t src_var = op->src.var; - const char *match = flecs_query_name_arg(op, ctx); - ecs_table_range_t l; - if (!redo) { - l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - if (!l.table) { - return false; - } + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_iter_t *it = ctx->it; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + ecs_table_range_t range; - if (!l.count) { - l.count = ecs_table_count(l.table); + do { + bool result; + if (kind == FlecsQueryUpSelectId) { + result = flecs_query_select_id(op, redo, ctx, 0); + } else if (kind == FlecsQueryUpSelectDefault) { + result = flecs_query_select_w_id(op, redo, ctx, + op_ctx->with, 0); + } else if (kind == FlecsQueryUpSelectUnion) { + result = flecs_query_union_select(op, redo, ctx); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - op_ctx->range = l; - op_ctx->index = l.offset; - op_ctx->name_col = flecs_ito(int16_t, - ecs_table_get_type_index(ctx->world, l.table, - ecs_pair(ecs_id(EcsIdentifier), EcsName))); - if (op_ctx->name_col == -1) { - return is_neq; - } - op_ctx->name_col = flecs_ito(int16_t, - l.table->column_map[op_ctx->name_col]); - ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); - } else { - if (op_ctx->name_col == -1) { - /* Table has no name */ + if (!result) { + /* No remaining tables with component found. */ return false; } - l = op_ctx->range; + redo = true; + + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep searching until we find a table that has the requested component, + * with traversable entities */ + } while (!self && range.table->_->traversable_count == 0); + + if (!range.count) { + range.count = ecs_table_count(range.table); } - const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; - int32_t count = l.offset + l.count, offset = -1; - for (; op_ctx->index < count; op_ctx->index ++) { - const char *name = names[op_ctx->index].value; - bool result = strstr(name, match); - if (is_neq) { - result = !result; - } + op_ctx->table = range.table; + op_ctx->row = range.offset; + op_ctx->end = range.offset + range.count; + op_ctx->matched = it->ids[op->field_index]; - if (!result) { - if (offset != -1) { + return true; +} + +/* Find next traversable entity in table. */ +static +ecs_trav_down_t* flecs_query_up_find_next_traversable( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_world_t *world = ctx->world; + ecs_iter_t *it = ctx->it; + const ecs_query_t *q = &ctx->query->pub; + ecs_table_t *table = op_ctx->table; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + + if (table->_->traversable_count == 0) { + /* No traversable entities in table */ + op_ctx->table = NULL; + return NULL; + } else { + int32_t row; + ecs_entity_t entity = 0; + const ecs_entity_t *entities = ecs_table_entities(table); + + for (row = op_ctx->row; row < op_ctx->end; row ++) { + entity = entities[row]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (record->row & EcsEntityIsTraversable) { + /* Found traversable entity */ + it->sources[op->field_index] = entity; break; } - } else { - if (offset == -1) { - offset = op_ctx->index; - } } - } - if (offset == -1) { - ctx->vars[src_var].range = op_ctx->range; - return false; - } + if (row == op_ctx->end) { + /* No traversable entities remaining in table */ + op_ctx->table = NULL; + return NULL; + } - ctx->vars[src_var].range.offset = offset; - ctx->vars[src_var].range.count = (op_ctx->index - offset); - return true; -} + op_ctx->row = row; -bool flecs_query_pred_eq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, false); -} + /* Get down cache entry for traversable entity */ + bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; + op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, + op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); + op_ctx->cache_elem = -1; + } -bool flecs_query_pred_neq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, true); + return op_ctx->down; } -bool flecs_query_pred_neq( +/* Select all tables that can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) { - uint64_t written = ctx->written[ctx->op_index]; (void)written; - ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), - ECS_INTERNAL_ERROR, - "invalid instruction sequence: uninitialized neq operand"); + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_iter_t *it = ctx->it; + bool redo_select = redo; + const ecs_query_t *q = &ctx->query->pub; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_table_range_t r = flecs_query_get_range( - op, &op->second, EcsQuerySecond, ctx); - return flecs_query_pred_neq_w_range(op, redo, ctx, r); -} + op_ctx->trav = q->terms[op->term_index].trav; -bool flecs_query_pred_neq_name( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - const char *name = flecs_query_name_arg(op, ctx); - ecs_entity_t e = ecs_lookup(ctx->world, name); - if (!e) { - /* Entity doesn't exist */ - return true && !redo; + /* Reuse id record from previous iteration if possible*/ + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); } - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_neq_w_range(op, redo, ctx, r); -} + /* 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)) + { + if (!self) { + /* If operation does not match owned components, return false */ + return false; + } else if (kind == FlecsQueryUpSelectId) { + return flecs_query_select_id(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } else if (kind == FlecsQueryUpSelectDefault) { + return flecs_query_select(op, redo, ctx); + } else if (kind == FlecsQueryUpSelectUnion) { + return flecs_query_union_select(op, redo, ctx); + } else { + /* Invalid select kind */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + } -/** - * @file query/engine/eval_toggle.c - * @brief Bitset toggle evaluation. - */ + if (!redo) { + /* Get component id to match */ + op_ctx->with = flecs_query_op_get_id(op, ctx); + /* Get id record for component to match */ + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + if (!op_ctx->idr_with) { + /* If id record does not exist, there can't be any results */ + return false; + } -typedef struct { - ecs_flags64_t mask; - bool has_bitset; -} flecs_query_row_mask_t; + op_ctx->down = NULL; + op_ctx->cache_elem = 0; + } -static -flecs_query_row_mask_t flecs_query_get_row_mask( - ecs_iter_t *it, - ecs_table_t *table, - int32_t block_index, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields, - ecs_query_toggle_ctx_t *op_ctx) -{ - ecs_flags64_t mask = UINT64_MAX; - int32_t i, field_count = it->field_count; - ecs_flags64_t fields = and_fields | not_fields; - bool has_bitset = false; + /* Get last used entry from down traversal cache. Cache entries in the down + * traversal cache contain a list of tables that can reach the requested + * component through the traversal relationship, for a traversable entity + * which acts as the key for the cache. */ + ecs_trav_down_t *down = op_ctx->down; - for (i = 0; i < field_count; i ++) { - uint64_t field_bit = 1llu << i; - if (!(fields & field_bit)) { - continue; - } +next_down_entry: + /* Get (next) entry in down traversal cache */ + while (!down) { + ecs_table_t *table = op_ctx->table; - if (not_fields & field_bit) { - it->set_fields &= (ecs_termset_t)~field_bit; - } else if (and_fields & field_bit) { - ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } + /* Get (next) table with traversable entities that have the + * requested component. We'll traverse downwards from the + * traversable entities in the table to find all entities that can + * reach the component through the traversal relationship. */ + if (!table) { + /* Reset source, in case we have to return a component matched + * by the entity in the found table. */ + it->sources[op->field_index] = 0; - ecs_id_t id = it->ids[i]; - ecs_bitset_t *bs = flecs_table_get_toggle(table, id); - if (!bs) { - if (not_fields & field_bit) { - if (op_ctx->prev_set_fields & field_bit) { - has_bitset = false; - break; - } + if (!flecs_query_up_select_table( + op, redo_select, ctx, trav_kind, kind)) + { + return false; } - continue; - } - - ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); - ecs_flags64_t block = bs->data[block_index]; - - if (not_fields & field_bit) { - block = ~block; - } - mask &= block; - has_bitset = true; - } - return (flecs_query_row_mask_t){ mask, has_bitset }; -} + table = op_ctx->table; -static -bool flecs_query_toggle_for_up( - ecs_iter_t *it, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields) -{ - int32_t i, field_count = it->field_count; - ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; + /* If 'self' is true, we're evaluating a term with self|up. This + * means that before traversing downwards, we should also return + * the current table as result. */ + if (self) { + if (!flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + flecs_reset_source_set_flag(it, op->field_index); + op_ctx->row --; + return true; + } + } - for (i = 0; i < field_count; i ++) { - uint64_t field_bit = 1llu << i; - if (!(fields & field_bit)) { - continue; + redo_select = true; + } else { + /* Evaluate next entity in table */ + op_ctx->row ++; } - bool match = false; - if ((it->set_fields & field_bit)) { - ecs_entity_t src = it->sources[i]; - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); - match = ecs_is_enabled_id(it->world, src, it->ids[i]); + /* Get down cache entry for next traversable entity in table */ + down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); + if (!down) { + goto next_down_entry; } + } - if (field_bit & not_fields) { - match = !match; - } +next_down_elem: + /* Get next element (table) in cache entry */ + if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { + /* No more elements in cache entry, find next.*/ + down = NULL; + goto next_down_entry; + } - if (!match) { - return false; - } + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); + flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); + flecs_query_set_vars(op, op_ctx->matched, ctx); + + if (flecs_query_table_filter(elem->table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + /* Go to next table if table contains prefabs, disabled entities or + * entities that are not queryable. */ + goto next_down_elem; } + flecs_set_source_set_flag(it, op->field_index); + return true; } -static -bool flecs_query_toggle_cmp( +/* Check if a table can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, - ecs_query_run_ctx_t *ctx, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields) + const ecs_query_run_ctx_t *ctx) { + const ecs_query_t *q = &ctx->query->pub; + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - ecs_table_range_t range = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - ecs_table_t *table = range.table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if ((and_fields & op_ctx->prev_set_fields) != and_fields) { - /* If not all fields matching and toggles are set, table can't match */ + op_ctx->trav = q->terms[op->term_index].trav; + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); + } + + if (!op_ctx->idr_trav || + !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) + { + /* If there are no tables with traversable relationship, there are no + * matches. */ return false; } - ecs_flags32_t up_fields = it->up_fields; if (!redo) { - if (up_fields & (and_fields|not_fields)) { - /* If there are toggle fields that were matched with query - * traversal, evaluate those separately. */ - if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { - return false; - } + op_ctx->trav = q->terms[op->term_index].trav; + op_ctx->with = flecs_query_op_get_id(op, ctx); + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); + /* If id record for component doesn't exist, there are no matches */ + if (!op_ctx->idr_with) { + return false; } - } - - /* Shared fields are evaluated, can be ignored from now on */ - // and_fields &= ~up_fields; - not_fields &= ~up_fields; - if (!(table->flags & EcsTableHasToggle)) { - if (not_fields) { - /* If any of the toggle fields with a not operator are for fields - * that are set, without a bitset those fields can't match. */ + /* Get the range (table) that is currently being evaluated. In most + * cases the range will cover the entire table, but in some cases it + * can only cover a subset of the entities in the table. */ + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.table) { return false; - } else { - /* If table doesn't have toggles but query matched toggleable - * components, all entities match. */ - if (!redo) { - return true; - } else { - return false; - } } - } - if (table && !range.count) { - range.count = ecs_table_count(table); - if (!range.count) { + /* Get entry from up traversal cache. The up traversal cache contains + * the entity on which the component was found, with additional metadata + * on where it is stored. */ + ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &op_ctx->cache, + range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, + op_ctx->idr_trav); + + if (!up) { + /* Component is not reachable from table */ return false; } + + it->sources[op->field_index] = flecs_entities_get_alive( + ctx->world, up->src); + it->trs[op->field_index] = up->tr; + it->ids[op->field_index] = up->id; + flecs_query_set_vars(op, up->id, ctx); + flecs_set_source_set_flag(it, op->field_index); + return true; + } else { + /* The table either can or can't reach the component, nothing to do for + * a second evaluation of this operation.*/ + return false; } +} - int32_t i, j; - int32_t first, last, block_index, cur; - uint64_t block = 0; +/* Check if a table can reach the target component through the traversal + * relationship, or if the table has the target component itself. */ +bool flecs_query_self_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool id_only) +{ if (!redo) { - op_ctx->range = range; - cur = op_ctx->cur = range.offset; - block_index = op_ctx->block_index = -1; - first = range.offset; - last = range.offset + range.count; - } else { - if (!op_ctx->has_bitset) { - goto done; - } + bool result; - last = op_ctx->range.offset + op_ctx->range.count; - cur = op_ctx->cur; - ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - if (cur == last) { - goto done; + if (id_only) { + /* Simple id, no wildcards */ + result = flecs_query_with_id(op, redo, ctx); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + op_ctx->remaining = 1; + } else { + result = flecs_query_with(op, redo, ctx); } - first = cur; - block_index = op_ctx->block_index; - block = op_ctx->block; - } - - /* If end of last iteration is start of new block, compute new block */ - int32_t new_block_index = cur / 64, row = first; - if (new_block_index != block_index) { -compute_block: - block_index = op_ctx->block_index = new_block_index; - - flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( - it, table, block_index, and_fields, not_fields, op_ctx); + flecs_reset_source_set_flag(ctx->it, op->field_index); - /* If table doesn't have bitset columns, all columns match */ - if (!(op_ctx->has_bitset = row_mask.has_bitset)) { - if (!not_fields) { - return true; - } else { - goto done; + if (result) { + /* Table has component, no need to traverse*/ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + op_ctx->trav = 0; + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { + /* Matching self, so set sources to 0 */ + ecs_iter_t *it = ctx->it; + it->sources[op->field_index] = 0; } + return true; } - /* No enabled bits */ - block = row_mask.mask; - if (!block) { -next_block: - new_block_index ++; - cur = new_block_index * 64; - if (cur >= last) { - /* No more rows */ - goto done; - } - - op_ctx->cur = cur; - goto compute_block; + /* Table doesn't have component, traverse relationship */ + return flecs_query_up_with(op, redo, ctx); + } else { + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + if (op_ctx->trav == 0) { + /* If matching components without traversing, make sure to still + * match remaining components that match the id (wildcard). */ + return flecs_query_with(op, redo, ctx); } - - op_ctx->block = block; } - /* Find first enabled bit (TODO: use faster bitmagic) */ - int32_t first_bit = cur - (block_index * 64); - int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); - ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); - ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); + return false; +} - for (i = first_bit; i < last_bit; i ++) { - uint64_t bit = (1ull << i); - bool cond = 0 != (block & bit); - if (cond) { - /* Find last enabled bit */ - for (j = i; j < last_bit; j ++) { - bit = (1ull << j); - cond = !(block & bit); - if (cond) { - break; - } - } +/** + * @file query/engine/eval_utils.c + * @brief Query engine evaluation utilities. + */ - row = i + (block_index * 64); - cur = j + (block_index * 64); - break; + +void flecs_query_set_iter_this( + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx) +{ + const ecs_var_t *var = &ctx->vars[0]; + const ecs_table_range_t *range = &var->range; + ecs_table_t *table = range->table; + int32_t count = range->count; + if (table) { + if (!count) { + count = ecs_table_count(table); + } + it->table = table; + it->offset = range->offset; + it->count = count; + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities += it->offset; } + } else if (count == 1) { + it->count = 1; + it->entities = &ctx->vars[0].entity; } +} - if (i == last_bit) { - goto next_block; - } +ecs_query_op_ctx_t* flecs_op_ctx_( + const ecs_query_run_ctx_t *ctx) +{ + return &ctx->op_ctx[ctx->op_index]; +} - ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + +void flecs_reset_source_set_flag( + ecs_iter_t *it, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); +} + +void flecs_set_source_set_flag( + ecs_iter_t *it, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_SET(it->up_fields, 1u << field_index); +} - if (!(cur - row)) { - goto done; +ecs_table_range_t flecs_range_from_entity( + ecs_entity_t e, + const ecs_query_run_ctx_t *ctx) +{ + ecs_record_t *r = flecs_entities_get(ctx->world, e); + if (!r) { + return (ecs_table_range_t){ 0 }; } + return (ecs_table_range_t){ + .table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1 + }; +} - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); +ecs_table_range_t flecs_query_var_get_range( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return var->range; } - op_ctx->cur = cur; - return true; - -done: - /* Restore range & set fields */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, - table, op_ctx->range.offset, op_ctx->range.count, ctx); + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range; } - it->set_fields = op_ctx->prev_set_fields; - return false; + return (ecs_table_range_t){ 0 }; } -bool flecs_query_toggle( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) +ecs_table_t* flecs_query_var_get_table( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - if (!redo) { - op_ctx->prev_set_fields = it->set_fields; + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return table; } - ecs_flags64_t and_fields = op->first.entity; - ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range.table; + } - return flecs_query_toggle_cmp( - op, redo, ctx, and_fields, not_fields); + return NULL; } -bool flecs_query_toggle_option( +ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t *it = ctx->it; - ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); - if (!redo) { - op_ctx->prev_set_fields = it->set_fields; - op_ctx->optional_not = false; - op_ctx->has_bitset = false; - } - -repeat: {} - ecs_flags64_t and_fields = 0, not_fields = 0; - if (op_ctx->optional_not) { - not_fields = op->first.entity & op_ctx->prev_set_fields; + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + return ecs_get_table(ctx->world, ref->entity); } else { - and_fields = op->first.entity; - } - - bool result = flecs_query_toggle_cmp( - op, redo, ctx, and_fields, not_fields); - if (!result) { - if (!op_ctx->optional_not) { - /* Run the not-branch of optional fields */ - op_ctx->optional_not = true; - it->set_fields = op_ctx->prev_set_fields; - redo = false; - goto repeat; - } + return flecs_query_var_get_table(ref->var, ctx); } - - return result; } - -/** - * @file query/engine/eval_trav.c - * @brief Transitive/reflexive relationship traversal. - */ - - -static -bool flecs_query_trav_fixed_src_reflexive( +ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_table_range_t *range, - ecs_entity_t trav, - ecs_entity_t second) + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) { - ecs_table_t *table = range->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t count = range->count; - if (!count) { - count = ecs_table_count(table); - } - - int32_t i = range->offset, end = i + count; - for (; i < end; i ++) { - if (entities[i] == second) { - /* Even though table doesn't have the specific relationship - * pair, the relationship is reflexive and the target entity - * is stored in the table. */ - break; + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ref->entity, ctx); + } else { + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(var->entity, ctx); } } - if (i == end) { - /* Table didn't contain target entity */ - return false; - } - if (count > 1) { - /* If the range contains more than one entity, set the range to - * return only the entity matched by the reflexive property. */ - ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[op->src.var]; - ecs_table_range_t *var_range = &var->range; - var_range->offset = i; - var_range->count = 1; - var->entity = entities[i]; - } - - flecs_query_set_trav_match(op, NULL, trav, second, ctx); - return true; + return (ecs_table_range_t){0}; } -static -bool flecs_query_trav_unknown_src_reflexive( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t trav, - ecs_entity_t second) +ecs_entity_t flecs_query_var_get_entity( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_var_id_t src_var = op->src.var; - flecs_query_var_set_entity(op, src_var, second, ctx); - flecs_query_var_get_table(src_var, ctx); - - ecs_table_t *table = ctx->vars[src_var].range.table; - if (table) { - if (flecs_query_table_filter(table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - return false; - } + ecs_var_t *var = &ctx->vars[var_id]; + ecs_entity_t entity = var->entity; + if (entity) { + return entity; } - flecs_query_set_trav_match(op, NULL, trav, second, ctx); - return true; + ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = var->range.table; + const ecs_entity_t *entities = ecs_table_entities(table); + var->entity = entities[var->range.offset]; + return var->entity; } -static -bool flecs_query_trav_fixed_src_up_fixed_second( - const ecs_query_op_t *op, - bool redo, +void flecs_query_var_reset( + ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { - if (redo) { - return false; /* If everything's fixed, can only have a single result */ - } + ctx->vars[var_id].entity = EcsWildcard; + ctx->vars[var_id].range.table = NULL; +} - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); - ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); - ecs_table_t *table = range.table; +void flecs_query_var_set_range( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx) +{ + (void)op; + ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; +} - /* Check if table has transitive relationship by traversing upwards */ - ecs_table_record_t *tr = NULL; - ecs_search_relation(ctx->world, table, 0, - ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); +void flecs_query_var_narrow_range( + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx) +{ + ecs_var_t *var = &ctx->vars[var_id]; + + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; - if (!tr) { - if (op->match_flags & EcsTermReflexive) { - return flecs_query_trav_fixed_src_reflexive(op, ctx, - &range, trav, second); - } else { - return false; - } + ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); + if (ctx->query_vars[var_id].kind != EcsVarTable) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + var->entity = ecs_table_entities(table)[offset]; } - - flecs_query_set_trav_match(op, tr, trav, second, ctx); - return true; } -static -bool flecs_query_trav_unknown_src_up_fixed_second( +void flecs_query_var_set_entity( const ecs_query_op_t *op, - bool redo, + ecs_var_id_t var_id, + ecs_entity_t entity, const ecs_query_run_ctx_t *ctx) { - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - - if (!redo) { - ecs_record_t *r_second = flecs_entities_get(ctx->world, second); - bool traversable = r_second && r_second->row & EcsEntityIsTraversable; - bool reflexive = op->match_flags & EcsTermReflexive; - if (!traversable && !reflexive) { - trav_ctx->cache.id = 0; - - /* If there's no record for the entity, it can't have a subtree so - * forward operation to a regular select. */ - return flecs_query_select(op, redo, ctx); - } + (void)op; + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->range.table = NULL; + var->entity = entity; +} - /* Entity is traversable, which means it could have a subtree */ - flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); - trav_ctx->index = 0; +void flecs_query_set_vars( + const ecs_query_op_t *op, + ecs_id_t id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - if (op->match_flags & EcsTermReflexive) { - trav_ctx->index = -1; - if(flecs_query_trav_unknown_src_reflexive( - op, ctx, trav, second)) - { - /* It's possible that we couldn't return the entity required for - * reflexive matching, like when it's a prefab or disabled. */ - return true; + if (flags_1st & EcsQueryIsVar) { + ecs_var_id_t var = op->first.var; + if (op->written & (1ull << var)) { + if (ECS_IS_PAIR(id)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + } else { + flecs_query_var_set_entity(op, var, id, ctx); } } - } else { - if (!trav_ctx->cache.id) { - /* No traversal cache, which means this is a regular select */ - return flecs_query_select(op, redo, ctx); - } } - if (trav_ctx->index == -1) { - redo = false; /* First result after handling reflexive relationship */ - trav_ctx->index = 0; + if (flags_2nd & EcsQueryIsVar) { + ecs_var_id_t var = op->second.var; + if (op->written & (1ull << var)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + } } +} - /* Forward to select */ - int32_t count = ecs_vec_count(&trav_ctx->cache.entities); - ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); - for (; trav_ctx->index < count; trav_ctx->index ++) { - ecs_trav_elem_t *el = &elems[trav_ctx->index]; - trav_ctx->and.idr = el->idr; /* prevents lookup by select */ - if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - return true; - } - - redo = false; +ecs_table_range_t flecs_get_ref_range( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) +{ + if (flag & EcsQueryIsEntity) { + return flecs_range_from_entity(ref->entity, ctx); + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_range(ref->var, ctx); } + return (ecs_table_range_t){0}; +} - return false; +ecs_entity_t flecs_get_ref_entity( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) +{ + if (flag & EcsQueryIsEntity) { + return ref->entity; + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_entity(ref->var, ctx); + } + return 0; } -static -bool flecs_query_trav_yield_reflexive_src( +ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx, - ecs_table_range_t *range, - ecs_entity_t trav) + uint64_t written, + const ecs_query_run_ctx_t *ctx) { - ecs_var_t *vars = ctx->vars; - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - int32_t offset = trav_ctx->offset, count = trav_ctx->count; - bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t first = 0, second = 0; - if (trav_ctx->index >= (offset + count)) { - /* Restore previous offset, count */ - if (src_is_var) { - ecs_var_id_t src_var = op->src.var; - vars[src_var].range.offset = offset; - vars[src_var].range.count = count; - vars[src_var].entity = 0; + if (flags_1st) { + if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { + first = flecs_get_ref_entity(&op->first, flags_1st, ctx); + } else if (flags_1st & EcsQueryIsVar) { + first = EcsWildcard; } - return false; } - - ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; - flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - - /* Hijack existing variable to return one result at a time */ - if (src_is_var) { - ecs_var_id_t src_var = op->src.var; - ecs_table_t *table = vars[src_var].range.table; - ecs_assert(!table || table == ecs_get_table(ctx->world, entity), - ECS_INTERNAL_ERROR, NULL); - (void)table; - vars[src_var].entity = entity; - vars[src_var].range = flecs_range_from_entity(entity, ctx); + if (flags_2nd) { + if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); + } else if (flags_2nd & EcsQueryIsVar) { + second = EcsWildcard; + } } - return true; + if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { + return ecs_pair(first, second); + } else { + return flecs_entities_get_alive(ctx->world, first); + } } -static -bool flecs_query_trav_fixed_src_up_unknown_second( +ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, - bool redo, const ecs_query_run_ctx_t *ctx) { - ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); - ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); - ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); - ecs_table_t *table = range.table; - ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + uint64_t written = ctx->written[ctx->op_index]; + return flecs_query_op_get_id_w_written(op, written, ctx); +} - if (!redo) { - flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); - trav_ctx->index = 0; - if (op->match_flags & EcsTermReflexive) { - trav_ctx->yield_reflexive = true; - trav_ctx->index = range.offset; - trav_ctx->offset = range.offset; - trav_ctx->count = range.count ? range.count : ecs_table_count(table); - } +int16_t flecs_query_next_column( + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { + column = column + 1; } else { - trav_ctx->index ++; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(NULL, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); } + return flecs_ito(int16_t, column); +} - if (trav_ctx->yield_reflexive) { - if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { - return true; - } - trav_ctx->yield_reflexive = false; - trav_ctx->index = 0; - } +void flecs_query_it_set_tr( + ecs_iter_t *it, + int32_t field_index, + const ecs_table_record_t *tr) +{ + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + it->trs[field_index] = tr; +} - if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { - return false; - } +ecs_id_t flecs_query_it_set_id( + ecs_iter_t *it, + ecs_table_t *table, + int32_t field_index, + int32_t column) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + return it->ids[field_index] = table->type.array[column]; +} - ecs_trav_elem_t *el = ecs_vec_get_t( - &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); - flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); - return true; +void flecs_query_set_match( + const ecs_query_op_t *op, + ecs_table_t *table, + int32_t column, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t field_index = op->field_index; + if (field_index == -1) { + return; + } + + ecs_iter_t *it = ctx->it; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[column]; + flecs_query_it_set_tr(it, field_index, tr); + ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); + flecs_query_set_vars(op, matched, ctx); } -bool flecs_query_trav( +void flecs_query_set_trav_match( const ecs_query_op_t *op, - bool redo, + const ecs_table_record_t *tr, + ecs_entity_t trav, + ecs_entity_t second, const ecs_query_run_ctx_t *ctx) { - uint64_t written = ctx->written[ctx->op_index]; - - if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - /* This can't happen, src or second should have been resolved */ - ecs_abort(ECS_INTERNAL_ERROR, - "invalid instruction sequence: unconstrained traversal"); - } else { - return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); - } - } else { - if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); - } else { - return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); - } + int32_t field_index = op->field_index; + if (field_index == -1) { + return; } + + ecs_iter_t *it = ctx->it; + ecs_id_t matched = ecs_pair(trav, second); + it->ids[op->field_index] = matched; + flecs_query_it_set_tr(it, op->field_index, tr); + flecs_query_set_vars(op, matched, ctx); +} + +bool flecs_query_table_filter( + ecs_table_t *table, + ecs_query_lbl_t other, + ecs_flags32_t filter_mask) +{ + uint32_t filter = flecs_ito(uint32_t, other); + return (table->flags & filter_mask & filter) != 0; } /** - * @file query/engine/eval_union.c - * @brief Union relationship evaluation. + * @file query/engine/trav_cache.c + * @brief Cache that stores the result of graph traversal. */ static -bool flecs_query_union_with_wildcard( - const ecs_query_op_t *op, - bool redo, +void flecs_query_build_down_cache( + ecs_world_t *world, + ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - bool neq) + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); + if (!idr) { + return; + } - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); + ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + elem->entity = entity; + elem->idr = idr; + + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + int32_t i, count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (r->row & EcsEntityIsTraversable) { + flecs_query_build_down_cache( + world, a, ctx, cache, trav, entities[i]); + } + } } + } +} - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return neq; +static +void flecs_query_build_up_cache( + ecs_world_t *world, + ecs_allocator_t *a, + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table, + const ecs_table_record_t *tr, + int32_t root_column) +{ + ecs_id_t *ids = table->type.array; + int32_t i = tr->index, end = i + tr->count; + bool is_root = root_column == -1; + + for (; i < end; i ++) { + ecs_entity_t second = ecs_pair_second(world, ids[i]); + if (is_root) { + root_column = i; } - if (neq) { - if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { - /* If table has (R, Union) none match !(R, _) */ - return false; - } else { - /* If table doesn't have (R, Union) all match !(R, _) */ - return true; + ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + + el->entity = second; + el->tr = &table->_->records[i]; + el->idr = NULL; + + ecs_record_t *r = flecs_entities_get_any(world, second); + if (r->table) { + ecs_table_record_t *r_tr = flecs_id_record_get_table( + cache->idr, r->table); + if (!r_tr) { + return; } + flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, + r_tr, root_column); } + } +} - op_ctx->row = 0; - } else { - if (neq) { - /* !(R, _) terms only can have a single result */ - return false; - } +void flecs_query_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache) +{ + ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); +} - range = op_ctx->range; - table = range.table; - op_ctx->row ++; +void flecs_query_get_trav_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) +{ + if (cache->id != ecs_pair(trav, entity) || cache->up) { + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); + cache->id = ecs_pair(trav, entity); + cache->up = false; } +} -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); +void flecs_query_get_trav_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + + ecs_id_record_t *idr = cache->idr; + if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { + idr = cache->idr = flecs_id_record_get(world, + ecs_pair(trav, EcsWildcard)); + if (!idr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; } - return false; } - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - if (!tgt) { - op_ctx->row ++; - goto next_row; + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; } - it->ids[field_index] = ecs_pair(rel, tgt); + ecs_id_t id = table->type.array[tr->index]; - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); + if (cache->id != id || !cache->up) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); + cache->id = id; + cache->up = true; + } +} + +/** + * @file query/engine/trav_down_cache.c + * @brief Compile query term. + */ + + +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self, + bool empty); + +static +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self, + bool empty); + +static +ecs_trav_down_t* flecs_trav_down_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t entity) +{ + ecs_trav_down_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_down_t, entity); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); + ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); } - flecs_query_set_vars(op, it->ids[field_index], ctx); - return true; + return trav[0]; } static -bool flecs_query_union_with_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt, - bool neq) +ecs_trav_down_t* flecs_trav_table_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); - } + if (!table->_->traversable_count) { + return dst; + } - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t entity = entities[i]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (!record) { + continue; } - op_ctx->row = 0; - } else { - range = op_ctx->range; - table = range.table; - op_ctx->row ++; - } + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, entity)); + if (!idr_trav) { + continue; + } -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); + flecs_trav_entity_down(world, a, cache, dst, + trav, idr_trav, idr_with, self, empty); } - return false; } - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - bool match = e_tgt == tgt; - if (neq) { - match = !match; - } + return dst; +} - if (!match) { - op_ctx->row ++; - goto next_row; +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self, + bool empty) +{ + if (trav == EcsIsA || !world->idr_isa_wildcard) { + return; } - it->ids[field_index] = ecs_pair(rel, tgt); - - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); + ecs_id_record_t *idr_isa = flecs_id_record_get( + world, ecs_pair(EcsIsA, entity)); + if (!idr_isa) { + return; } - flecs_query_set_vars(op, it->ids[field_index], ctx); + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr_isa->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } - return true; -} + if (ecs_table_has_id(world, table, idr_with->id)) { + /* Table owns component */ + continue; + } -bool flecs_query_union_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool neq) -{ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *record = flecs_entities_get(world, e); + if (!record) { + continue; + } - if (tgt == EcsWildcard) { - return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); - } else { - return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, e)); + if (idr_trav) { + flecs_trav_entity_down(world, a, cache, dst, trav, + idr_trav, idr_with, self, empty); + } + + flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, + idr_with, self, empty); + } + } + } } } static -bool flecs_query_union_select_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt) +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } + int32_t first = ecs_vec_count(&dst->elems); - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); + ecs_table_cache_iter_t it; + bool result; + if (empty) { + result = flecs_table_cache_all_iter(&idr_trav->cache, &it); } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + result = flecs_table_cache_iter(&idr_trav->cache, &it); } - if (!op_ctx->cur) { - return false; - } + if (result) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + bool leaf = false; - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); + if (flecs_id_record_get_table(idr_with, table) != NULL) { + if (self) { + continue; + } + leaf = true; + } - it->ids[field_index] = ecs_pair(rel, tgt); + /* If record is not the first instance of (trav, *), don't add it + * to the cache. */ + int32_t index = tr->index; + if (index) { + ecs_id_t id = table->type.array[index - 1]; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { + int32_t col = ecs_search_relation(world, table, 0, + idr_with->id, trav, EcsUp, NULL, NULL, &tr); + ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); + + if (col != index) { + /* First relationship through which the id is + * reachable is not the current one, so skip. */ + continue; + } + } + } + + ecs_trav_down_elem_t *elem = ecs_vec_append_t( + a, &dst->elems, ecs_trav_down_elem_t); + elem->table = table; + elem->leaf = leaf; + } + } + + /* Breadth first walk */ + int32_t t, last = ecs_vec_count(&dst->elems); + for (t = first; t < last; t ++) { + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &dst->elems, ecs_trav_down_elem_t, t); + if (!elem->leaf) { + flecs_trav_table_down(world, a, cache, dst, trav, + elem->table, idr_with, self, empty); + } + } - return true; + return dst; } -static -bool flecs_query_union_select_wildcard( - const ecs_query_op_t *op, - bool redo, +ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel) + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t e, + ecs_id_record_t *idr_with, + bool self, + bool empty) { - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; + ecs_world_t *world = ctx->it->real_world; + ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravDown; - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); - op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); - op_ctx->tgt = 0; + ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); + if (result->ready) { + return result; } -next_tgt: - if (!op_ctx->tgt) { - if (!ecs_map_next(&op_ctx->tgt_iter)) { - return false; + ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); + if (!idr_trav) { + if (trav != EcsIsA) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, idr_with, self, empty); } - - op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); - op_ctx->cur = 0; - it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); + result->ready = true; + return result; } - if (!op_ctx->cur) { - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); - } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); - } + ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); - if (!op_ctx->cur) { - op_ctx->tgt = 0; - goto next_tgt; + /* Cover IsA -> trav paths. If a parent inherits a component, then children + * of that parent should find the component through up traversal. */ + if (idr_with->flags & EcsIdOnInstantiateInherit) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, idr_with, self, empty); } - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); + flecs_trav_entity_down( + world, a, cache, result, trav, idr_trav, idr_with, self, empty); + result->ready = true; - return true; + return result; } -bool flecs_query_union_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +void flecs_query_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - - if (tgt == EcsWildcard) { - return flecs_query_union_select_wildcard(op, redo, ctx, rel); - } else { - return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); + ecs_map_iter_t it = ecs_map_iter(&cache->src); + while (ecs_map_next(&it)) { + ecs_trav_down_t *t = ecs_map_ptr(&it); + ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); } + ecs_map_fini(&cache->src); } -bool flecs_query_union( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +/** + * @file query/engine/trav_up_cache.c + * @brief Compile query term. + */ + + +static +ecs_trav_up_t* flecs_trav_up_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + uint64_t table_id) { - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_union_with(op, redo, ctx, false); - } else { - return flecs_query_union_select(op, redo, ctx); + ecs_trav_up_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_up_t, table_id); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); } + + return trav[0]; } -bool flecs_query_union_neq( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - if (written & (1ull << op->src.var)) { - return flecs_query_union_with(op, redo, ctx, true); - } else { - return false; +static +int32_t flecs_trav_type_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + ecs_type_t *type) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); + if (tr) { + up->id = type->array[tr->index]; + up->tr = tr; + return tr->index; } + + return -1; } static -void flecs_query_union_set_shared( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx) +int32_t flecs_trav_type_offset_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + int32_t offset, + ecs_id_t with, + ecs_type_t *type) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_id_record_t *idr = op_ctx->idr_with; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); - idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + while (offset < type->count) { + ecs_id_t type_id = type->array[offset ++]; + if (ecs_id_match(type_id, with)) { + up->id = type_id; + up->tr = &table->_->records[offset - 1]; + return offset - 1; + } + } - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - ecs_entity_t src = it->sources[field_index]; - ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); - - it->ids[field_index] = ecs_pair(rel, tgt); + return -1; } -bool flecs_query_union_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +static +ecs_trav_up_t* flecs_trav_table_up( + const ecs_query_run_ctx_t *ctx, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + const ecs_world_t *world, + ecs_entity_t src, + ecs_id_t with, + ecs_id_t rel, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (!redo) { - if (!flecs_query_up_with(op, redo, ctx)) { - return false; + ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); + if (up->ready) { + return up; + } + + ecs_record_t *src_record = flecs_entities_get_any(world, src); + ecs_table_t *table = src_record->table; + if (!table) { + goto not_found; + } + + ecs_type_t type = table->type; + if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { + up->src = src; + goto found; + } + + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = idr_trav == world->idr_isa_wildcard; + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + goto not_found; + } + + if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { + goto not_found; + } + } + + ecs_trav_up_t up_pair = {0}; + int32_t r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; } - flecs_query_union_set_shared(op, ctx); - return true; - } else { - return false; + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } + + if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { + idr_trav = world->idr_isa_wildcard; + r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } } - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); } + +not_found: + up->tr = NULL; +found: + up->ready = true; + return up; } -bool flecs_query_union_self_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_trav_up_t* flecs_query_get_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + ecs_id_t with, + ecs_entity_t trav, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) { - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - if (redo) { - goto next_for_union; - } + if (cache->with && cache->with != with) { + flecs_query_up_cache_fini(cache); + } -next_for_self_up_with: - if (!flecs_query_self_up_with(op, redo, ctx, false)) { - return false; - } + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - if (it->sources[field_index]) { - flecs_query_union_set_shared(op, ctx); - return true; - } + ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravUp; + cache->with = with; -next_for_union: - if (!flecs_query_union_with(op, redo, ctx, false)) { - goto next_for_self_up_with; - } + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); + if (!tr) { + return NULL; /* Table doesn't have the relationship */ + } - return true; - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_id_t id = table->type.array[i]; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, + with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (result->src != 0) { + return result; + } } + + return NULL; +} + +void flecs_query_up_cache_fini( + ecs_trav_up_cache_t *cache) +{ + ecs_map_fini(&cache->src); } /** - * @file query/engine/eval.c - * @brief Query engine implementation. + * @file query/engine/trivial_iter.c + * @brief Iterator for trivial queries. */ -/* Find tables with requested component that has traversable entities. */ static -bool flecs_query_up_select_table( - const ecs_query_op_t *op, - bool redo, +bool flecs_query_trivial_search_init( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind, - ecs_query_up_select_kind_t kind) + ecs_query_trivial_ctx_t *op_ctx, + const ecs_query_t *query, + bool redo, + ecs_flags64_t term_set) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_iter_t *it = ctx->it; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_table_range_t range; - - do { - bool result; - if (kind == FlecsQueryUpSelectId) { - result = flecs_query_select_id(op, redo, ctx, 0); - } else if (kind == FlecsQueryUpSelectDefault) { - result = flecs_query_select_w_id(op, redo, ctx, - op_ctx->with, 0); - } else if (kind == FlecsQueryUpSelectUnion) { - result = flecs_query_union_select(op, redo, ctx); - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); + if (!redo) { + /* Find first trivial term*/ + int32_t t = 0; + if (term_set) { + for (; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } } - if (!result) { - /* No remaining tables with component found. */ + ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); + op_ctx->start_from = t; + + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); + if (!idr) { return false; } - redo = true; - - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + if (query->flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } - /* Keep searching until we find a table that has the requested component, - * with traversable entities */ - } while (!self && range.table->_->traversable_count == 0); + /* Find next term to evaluate once */ + + for (t = t + 1; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } - if (!range.count) { - range.count = ecs_table_count(range.table); + op_ctx->first_to_eval = t; } - op_ctx->table = range.table; - op_ctx->row = range.offset; - op_ctx->end = range.offset + range.count; - op_ctx->matched = it->ids[op->field_index]; - return true; } -/* Find next traversable entity in table. */ -static -ecs_trav_down_t* flecs_query_up_find_next_traversable( - const ecs_query_op_t *op, +bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind) + ecs_query_trivial_ctx_t *op_ctx, + bool redo, + ecs_flags64_t term_set) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_world_t *world = ctx->world; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; - const ecs_query_t *q = &ctx->query->pub; - ecs_table_t *table = op_ctx->table; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; + int32_t t, term_count = query->pub.term_count; - if (table->_->traversable_count == 0) { - /* No traversable entities in table */ - op_ctx->table = NULL; - return NULL; - } else { - int32_t row; - ecs_entity_t entity = 0; - const ecs_entity_t *entities = ecs_table_entities(table); + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { + return false; + } - for (row = op_ctx->row; row < op_ctx->end; row ++) { - entity = entities[row]; - ecs_record_t *record = flecs_entities_get(world, entity); - if (record->row & EcsEntityIsTraversable) { - /* Found traversable entity */ - it->sources[op->field_index] = entity; - break; - } + do { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; } - if (row == op_ctx->end) { - /* No traversable entities remaining in table */ - op_ctx->table = NULL; - return NULL; + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + continue; } - op_ctx->row = row; + for (t = op_ctx->first_to_eval; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; + } - /* Get down cache entry for traversable entity */ - bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; - op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, - op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); - op_ctx->cache_elem = -1; - } + const ecs_term_t *term = &terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); + if (!idr) { + break; + } - return op_ctx->down; + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + idr, table); + if (!tr_with) { + break; + } + + it->trs[term->field_index] = tr_with; + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->trs[op_ctx->start_from] = tr; + break; + } + } while (true); + + return true; } -/* Select all tables that can reach the target component through the traversal - * relationship. */ -bool flecs_query_up_select( - const ecs_query_op_t *op, - bool redo, +bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, - ecs_query_up_select_trav_kind_t trav_kind, - ecs_query_up_select_kind_t kind) + ecs_query_trivial_ctx_t *op_ctx, + bool redo) { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_id_t *ids = q->ids; ecs_iter_t *it = ctx->it; - bool redo_select = redo; - const ecs_query_t *q = &ctx->query->pub; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - - op_ctx->trav = q->terms[op->term_index].trav; + int32_t t, term_count = query->pub.term_count; - /* Reuse id record from previous iteration if possible*/ - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { + return false; } - /* 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)) +next: { - if (!self) { - /* If operation does not match owned components, return false */ + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { return false; - } else if (kind == FlecsQueryUpSelectId) { - return flecs_query_select_id(op, redo, ctx, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } else if (kind == FlecsQueryUpSelectDefault) { - return flecs_query_select(op, redo, ctx); - } else if (kind == FlecsQueryUpSelectUnion) { - return flecs_query_union_select(op, redo, ctx); - } else { - /* Invalid select kind */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); } - } - if (!redo) { - /* Get component id to match */ - op_ctx->with = flecs_query_op_get_id(op, ctx); + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + goto next; + } - /* Get id record for component to match */ - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - if (!op_ctx->idr_with) { - /* If id record does not exist, there can't be any results */ - return false; + for (t = 1; t < term_count; t ++) { + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); + if (!idr) { + return false; + } + + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + idr, table); + if (!tr_with) { + goto next; + } + + it->trs[t] = tr_with; } - op_ctx->down = NULL; - op_ctx->cache_elem = 0; + it->table = table; + it->count = ecs_table_count(table); + it->entities = ecs_table_entities(table); + it->trs[0] = tr; } - /* Get last used entry from down traversal cache. Cache entries in the down - * traversal cache contain a list of tables that can reach the requested - * component through the traversal relationship, for a traversable entity - * which acts as the key for the cache. */ - ecs_trav_down_t *down = op_ctx->down; + return true; +} -next_down_entry: - /* Get (next) entry in down traversal cache */ - while (!down) { - ecs_table_t *table = op_ctx->table; +bool flecs_query_trivial_test( + const ecs_query_run_ctx_t *ctx, + bool redo, + ecs_flags64_t term_set) +{ + if (redo) { + return false; + } else { + const ecs_query_impl_t *impl = ctx->query; + const ecs_query_t *q = &impl->pub; + const ecs_term_t *terms = q->terms; + ecs_iter_t *it = ctx->it; + int32_t t, term_count = impl->pub.term_count; - /* Get (next) table with traversable entities that have the - * requested component. We'll traverse downwards from the - * traversable entities in the table to find all entities that can - * reach the component through the traversal relationship. */ - if (!table) { - /* Reset source, in case we have to return a component matched - * by the entity in the found table. */ - it->sources[op->field_index] = 0; + ecs_table_t *table = it->table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); - if (!flecs_query_up_select_table( - op, redo_select, ctx, trav_kind, kind)) - { - return false; + for (t = 0; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; } - table = op_ctx->table; + const ecs_term_t *term = &terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); + if (!idr) { + return false; + } - /* If 'self' is true, we're evaluating a term with self|up. This - * means that before traversing downwards, we should also return - * the current table as result. */ - if (self) { - if (!flecs_query_table_filter(table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - flecs_reset_source_set_flag(it, op->field_index); - op_ctx->row --; - return true; - } + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; } - redo_select = true; - } else { - /* Evaluate next entity in table */ - op_ctx->row ++; + it->trs[term->field_index] = tr; } - /* Get down cache entry for next traversable entity in table */ - down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); - if (!down) { - goto next_down_entry; + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities = &it->entities[it->offset]; } - } -next_down_elem: - /* Get next element (table) in cache entry */ - if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { - /* No more elements in cache entry, find next.*/ - down = NULL; - goto next_down_entry; + return true; } +} - ecs_trav_down_elem_t *elem = ecs_vec_get_t( - &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); - flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); - flecs_query_set_vars(op, op_ctx->matched, ctx); +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - if (flecs_query_table_filter(elem->table, op->other, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) - { - /* Go to next table if table contains prefabs, disabled entities or - * entities that are not queryable. */ - goto next_down_elem; - } - flecs_set_source_set_flag(it, op->field_index); +#ifdef FLECS_SCRIPT + +#define flecs_expr_ast_new(parser, T, kind)\ + (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) + +static +void* flecs_expr_ast_new_( + ecs_script_parser_t *parser, + ecs_size_t size, + ecs_expr_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_expr_node_t *result = flecs_calloc(a, size); + result->kind = kind; + result->pos = parser->pos; + return result; +} + +ecs_expr_val_t* flecs_expr_bool( + ecs_script_parser_t *parser, + bool value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.bool_ = value; + result->ptr = &result->storage.bool_; + result->node.type = ecs_id(ecs_bool_t); + return result; +} + +ecs_expr_val_t* flecs_expr_int( + ecs_script_parser_t *parser, + int64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.i64 = value; + result->ptr = &result->storage.i64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_uint( + ecs_script_parser_t *parser, + uint64_t value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.u64 = value; + result->ptr = &result->storage.u64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_float( + ecs_script_parser_t *parser, + double value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.f64 = value; + result->ptr = &result->storage.f64; + result->node.type = ecs_id(ecs_f64_t); + return result; +} + +ecs_expr_val_t* flecs_expr_string( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_val_t *result = flecs_expr_ast_new( + parser, ecs_expr_val_t, EcsExprValue); + result->storage.string = value; + result->ptr = &result->storage.string; + result->node.type = ecs_id(ecs_string_t); + return result; +} + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_identifier_t *result = flecs_expr_ast_new( + parser, ecs_expr_identifier_t, EcsExprIdentifier); + result->value = value; + return result; +} - return true; +ecs_expr_variable_t* flecs_expr_variable( + ecs_script_parser_t *parser, + const char *value) +{ + ecs_expr_variable_t *result = flecs_expr_ast_new( + parser, ecs_expr_variable_t, EcsExprVariable); + result->value = value; + return result; } -/* Check if a table can reach the target component through the traversal - * relationship. */ -bool flecs_query_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) +ecs_expr_unary_t* flecs_expr_unary( + ecs_script_parser_t *parser) { - const ecs_query_t *q = &ctx->query->pub; - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_iter_t *it = ctx->it; + ecs_expr_unary_t *result = flecs_expr_ast_new( + parser, ecs_expr_unary_t, EcsExprUnary); + return result; +} - op_ctx->trav = q->terms[op->term_index].trav; - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); - } +ecs_expr_binary_t* flecs_expr_binary( + ecs_script_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprBinary); + return result; +} - if (!op_ctx->idr_trav || - !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)) - { - /* If there are no tables with traversable relationship, there are no - * matches. */ - return false; - } +ecs_expr_member_t* flecs_expr_member( + ecs_script_parser_t *parser) +{ + ecs_expr_member_t *result = flecs_expr_ast_new( + parser, ecs_expr_member_t, EcsExprMember); + return result; +} - if (!redo) { - op_ctx->trav = q->terms[op->term_index].trav; - op_ctx->with = flecs_query_op_get_id(op, ctx); - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); +ecs_expr_element_t* flecs_expr_element( + ecs_script_parser_t *parser) +{ + ecs_expr_element_t *result = flecs_expr_ast_new( + parser, ecs_expr_element_t, EcsExprElement); + return result; +} - /* If id record for component doesn't exist, there are no matches */ - if (!op_ctx->idr_with) { - return false; - } +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *expr, + ecs_entity_t type) +{ + 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; + result->node.pos = expr->pos; + result->node.type = type; + result->expr = expr; + return result; +} - /* Get the range (table) that is currently being evaluated. In most - * cases the range will cover the entire table, but in some cases it - * can only cover a subset of the entities in the table. */ - ecs_table_range_t range = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - if (!range.table) { - return false; - } +#endif - /* Get entry from up traversal cache. The up traversal cache contains - * the entity on which the component was found, with additional metadata - * on where it is stored. */ - ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &op_ctx->cache, - range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, - op_ctx->idr_trav); +/** + * @file addons/script/expr/parser.c + * @brief Script expression parser. + */ - if (!up) { - /* Component is not reachable from table */ - return false; - } - it->sources[op->field_index] = flecs_entities_get_alive( - ctx->world, up->src); - it->trs[op->field_index] = up->tr; - it->ids[op->field_index] = up->id; - flecs_query_set_vars(op, up->id, ctx); - flecs_set_source_set_flag(it, op->field_index); - return true; - } else { - /* The table either can or can't reach the component, nothing to do for - * a second evaluation of this operation.*/ - return false; - } -} +#ifdef FLECS_SCRIPT -/* Check if a table can reach the target component through the traversal - * relationship, or if the table has the target component itself. */ -bool flecs_query_self_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool id_only) -{ - if (!redo) { - bool result; +/* From https://en.cppreference.com/w/c/language/operator_precedence */ - if (id_only) { - /* Simple id, no wildcards */ - result = flecs_query_with_id(op, redo, ctx); - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - op_ctx->remaining = 1; - } else { - result = flecs_query_with(op, redo, ctx); - } +static int flecs_expr_precedence[] = { + [EcsTokParenOpen] = 1, + [EcsTokMember] = 1, + [EcsTokBracketOpen] = 1, + [EcsTokNot] = 2, + [EcsTokMul] = 3, + [EcsTokDiv] = 3, + [EcsTokMod] = 3, + [EcsTokAdd] = 4, + [EcsTokSub] = 4, + [EcsTokShiftLeft] = 5, + [EcsTokShiftRight] = 5, + [EcsTokGt] = 6, + [EcsTokGtEq] = 6, + [EcsTokLt] = 6, + [EcsTokLtEq] = 6, + [EcsTokEq] = 7, + [EcsTokNeq] = 7, + [EcsTokBitwiseAnd] = 8, + [EcsTokBitwiseOr] = 10, + [EcsTokAnd] = 11, + [EcsTokOr] = 12, +}; - flecs_reset_source_set_flag(ctx->it, op->field_index); +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); - if (result) { - /* Table has component, no need to traverse*/ - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - op_ctx->trav = 0; - if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { - /* Matching self, so set sources to 0 */ - ecs_iter_t *it = ctx->it; - it->sources[op->field_index] = 0; - } - return true; - } +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out); - /* Table doesn't have component, traverse relationship */ - return flecs_query_up_with(op, redo, ctx); - } else { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - if (op_ctx->trav == 0) { - /* If matching components without traversing, make sure to still - * match remaining components that match the id (wildcard). */ - return flecs_query_with(op, redo, ctx); - } +static +bool flecs_has_precedence( + ecs_script_token_kind_t first, + ecs_script_token_kind_t second) +{ + if (!flecs_expr_precedence[first]) { + return false; } - - return false; + return flecs_expr_precedence[first] < flecs_expr_precedence[second]; } -/** - * @file query/engine/eval_utils.c - * @brief Query engine evaluation utilities. - */ +static +const char* flecs_script_parse_rhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_expr_node_t *left, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + const char *last_pos = pos; + TokenFramePush(); -void flecs_query_set_iter_this( - ecs_iter_t *it, - const ecs_query_run_ctx_t *ctx) -{ - const ecs_var_t *var = &ctx->vars[0]; - const ecs_table_range_t *range = &var->range; - ecs_table_t *table = range->table; - int32_t count = range->count; - if (table) { - if (!count) { - count = ecs_table_count(table); - } - it->table = table; - it->offset = range->offset; - it->count = count; - it->entities = ecs_table_entities(table); - if (it->entities) { - it->entities += it->offset; - } - } else if (count == 1) { - it->count = 1; - it->entities = &ctx->vars[0].entity; - } -} + LookAhead( + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_script_token_kind_t oper = lookahead_token.kind; -ecs_query_op_ctx_t* flecs_op_ctx_( - const ecs_query_run_ctx_t *ctx) -{ - return &ctx->op_ctx[ctx->op_index]; -} + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } -#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + /* Consume lookahead token */ + pos = lookahead; -void flecs_reset_source_set_flag( - ecs_iter_t *it, - int32_t field_index) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); -} + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; -void flecs_set_source_set_flag( - ecs_iter_t *it, - int32_t field_index) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_SET(it->up_fields, 1u << field_index); -} + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } -ecs_table_range_t flecs_range_from_entity( - ecs_entity_t e, - const ecs_query_run_ctx_t *ctx) -{ - ecs_record_t *r = flecs_entities_get(ctx->world, e); - if (!r) { - return (ecs_table_range_t){ 0 }; - } - return (ecs_table_range_t){ - .table = r->table, - .offset = ECS_RECORD_TO_ROW(r->row), - .count = 1 - }; -} + Parse_1(']', { + *out = (ecs_expr_node_t*)result; + break; + }); -ecs_table_range_t flecs_query_var_get_range( - int32_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - ecs_table_t *table = var->range.table; - if (table) { - return var->range; - } + break; + } - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range; - } + case EcsTokMember: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; - return (ecs_table_range_t){ 0 }; -} + Parse_1(EcsTokIdentifier, { + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + break; + }); -ecs_table_t* flecs_query_var_get_table( - int32_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_var_t *var = &ctx->vars[var_id]; - ecs_table_t *table = var->range.table; - if (table) { - return table; - } + break; + } - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range.table; - } + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; - return NULL; -} + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + *out = (ecs_expr_node_t*)result; + break; + } + }; -ecs_table_t* flecs_query_get_table( - const ecs_query_op_t *op, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - if (flags & EcsQueryIsEntity) { - return ecs_get_table(ctx->world, ref->entity); - } else { - return flecs_query_var_get_table(ref->var, ctx); - } -} + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; -ecs_table_range_t flecs_query_get_range( - const ecs_query_op_t *op, - const ecs_query_ref_t *ref, - ecs_flags16_t ref_kind, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - if (flags & EcsQueryIsEntity) { - ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); - return flecs_range_from_entity(ref->entity, ctx); - } else { - ecs_var_t *var = &ctx->vars[ref->var]; - if (var->range.table) { - return ctx->vars[ref->var].range; - } else if (var->entity) { - return flecs_range_from_entity(var->entity, ctx); + break; } - } - return (ecs_table_range_t){0}; -} + ) -ecs_entity_t flecs_query_var_get_entity( - ecs_var_id_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - ecs_entity_t entity = var->entity; - if (entity) { - return entity; + if (pos[0] && (pos != last_pos)) { + pos = flecs_script_parse_rhs(parser, pos, tokenizer, *out, 0, out); } - ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = var->range.table; - const ecs_entity_t *entities = ecs_table_entities(table); - var->entity = entities[var->range.offset]; - return var->entity; -} + TokenFramePop(); -void flecs_query_var_reset( - ecs_var_id_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ctx->vars[var_id].entity = EcsWildcard; - ctx->vars[var_id].range.table = NULL; + return pos; +error: + return NULL; } -void flecs_query_var_set_range( - const ecs_query_op_t *op, - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - const ecs_query_run_ctx_t *ctx) +static +const char* flecs_script_parse_lhs( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_tokenizer_t *tokenizer, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) { - (void)op; - ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_query_is_written(var_id, op->written), - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - var->entity = 0; - var->range = (ecs_table_range_t){ - .table = table, - .offset = offset, - .count = count - }; -} - -void flecs_query_var_narrow_range( - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - const ecs_query_run_ctx_t *ctx) -{ - ecs_var_t *var = &ctx->vars[var_id]; - - var->entity = 0; - var->range = (ecs_table_range_t){ - .table = table, - .offset = offset, - .count = count - }; - - ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - if (ctx->query_vars[var_id].kind != EcsVarTable) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - var->entity = ecs_table_entities(table)[offset]; - } -} + TokenFramePush(); -void flecs_query_var_set_entity( - const ecs_query_op_t *op, - ecs_var_id_t var_id, - ecs_entity_t entity, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_query_is_written(var_id, op->written), - ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &ctx->vars[var_id]; - var->range.table = NULL; - var->entity = entity; -} + Parse( + case EcsTokNumber: { + const char *expr = Token(0); + if (strchr(expr, '.') || strchr(expr, 'e')) { + *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); + } else if (expr[0] == '-') { + *out = (ecs_expr_node_t*)flecs_expr_int(parser, atoll(expr)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, atoll(expr)); + } + break; + } -void flecs_query_set_vars( - const ecs_query_op_t *op, - ecs_id_t id, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + case EcsTokString: { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); + break; + } - if (flags_1st & EcsQueryIsVar) { - ecs_var_id_t var = op->first.var; - if (op->written & (1ull << var)) { - if (ECS_IS_PAIR(id)) { - flecs_query_var_set_entity( - op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + case EcsTokIdentifier: { + const char *expr = Token(0); + if (expr[0] == '$') { + *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); + } else if (!ecs_os_strcmp(expr, "true")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); + } else if (!ecs_os_strcmp(expr, "false")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { - flecs_query_var_set_entity(op, var, id, ctx); + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); } + break; } - } - if (flags_2nd & EcsQueryIsVar) { - ecs_var_id_t var = op->second.var; - if (op->written & (1ull << var)) { - flecs_query_var_set_entity( - op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + case EcsTokParenOpen: { + pos = flecs_script_parse_expr(parser, pos, 0, out); + Parse_1(EcsTokParenClose, { + break; + }) + break; } - } + + case EcsTokNot: { + ecs_expr_unary_t *unary = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &unary->expr); + unary->operator = EcsTokNot; + *out = (ecs_expr_node_t*)unary; + break; + } + ) + + TokenFramePop(); + + /* Parse right-hand side of expression if there is one */ + return flecs_script_parse_rhs( + parser, pos, tokenizer, *out, left_oper, out); +error: + return NULL; } -ecs_table_range_t flecs_get_ref_range( - const ecs_query_ref_t *ref, - ecs_flags16_t flag, - const ecs_query_run_ctx_t *ctx) +static +const char* flecs_script_parse_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_kind_t left_oper, + ecs_expr_node_t **out) { - if (flag & EcsQueryIsEntity) { - return flecs_range_from_entity(ref->entity, ctx); - } else if (flag & EcsQueryIsVar) { - return flecs_query_var_get_range(ref->var, ctx); - } - return (ecs_table_range_t){0}; + ParserBegin; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); + + EndOfRule; + + ParserEnd; } -ecs_entity_t flecs_get_ref_entity( - const ecs_query_ref_t *ref, - ecs_flags16_t flag, - const ecs_query_run_ctx_t *ctx) +ecs_expr_node_t* ecs_script_parse_expr( + ecs_world_t *world, + ecs_script_t *script, + const char *name, + const char *expr) { - if (flag & EcsQueryIsEntity) { - return ref->entity; - } else if (flag & EcsQueryIsVar) { - return flecs_query_var_get_entity(ref->var, ctx); + if (!script) { + script = flecs_script_new(world); } - return 0; + + ecs_script_parser_t parser = { + .script = flecs_script_impl(script), + .scope = flecs_script_impl(script)->root, + .significant_newline = false + }; + + ecs_script_impl_t *impl = flecs_script_impl(script); + + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc( + &impl->allocator, impl->token_buffer_size); + parser.token_cur = impl->token_buffer; + + ecs_expr_node_t *out = NULL; + + const char *result = flecs_script_parse_expr(&parser, expr, 0, &out); + if (!result) { + goto error; + } + + flecs_script_expr_visit_type(script, out); + flecs_script_expr_visit_fold(script, &out); + + return out; +error: + return NULL; } -ecs_id_t flecs_query_op_get_id_w_written( - const ecs_query_op_t *op, - uint64_t written, - const ecs_query_run_ctx_t *ctx) -{ - ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); - ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_entity_t first = 0, second = 0; +#endif - if (flags_1st) { - if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { - first = flecs_get_ref_entity(&op->first, flags_1st, ctx); - } else if (flags_1st & EcsQueryIsVar) { - first = EcsWildcard; - } +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ + + +#ifdef FLECS_SCRIPT + +#define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - if (flags_2nd) { - if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { - second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); - } else if (flags_2nd & EcsQueryIsVar) { - second = EcsWildcard; - } + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { - return ecs_pair(first, second); - } else { - return flecs_entities_get_alive(ctx->world, first); +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + flecs_expr_visit_error(script, left, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } -} -ecs_id_t flecs_query_op_get_id( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - return flecs_query_op_get_id_w_written(op, written, ctx); -} +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } -int16_t flecs_query_next_column( - ecs_table_t *table, - ecs_id_t id, - int32_t column) -{ - if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { - column = column + 1; - } else { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - column = ecs_search_offset(NULL, table, column + 1, id, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - return flecs_ito(int16_t, column); -} -void flecs_query_it_set_tr( - ecs_iter_t *it, - int32_t field_index, - const ecs_table_record_t *tr) -{ - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - it->trs[field_index] = tr; -} +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } -ecs_id_t flecs_query_it_set_id( - ecs_iter_t *it, - ecs_table_t *table, - int32_t field_index, - int32_t column) +int flecs_expr_unary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - return it->ids[field_index] = table->type.array[column]; -} + ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; -void flecs_query_set_match( - const ecs_query_op_t *op, - ecs_table_t *table, - int32_t column, - const ecs_query_run_ctx_t *ctx) -{ - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t field_index = op->field_index; - if (field_index == -1) { - return; + if (node->operator != EcsTokNot) { + flecs_expr_visit_error(script, node, + "operator invalid for unary expression"); + goto error; } - ecs_iter_t *it = ctx->it; - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = &table->_->records[column]; - flecs_query_it_set_tr(it, field_index, tr); - ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); - flecs_query_set_vars(op, matched, ctx); -} + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } -void flecs_query_set_trav_match( - const ecs_query_op_t *op, - const ecs_table_record_t *tr, - ecs_entity_t trav, - ecs_entity_t second, - const ecs_query_run_ctx_t *ctx) -{ - int32_t field_index = op->field_index; - if (field_index == -1) { - return; + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; } - ecs_iter_t *it = ctx->it; - ecs_id_t matched = ecs_pair(trav, second); - it->ids[op->field_index] = matched; - flecs_query_it_set_tr(it, op->field_index, tr); - flecs_query_set_vars(op, matched, ctx); -} + if (node->expr->type != ecs_id(ecs_bool_t)) { + char *type_str = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "! operator cannot be applied to value of type '%s' (must be bool)"); + ecs_os_free(type_str); + goto error; + } -bool flecs_query_table_filter( - ecs_table_t *table, - ecs_query_lbl_t other, - ecs_flags32_t filter_mask) -{ - uint32_t filter = flecs_ito(uint32_t, other); - return (table->flags & filter_mask & filter) != 0; -} + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = ecs_id(ecs_bool_t); + result->ptr = &result->storage.bool_; + *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); -/** - * @file query/engine/trav_cache.c - * @brief Cache that stores the result of graph traversal. - */ + *node_ptr = (ecs_expr_node_t*)result; + return 0; +error: + return -1; +} -static -void flecs_query_build_down_cache( - ecs_world_t *world, - ecs_allocator_t *a, - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); - if (!idr) { - return; + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; + + if (flecs_script_expr_visit_fold(script, &node->left)) { + goto error; } - ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - elem->entity = entity; - elem->idr = idr; + if (flecs_script_expr_visit_fold(script, &node->right)) { + goto error; + } - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = tr->hdr.table; - if (!table->_->traversable_count) { - continue; - } + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; + } - int32_t i, count = ecs_table_count(table); - const ecs_entity_t *entities = ecs_table_entities(table); - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - if (r->row & EcsEntityIsTraversable) { - flecs_query_build_down_cache( - world, a, ctx, cache, trav, entities[i]); - } - } - } + ecs_expr_val_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_val_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node->node.pos; + result->node.type = node->node.type; + + switch(node->operator) { + case EcsTokAdd: + ECS_BINARY_OP(node->left, node->right, result, +); + break; + case EcsTokSub: + ECS_BINARY_OP(node->left, node->right, result, -); + break; + case EcsTokMul: + ECS_BINARY_OP(node->left, node->right, result, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(node->left, node->right, result, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(node->left, node->right, result, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(node->left, node->right, result, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(node->left, node->right, result, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(node->left, node->right, result, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(node->left, node->right, result, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(node->left, node->right, result, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(node->left, node->right, result, ||); + break; + case EcsTokShiftLeft: + ECS_BINARY_UINT_OP(node->left, node->right, result, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_UINT_OP(node->left, node->right, result, >>); + break; + default: + flecs_expr_visit_error(script, node->left, "unsupported operator"); + goto error; } + + *node_ptr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; } -static -void flecs_query_build_up_cache( - ecs_world_t *world, - ecs_allocator_t *a, - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_table_t *table, - const ecs_table_record_t *tr, - int32_t root_column) +int flecs_expr_cast_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_id_t *ids = table->type.array; - int32_t i = tr->index, end = i + tr->count; - bool is_root = root_column == -1; - - for (; i < end; i ++) { - ecs_entity_t second = ecs_pair_second(world, ids[i]); - if (is_root) { - root_column = i; - } + ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - - el->entity = second; - el->tr = &table->_->records[i]; - el->idr = NULL; + if (flecs_script_expr_visit_fold(script, &node->expr)) { + goto error; + } - ecs_record_t *r = flecs_entities_get_any(world, second); - if (r->table) { - ecs_table_record_t *r_tr = flecs_id_record_get_table( - cache->idr, r->table); - if (!r_tr) { - return; - } - flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, - r_tr, root_column); - } + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; } -} -void flecs_query_trav_cache_fini( - ecs_allocator_t *a, - ecs_trav_cache_t *cache) -{ - ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); -} + ecs_expr_val_t *expr = (ecs_expr_val_t*)node->expr; + + /* Reuse existing node to hold casted value */ + *node_ptr = (ecs_expr_node_t*)expr; + + ecs_entity_t dst_type = node->node.type; + ecs_entity_t src_type = expr->node.type; -void flecs_query_get_trav_down_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) -{ - if (cache->id != ecs_pair(trav, entity) || cache->up) { - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); - cache->id = ecs_pair(trav, entity); - cache->up = false; + if (dst_type == src_type) { + /* No cast necessary if types are equal */ + return 0; } + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, expr->ptr); + ecs_value_t value = { + .type = src_type, + .ptr = expr->ptr + }; + + ecs_meta_set_value(&cur, &value); + + expr->node.type = dst_type; + + return 0; +error: + return -1; } -void flecs_query_get_trav_up_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_table_t *table) +int flecs_script_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; - ecs_id_record_t *idr = cache->idr; - if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { - idr = cache->idr = flecs_id_record_get(world, - ecs_pair(trav, EcsWildcard)); - if (!idr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_fold(script, node_ptr)) { + goto error; } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr)) { + goto error; + } + break; + case EcsExprIdentifier: + break; + case EcsExprVariable: + break; + case EcsExprFunction: + break; + case EcsExprMember: + break; + case EcsExprElement: + break; + case EcsExprCast: + if (flecs_expr_cast_visit_fold(script, node_ptr)) { + goto error; + } + break; } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; - } - - ecs_id_t id = table->type.array[tr->index]; - - if (cache->id != id || !cache->up) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); - cache->id = id; - cache->up = true; - } + return 0; +error: + return -1; } +#endif + /** - * @file query/engine/trav_down_cache.c - * @brief Compile query term. + * @file addons/script/expr_to_str.c + * @brief Script expression AST to string visitor. */ -static -void flecs_trav_entity_down_isa( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_entity_t entity, - ecs_id_record_t *idr_with, - bool self, - bool empty); +#ifdef FLECS_SCRIPT -static -ecs_trav_down_t* flecs_trav_entity_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, - bool self, - bool empty); +typedef struct ecs_expr_str_visitor_t { + const ecs_world_t *world; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; +} ecs_expr_str_visitor_t; -static -ecs_trav_down_t* flecs_trav_down_ensure( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_entity_t entity) +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node); + +int flecs_expr_value_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_val_t *node) { - ecs_trav_down_t **trav = ecs_map_ensure_ref( - &cache->src, ecs_trav_down_t, entity); - if (!trav[0]) { - trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); - ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); + return ecs_ptr_to_str_buf( + v->world, node->node.type, node->ptr, v->buf); +} + +int flecs_expr_unary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_unary_t *node) +{ + switch(node->operator) { + case EcsTokNot: ecs_strbuf_appendlit(v->buf, "!"); break; + default: + ecs_err("invalid operator for unary expression"); + return -1; + }; + + if (flecs_expr_node_to_str(v, node->expr)) { + goto error; } - return trav[0]; + return 0; +error: + return -1; } -static -ecs_trav_down_t* flecs_trav_table_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - const ecs_table_t *table, - ecs_id_record_t *idr_with, - bool self, - bool empty) +int flecs_expr_binary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_binary_t *node) { - ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(v->buf, "("); - if (!table->_->traversable_count) { - return dst; + if (flecs_expr_node_to_str(v, node->left)) { + goto error; } - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(v->buf, " "); - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_entity_t entity = entities[i]; - ecs_record_t *record = flecs_entities_get(world, entity); - if (!record) { - continue; - } + switch(node->operator) { + case EcsTokAdd: ecs_strbuf_appendlit(v->buf, "+"); break; + case EcsTokSub: ecs_strbuf_appendlit(v->buf, "-"); break; + case EcsTokMul: ecs_strbuf_appendlit(v->buf, "*"); break; + case EcsTokDiv: ecs_strbuf_appendlit(v->buf, "/"); break; + case EcsTokMod: ecs_strbuf_appendlit(v->buf, "%%"); break; + case EcsTokBitwiseOr: ecs_strbuf_appendlit(v->buf, "|"); break; + case EcsTokBitwiseAnd: ecs_strbuf_appendlit(v->buf, "&"); break; + case EcsTokEq: ecs_strbuf_appendlit(v->buf, "=="); break; + case EcsTokNeq: ecs_strbuf_appendlit(v->buf, "!="); break; + case EcsTokGt: ecs_strbuf_appendlit(v->buf, ">"); break; + case EcsTokGtEq: ecs_strbuf_appendlit(v->buf, ">="); break; + case EcsTokLt: ecs_strbuf_appendlit(v->buf, "<"); break; + case EcsTokLtEq: ecs_strbuf_appendlit(v->buf, "<="); break; + case EcsTokAnd: ecs_strbuf_appendlit(v->buf, "&&"); break; + case EcsTokOr: ecs_strbuf_appendlit(v->buf, "||"); break; + case EcsTokShiftLeft: ecs_strbuf_appendlit(v->buf, "<<"); break; + case EcsTokShiftRight: ecs_strbuf_appendlit(v->buf, ">>"); break; + default: + ecs_err("invalid operator for binary expression"); + return -1; + }; - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, entity)); - if (!idr_trav) { - continue; - } + ecs_strbuf_appendlit(v->buf, " "); - flecs_trav_entity_down(world, a, cache, dst, - trav, idr_trav, idr_with, self, empty); - } + if (flecs_expr_node_to_str(v, node->right)) { + goto error; + } + + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; +error: + return -1; +} + +int flecs_expr_identifier_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_identifier_t *node) +{ + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_variable_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_variable_t *node) +{ + ecs_strbuf_appendlit(v->buf, "$"); + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} + +int flecs_expr_member_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_member_t *node) +{ + if (flecs_expr_node_to_str(v, node->left)) { + return -1; } - return dst; + ecs_strbuf_appendlit(v->buf, "."); + ecs_strbuf_appendstr(v->buf, node->member_name); + return 0; } -static -void flecs_trav_entity_down_isa( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_entity_t entity, - ecs_id_record_t *idr_with, - bool self, - bool empty) +int flecs_expr_element_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_element_t *node) { - if (trav == EcsIsA || !world->idr_isa_wildcard) { - return; + if (flecs_expr_node_to_str(v, node->left)) { + return -1; } - ecs_id_record_t *idr_isa = flecs_id_record_get( - world, ecs_pair(EcsIsA, entity)); - if (!idr_isa) { - return; + ecs_strbuf_appendlit(v->buf, "["); + if (flecs_expr_node_to_str(v, node->index)) { + return -1; } + ecs_strbuf_appendlit(v->buf, "]"); + return 0; +} - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr_isa->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (!table->_->traversable_count) { - continue; - } - - if (ecs_table_has_id(world, table, idr_with->id)) { - /* Table owns component */ - continue; - } +int flecs_expr_cast_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_cast_t *node) +{ + return flecs_expr_node_to_str(v, node->expr); +} - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_entity_t e = entities[i]; - ecs_record_t *record = flecs_entities_get(world, e); - if (!record) { - continue; - } +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, e)); - if (idr_trav) { - flecs_trav_entity_down(world, a, cache, dst, trav, - idr_trav, idr_with, self, empty); - } + if (node->type) { + ecs_strbuf_append(v->buf, "%s", ECS_BLUE); + ecs_strbuf_appendstr(v->buf, ecs_get_name(v->world, node->type)); + ecs_strbuf_append(v->buf, "%s(", ECS_NORMAL); + } - flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, - idr_with, self, empty); - } - } + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_to_str(v, (ecs_expr_val_t*)node)) { + goto error; + } + break; + case EcsExprUnary: + if (flecs_expr_unary_to_str(v, (ecs_expr_unary_t*)node)) { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_to_str(v, (ecs_expr_binary_t*)node)) { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_to_str(v, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_to_str(v, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_to_str(v, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_to_str(v, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + if (flecs_expr_cast_to_str(v, (ecs_expr_cast_t*)node)) { + goto error; } + break; } -} -static -ecs_trav_down_t* flecs_trav_entity_down( - ecs_world_t *world, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - ecs_trav_down_t *dst, - ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, - bool self, - bool empty) -{ - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); + if (node->type) { + ecs_strbuf_append(v->buf, ")"); + } - int32_t first = ecs_vec_count(&dst->elems); + return 0; +error: + return -1; +} - ecs_table_cache_iter_t it; - bool result; - if (empty) { - result = flecs_table_cache_all_iter(&idr_trav->cache, &it); - } else { - result = flecs_table_cache_iter(&idr_trav->cache, &it); +FLECS_API +char* ecs_script_expr_to_str( + const ecs_world_t *world, + const ecs_expr_node_t *expr) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_expr_str_visitor_t v = { .world = world, .buf = &buf }; + if (flecs_expr_node_to_str(&v, expr)) { + ecs_strbuf_reset(&buf); + return NULL; } - if (result) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = tr->hdr.table; - bool leaf = false; + return ecs_strbuf_get(&buf); +} - if (flecs_id_record_get_table(idr_with, table) != NULL) { - if (self) { - continue; - } - leaf = true; - } +#endif - /* If record is not the first instance of (trav, *), don't add it - * to the cache. */ - int32_t index = tr->index; - if (index) { - ecs_id_t id = table->type.array[index - 1]; - if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { - int32_t col = ecs_search_relation(world, table, 0, - idr_with->id, trav, EcsUp, NULL, NULL, &tr); - ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - if (col != index) { - /* First relationship through which the id is - * reachable is not the current one, so skip. */ - continue; - } - } - } - ecs_trav_down_elem_t *elem = ecs_vec_append_t( - a, &dst->elems, ecs_trav_down_elem_t); - elem->table = table; - elem->leaf = leaf; - } +#ifdef FLECS_SCRIPT + +static +bool flecs_expr_operator_is_equality( + ecs_script_token_kind_t op) +{ + switch(op) { + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return true; + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } +error: + return false; +} + +static +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 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 true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} - /* Breadth first walk */ - int32_t t, last = ecs_vec_count(&dst->elems); - for (t = first; t < last; t ++) { - ecs_trav_down_elem_t *elem = ecs_vec_get_t( - &dst->elems, ecs_trav_down_elem_t, t); - if (!elem->leaf) { - flecs_trav_table_down(world, a, cache, dst, trav, - elem->table, idr_with, self, empty); - } +static +ecs_entity_t flecs_expr_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } - - return dst; +error: + return 0; } -ecs_trav_down_t* flecs_query_get_down_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t e, - ecs_id_record_t *idr_with, - bool self, - bool empty) +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_expr_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) { - ecs_world_t *world = ctx->it->real_world; - ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); - cache->dir = EcsTravDown; - - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_map_init_if(&cache->src, a); - - ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); - if (result->ready) { - return result; + if (type == ecs_id(ecs_u64_t)) { + return promote_to; } - - ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); - if (!idr_trav) { - if (trav != EcsIsA) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); - } - result->ready = true; - return result; + if (promote_to == ecs_id(ecs_u64_t)) { + return type; } - - ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); - - /* Cover IsA -> trav paths. If a parent inherits a component, then children - * of that parent should find the component through up traversal. */ - if (idr_with->flags & EcsIdOnInstantiateInherit) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); + if (type == ecs_id(ecs_f64_t)) { + return type; } - - flecs_trav_entity_down( - world, a, cache, result, trav, idr_trav, idr_with, self, empty); - result->ready = true; - - return result; + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); } -void flecs_query_down_cache_fini( - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache) +static +bool flecs_expr_oper_valid_for_type( + ecs_entity_t type, + ecs_script_token_kind_t op) { - ecs_map_iter_t it = ecs_map_iter(&cache->src); - while (ecs_map_next(&it)) { - ecs_trav_down_t *t = ecs_map_ptr(&it); - ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); + switch(op) { + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + return flecs_expr_is_type_number(type); + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + return type == ecs_id(ecs_u64_t) || + type == ecs_id(ecs_u32_t) || + type == ecs_id(ecs_u16_t) || + type == ecs_id(ecs_u8_t); + case EcsTokEq: + case EcsTokNeq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return flecs_expr_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - ecs_map_fini(&cache->src); } -/** - * @file query/engine/trav_up_cache.c - * @brief Compile query term. - */ - - static -ecs_trav_up_t* flecs_trav_up_ensure( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - uint64_t table_id) +int flecs_expr_type_for_oper( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type) { - ecs_trav_up_t **trav = ecs_map_ensure_ref( - &cache->src, ecs_trav_up_t, table_id); - if (!trav[0]) { - trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); + ecs_world_t *world = script->world; + ecs_expr_node_t *left = node->left, *right = node->right; + + switch(node->operator) { + case EcsTokDiv: + /* Result type of a division is always a float */ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); + return 0; + case EcsTokAnd: + case EcsTokOr: + /* Result type of a condition operator is always a bool */ + *operand_type = ecs_id(ecs_bool_t); + *result_type = ecs_id(ecs_bool_t); + return 0; + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + *result_type = ecs_id(ecs_bool_t); + break; + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } - return trav[0]; -} + 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) { + 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)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } -static -int32_t flecs_trav_type_search( - ecs_trav_up_t *up, - const ecs_table_t *table, - ecs_id_record_t *idr_with, - ecs_type_t *type) -{ - ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); - if (tr) { - up->id = type->array[tr->index]; - up->tr = tr; - return tr->index; + ecs_entity_t ltype = flecs_expr_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_expr_largest_type(rtype_ptr); + if (ltype == rtype) { + *operand_type = ltype; + goto done; } - return -1; -} + if (flecs_expr_operator_is_equality(node->operator)) { + char *lname = ecs_id_str(world, ltype); + char *rname = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "mismatching types in equality expression (%s vs %s)", + lname, rname); + ecs_os_free(rname); + ecs_os_free(lname); + return 0; + } -static -int32_t flecs_trav_type_offset_search( - ecs_trav_up_t *up, - const ecs_table_t *table, - int32_t offset, - ecs_id_t with, - ecs_type_t *type) -{ - ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); + if (!flecs_expr_is_type_number(ltype) || !flecs_expr_is_type_number(rtype)) { + flecs_expr_visit_error(script, node, + "incompatible types in binary expression"); + return 0; + } - while (offset < type->count) { - ecs_id_t type_id = type->array[offset ++]; - if (ecs_id_match(type_id, with)) { - up->id = type_id; - up->tr = &table->_->records[offset - 1]; - return offset - 1; - } + *operand_type = flecs_expr_promote_type(ltype, rtype); + +done: + if (node->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); + } + + if (!*result_type) { + *result_type = *operand_type; } + return 0; +error: return -1; } static -ecs_trav_up_t* flecs_trav_table_up( - const ecs_query_run_ctx_t *ctx, - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache, - const ecs_world_t *world, - ecs_entity_t src, - ecs_id_t with, - ecs_id_t rel, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) +int flecs_expr_unary_visit_type( + ecs_script_t *script, + ecs_expr_unary_t *node) { - ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); - if (up->ready) { - return up; + if (flecs_script_expr_visit_type(script, node->expr)) { + goto error; } - ecs_record_t *src_record = flecs_entities_get_any(world, src); - ecs_table_t *table = src_record->table; - if (!table) { - goto not_found; - } + /* The only supported unary expression is not (!) which returns a bool */ + node->node.type = ecs_id(ecs_bool_t); - ecs_type_t type = table->type; - if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { - up->src = src; - goto found; + if (node->expr->type != ecs_id(ecs_bool_t)) { + node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); } - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && rel) { - bool is_a = idr_trav == world->idr_isa_wildcard; - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - goto not_found; - } - - if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { - goto not_found; - } - } - - ecs_trav_up_t up_pair = {0}; - int32_t r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); - - while (r_column != -1) { - ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, - world, tgt, with, rel, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } - - r_column = flecs_trav_type_offset_search( - &up_pair, table, r_column + 1, rel, &type); - } - - if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { - idr_trav = world->idr_isa_wildcard; - r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); + return 0; +error: + return -1; +} - while (r_column != -1) { - ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); +static +int flecs_expr_binary_visit_type( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + /* Operands must be of this type or casted to it */ + ecs_entity_t operand_type = 0; - ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, - world, tgt, with, rel, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } + /* Resulting type of binary expression */ + ecs_entity_t result_type = 0; - r_column = flecs_trav_type_offset_search( - &up_pair, table, r_column + 1, rel, &type); - } - } + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; } -not_found: - up->tr = NULL; -found: - up->ready = true; - return up; -} - -ecs_trav_up_t* flecs_query_get_up_cache( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - ecs_table_t *table, - ecs_id_t with, - ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) -{ - if (cache->with && cache->with != with) { - flecs_query_up_cache_fini(cache); + if (flecs_script_expr_visit_type(script, node->right)) { + goto error; } - ecs_world_t *world = ctx->it->real_world; - ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_map_init_if(&cache->src, a); + if (flecs_expr_type_for_oper(script, node, &operand_type, &result_type)) { + goto error; + } - ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); - cache->dir = EcsTravUp; - cache->with = with; + if (!flecs_expr_oper_valid_for_type(result_type, node->operator)) { + flecs_expr_visit_error(script, node, "invalid operator for type"); + goto error; + } - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); - if (!tr) { - return NULL; /* Table doesn't have the relationship */ + if (operand_type != node->left->type) { + node->left = (ecs_expr_node_t*)flecs_expr_cast( + script, node->left, operand_type); } - int32_t i = tr->index, end = i + tr->count; - for (; i < end; i ++) { - ecs_id_t id = table->type.array[i]; - ecs_entity_t tgt = ECS_PAIR_SECOND(id); - ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, - with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - if (result->src != 0) { - return result; - } + if (operand_type != node->right->type) { + node->right = (ecs_expr_node_t*)flecs_expr_cast( + script, node->right, operand_type); } - return NULL; -} + node->node.type = result_type; -void flecs_query_up_cache_fini( - ecs_trav_up_cache_t *cache) -{ - ecs_map_fini(&cache->src); + return 0; +error: + return -1; } -/** - * @file query/engine/trivial_iter.c - * @brief Iterator for trivial queries. - */ - - static -bool flecs_query_trivial_search_init( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - const ecs_query_t *query, - bool redo, - ecs_flags64_t term_set) +int flecs_expr_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node) { - if (!redo) { - /* Find first trivial term*/ - int32_t t = 0; - if (term_set) { - for (; t < query->term_count; t ++) { - if (term_set & (1llu << t)) { - break; - } - } - } - - ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); - op_ctx->start_from = t; - - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); - if (!idr) { - return false; - } - - if (query->flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } - - /* Find next term to evaluate once */ - - for (t = t + 1; t < query->term_count; t ++) { - if (term_set & (1llu << t)) { - break; - } - } - - op_ctx->first_to_eval = t; + node->node.type = ecs_id(ecs_entity_t); + node->id = ecs_lookup(script->world, node->value); + if (!node->id) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; } - return true; + return 0; +error: + return -1; } -bool flecs_query_trivial_search( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - bool redo, - ecs_flags64_t term_set) -{ - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - const ecs_term_t *terms = q->terms; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = query->pub.term_count; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { - return false; - } - - do { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } +static +int flecs_expr_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node) +{ + node->node.type = ecs_id(ecs_entity_t); + return 0; +} - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - continue; - } +static +int flecs_expr_member_visit_type( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; + } - for (t = op_ctx->first_to_eval; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); - if (!idr) { - break; - } + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); - if (!tr_with) { - break; - } + if (type->kind != EcsStructType) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on non-struct type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } - it->trs[term->field_index] = tr_with; - } + ecs_meta_cursor_t cur = ecs_meta_cursor(world, left_type, NULL); + ecs_meta_push(&cur); /* { */ + int prev_log = ecs_log_set_level(-4); + if (ecs_meta_dotmember(&cur, node->member_name)) { + ecs_log_set_level(prev_log); + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "unresolved member '%s' for type '%s'", + node->member_name, type_str); + ecs_os_free(type_str); + goto error; + } + ecs_log_set_level(prev_log); - if (t == term_count) { - ctx->vars[0].range.table = table; - ctx->vars[0].range.count = 0; - ctx->vars[0].range.offset = 0; - it->trs[op_ctx->start_from] = tr; - break; - } - } while (true); + node->node.type = ecs_meta_get_type(&cur); + ecs_meta_pop(&cur); /* } */ - return true; + return 0; +error: + return -1; } -bool flecs_query_is_trivial_search( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - bool redo) +static +int flecs_expr_element_visit_type( + ecs_script_t *script, + ecs_expr_element_t *node) { - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - const ecs_id_t *ids = q->ids; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = query->pub.term_count; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { - return false; + if (flecs_script_expr_visit_type(script, node->left)) { + goto error; } -next: - { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } - - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - goto next; - } + if (flecs_script_expr_visit_type(script, node->index)) { + goto error; + } - for (t = 1; t < term_count; t ++) { - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); - if (!idr) { - return false; - } + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot use [] on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; + } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); - if (!tr_with) { - goto next; - } + bool is_entity_type = false; - it->trs[t] = tr_with; + if (type->kind == EcsPrimitiveType) { + const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); + if (ptype->kind == EcsEntity) { + is_entity_type = true; } + } - it->table = table; - it->count = ecs_table_count(table); - it->entities = ecs_table_entities(table); - it->trs[0] = tr; + if (is_entity_type) { + if (node->index->kind == EcsExprIdentifier) { + node->node.type = ((ecs_expr_identifier_t*)node->index)->id; + } else { + flecs_expr_visit_error(script, node, + "invalid component expression"); + goto error; + } + } else if (type->kind == EcsArrayType) { + const EcsArray *type_array = ecs_get(world, left_type, EcsArray); + ecs_assert(type_array != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_array->type; + } else if (type->kind == EcsVectorType) { + const EcsVector *type_vector = ecs_get(world, left_type, EcsVector); + ecs_assert(type_vector != NULL, ECS_INTERNAL_ERROR, NULL); + node->node.type = type_vector->type; + } else { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); + goto error; } - return true; + return 0; +error: + return -1; } -bool flecs_query_trivial_test( - const ecs_query_run_ctx_t *ctx, - bool redo, - ecs_flags64_t term_set) +int flecs_script_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node) { - if (redo) { - return false; - } else { - const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; - const ecs_term_t *terms = q->terms; - ecs_iter_t *it = ctx->it; - int32_t t, term_count = impl->pub.term_count; - - ecs_table_t *table = it->table; - ecs_assert(table != NULL, ECS_INVALID_OPERATION, - "the variable set on the iterator is missing a table"); - - for (t = 0; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } - - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); - if (!idr) { - return false; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - it->trs[term->field_index] = tr; + switch(node->kind) { + case EcsExprValue: + /* Value types are assigned by the AST */ + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_type(script, (ecs_expr_unary_t*)node)) { + goto error; } - - it->entities = ecs_table_entities(table); - if (it->entities) { - it->entities = &it->entities[it->offset]; + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_type(script, (ecs_expr_binary_t*)node)) { + goto error; } - - return true; + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_visit_type(script, (ecs_expr_identifier_t*)node)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_type(script, (ecs_expr_variable_t*)node)) { + goto error; + } + break; + case EcsExprFunction: + break; + case EcsExprMember: + if (flecs_expr_member_visit_type(script, (ecs_expr_member_t*)node)) { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_visit_type(script, (ecs_expr_element_t*)node)) { + goto error; + } + break; + case EcsExprCast: + break; } + + return 0; +error: + return -1; } +#endif + diff --git a/src/addons/script/expr_ast.c b/src/addons/script/expr/ast.c similarity index 99% rename from src/addons/script/expr_ast.c rename to src/addons/script/expr/ast.c index 51680cbd6..aba0b1b38 100644 --- a/src/addons/script/expr_ast.c +++ b/src/addons/script/expr/ast.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" #define flecs_expr_ast_new(parser, T, kind)\ (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) diff --git a/src/addons/script/expr_ast.h b/src/addons/script/expr/ast.h similarity index 100% rename from src/addons/script/expr_ast.h rename to src/addons/script/expr/ast.h diff --git a/src/addons/script/new_expr.c b/src/addons/script/expr/parser.c similarity index 98% rename from src/addons/script/new_expr.c rename to src/addons/script/expr/parser.c index 571080828..e1bbb4525 100644 --- a/src/addons/script/new_expr.c +++ b/src/addons/script/expr/parser.c @@ -1,13 +1,13 @@ /** - * @file addons/script/expr.c + * @file addons/script/expr/parser.c * @brief Script expression parser. */ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" -#include "parser.h" +#include "../script.h" +#include "../parser.h" /* From https://en.cppreference.com/w/c/language/operator_precedence */ diff --git a/src/addons/script/expr_visit.h b/src/addons/script/expr/visit.h similarity index 100% rename from src/addons/script/expr_visit.h rename to src/addons/script/expr/visit.h diff --git a/src/addons/script/expr_visit_fold.c b/src/addons/script/expr/visit_fold.c similarity index 97% rename from src/addons/script/expr_visit_fold.c rename to src/addons/script/expr/visit_fold.c index f98b0be7d..9f416455b 100644 --- a/src/addons/script/expr_visit_fold.c +++ b/src/addons/script/expr/visit_fold.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" #define ECS_VALUE_GET(value, T) (*(T*)((ecs_expr_val_t*)value)->ptr) @@ -103,7 +103,7 @@ int flecs_expr_unary_visit_fold( return 0; } - if (node->node.type != ecs_id(ecs_bool_t)) { + if (node->expr->type != ecs_id(ecs_bool_t)) { char *type_str = ecs_get_path(script->world, node->node.type); flecs_expr_visit_error(script, node, "! operator cannot be applied to value of type '%s' (must be bool)"); @@ -117,7 +117,9 @@ int flecs_expr_unary_visit_fold( result->node.pos = node->node.pos; result->node.type = ecs_id(ecs_bool_t); result->ptr = &result->storage.bool_; - *(bool*)result->ptr = !(*(bool*)((ecs_expr_val_t*)node)->ptr); + *(bool*)result->ptr = !*(bool*)(((ecs_expr_val_t*)node->expr)->ptr); + + *node_ptr = (ecs_expr_node_t*)result; return 0; error: diff --git a/src/addons/script/expr_to_str.c b/src/addons/script/expr/visit_to_str.c similarity index 99% rename from src/addons/script/expr_to_str.c rename to src/addons/script/expr/visit_to_str.c index a91fa616a..1f16c79f4 100644 --- a/src/addons/script/expr_to_str.c +++ b/src/addons/script/expr/visit_to_str.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" typedef struct ecs_expr_str_visitor_t { const ecs_world_t *world; diff --git a/src/addons/script/expr_visit_type.c b/src/addons/script/expr/visit_type.c similarity index 98% rename from src/addons/script/expr_visit_type.c rename to src/addons/script/expr/visit_type.c index cf610593f..4352915a7 100644 --- a/src/addons/script/expr_visit_type.c +++ b/src/addons/script/expr/visit_type.c @@ -6,7 +6,7 @@ #include "flecs.h" #ifdef FLECS_SCRIPT -#include "script.h" +#include "../script.h" static bool flecs_expr_operator_is_equality( @@ -249,6 +249,10 @@ int flecs_expr_unary_visit_type( /* The only supported unary expression is not (!) which returns a bool */ node->node.type = ecs_id(ecs_bool_t); + if (node->expr->type != ecs_id(ecs_bool_t)) { + node->expr = flecs_expr_cast(script, node->expr, ecs_id(ecs_bool_t)); + } + return 0; error: return -1; diff --git a/src/addons/script/script.h b/src/addons/script/script.h index 805344002..a9b6416e9 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -45,8 +45,8 @@ struct ecs_script_parser_t { }; #include "ast.h" -#include "expr_ast.h" -#include "expr_visit.h" +#include "expr/ast.h" +#include "expr/visit.h" #include "visit.h" #include "visit_eval.h"