/** * @file addons/rules/api.c * @brief User facing API for rules. */ #include "rules.h" #include #ifdef FLECS_RULES static ecs_mixins_t ecs_rule_t_mixins = { .type_name = "ecs_rule_t", .elems = { [EcsMixinWorld] = offsetof(ecs_rule_t, filter.world), [EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity), [EcsMixinIterable] = offsetof(ecs_rule_t, iterable), [EcsMixinDtor] = offsetof(ecs_rule_t, dtor) } }; static const char* flecs_rule_op_str( uint16_t kind) { switch(kind) { case EcsRuleAnd: return "and "; case EcsRuleAndId: return "and_id "; case EcsRuleAndAny: return "andany "; case EcsRuleWith: return "with "; case EcsRuleTrav: return "trav "; case EcsRuleIdsRight: return "idsr "; case EcsRuleIdsLeft: return "idsl "; case EcsRuleEach: return "each "; case EcsRuleStore: return "store "; case EcsRuleReset: return "reset "; case EcsRuleUnion: return "union "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; case EcsRulePredEq: return "eq "; case EcsRulePredNeq: return "neq "; case EcsRulePredEqName: return "eq_nm "; case EcsRulePredNeqName: return "neq_nm "; case EcsRulePredEqMatch: return "eq_m "; case EcsRulePredNeqMatch: return "neq_m "; case EcsRuleLookup: return "lookup "; case EcsRuleSetVars: return "setvars "; case EcsRuleSetThis: return "setthis "; case EcsRuleSetFixed: return "setfix "; case EcsRuleSetIds: return "setids "; case EcsRuleContain: return "contain "; case EcsRulePairEq: return "pair_eq "; case EcsRuleSetCond: return "setcond "; case EcsRuleJmpCondFalse: return "jfalse "; case EcsRuleJmpNotSet: return "jnotset "; case EcsRuleYield: return "yield "; case EcsRuleNothing: return "nothing "; default: return "!invalid"; } } /* Implementation for iterable mixin */ static void flecs_rule_iter_mixin_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_rule_t); if (filter) { iter[1] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); } } static void flecs_rule_fini( ecs_rule_t *rule) { if (rule->vars != &rule->vars_cache.var) { ecs_os_free(rule->vars); } ecs_os_free(rule->ops); ecs_os_free(rule->src_vars); flecs_name_index_fini(&rule->tvar_index); flecs_name_index_fini(&rule->evar_index); ecs_filter_fini(&rule->filter); ecs_poly_free(rule, ecs_rule_t); } void ecs_rule_fini( ecs_rule_t *rule) { if (rule->filter.entity) { /* If filter is associated with entity, use poly dtor path */ ecs_delete(rule->filter.world, rule->filter.entity); } else { flecs_rule_fini(rule); } } ecs_rule_t* ecs_rule_init( ecs_world_t *world, const ecs_filter_desc_t *const_desc) { ecs_rule_t *result = ecs_poly_new(ecs_rule_t); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Initialize the query */ ecs_filter_desc_t desc = *const_desc; desc.storage = &result->filter; /* Use storage of rule */ result->filter = ECS_FILTER_INIT; if (ecs_filter_init(world, &desc) == NULL) { goto error; } result->iterable.init = flecs_rule_iter_mixin_init; /* Compile filter to operations */ if (flecs_rule_compile(world, stage, result)) { goto error; } ecs_entity_t entity = const_desc->entity; result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; if (entity) { EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); poly->poly = result; ecs_poly_modified(world, entity, ecs_rule_t); } return result; error: ecs_rule_fini(result); return NULL; } static int32_t flecs_rule_op_ref_str( const ecs_rule_t *rule, ecs_rule_ref_t *ref, ecs_flags16_t flags, ecs_strbuf_t *buf) { int32_t color_chars = 0; if (flags & EcsRuleIsVar) { ecs_assert(ref->var < rule->var_count, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *var = &rule->vars[ref->var]; ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, '['); } ecs_strbuf_appendlit(buf, "#[green]"); if (var->name) { ecs_strbuf_appendstr(buf, var->name); } else { if (var->id) { #ifdef FLECS_DEBUG if (var->label) { ecs_strbuf_appendstr(buf, var->label); ecs_strbuf_appendch(buf, '\''); } #endif ecs_strbuf_append(buf, "%d", var->id); } else { ecs_strbuf_appendlit(buf, "this"); } } ecs_strbuf_appendlit(buf, "#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, ']'); } color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); } else if (flags & EcsRuleIsEntity) { char *path = ecs_get_fullpath(rule->filter.world, ref->entity); ecs_strbuf_appendlit(buf, "#[blue]"); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "#[reset]"); ecs_os_free(path); color_chars = ecs_os_strlen("#[blue]#[reset]"); } return color_chars; } char* ecs_rule_str_w_profile( const ecs_rule_t *rule, const ecs_iter_t *it) { ecs_poly_assert(rule, ecs_rule_t); ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_rule_op_t *ops = rule->ops; int32_t i, count = rule->op_count, indent = 0; for (i = 0; i < count; i ++) { ecs_rule_op_t *op = &ops[i]; ecs_flags16_t flags = op->flags; ecs_flags16_t src_flags = flecs_rule_ref_flags(flags, EcsRuleSrc); ecs_flags16_t first_flags = flecs_rule_ref_flags(flags, EcsRuleFirst); ecs_flags16_t second_flags = flecs_rule_ref_flags(flags, EcsRuleSecond); if (it) { #ifdef FLECS_DEBUG const ecs_rule_iter_t *rit = &it->priv.iter.rule; ecs_strbuf_append(&buf, "#[green]%4d -> #[red]%4d <- #[grey] | ", rit->profile[i].count[0], rit->profile[i].count[1]); #endif } ecs_strbuf_append(&buf, "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", i, op->prev, op->next); int32_t hidden_chars, start = ecs_strbuf_written(&buf); if (op->kind == EcsRuleEnd) { indent --; } ecs_strbuf_append(&buf, "%*s", indent, ""); ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); ecs_strbuf_appendstr(&buf, " "); int32_t written = ecs_strbuf_written(&buf); for (int32_t j = 0; j < (10 - (written - start)); j ++) { ecs_strbuf_appendch(&buf, ' '); } if (op->kind == EcsRuleJmpCondFalse || op->kind == EcsRuleSetCond || op->kind == EcsRuleJmpNotSet) { ecs_strbuf_appendint(&buf, op->other); ecs_strbuf_appendch(&buf, ' '); } hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); if (op->kind == EcsRuleUnion) { indent ++; } if (!first_flags && !second_flags) { ecs_strbuf_appendstr(&buf, "\n"); continue; } written = ecs_strbuf_written(&buf) - hidden_chars; for (int32_t j = 0; j < (30 - (written - start)); j ++) { ecs_strbuf_appendch(&buf, ' '); } ecs_strbuf_appendstr(&buf, "("); flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); if (second_flags) { ecs_strbuf_appendstr(&buf, ", "); flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); } else { switch (op->kind) { case EcsRulePredEqName: case EcsRulePredNeqName: case EcsRulePredEqMatch: case EcsRulePredNeqMatch: { int8_t term_index = op->term_index; ecs_strbuf_appendstr(&buf, ", #[yellow]\""); ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); ecs_strbuf_appendstr(&buf, "\"#[reset]"); break; } case EcsRuleLookup: { ecs_var_id_t src_id = op->src.var; ecs_strbuf_appendstr(&buf, ", #[yellow]\""); ecs_strbuf_appendstr(&buf, rule->vars[src_id].lookup); ecs_strbuf_appendstr(&buf, "\"#[reset]"); break; } default: break; } } ecs_strbuf_appendch(&buf, ')'); ecs_strbuf_appendch(&buf, '\n'); } #ifdef FLECS_LOG char *str = ecs_strbuf_get(&buf); flecs_colorize_buf(str, true, &buf); ecs_os_free(str); #endif return ecs_strbuf_get(&buf); } char* ecs_rule_str( const ecs_rule_t *rule) { return ecs_rule_str_w_profile(rule, NULL); } const ecs_filter_t* ecs_rule_get_filter( const ecs_rule_t *rule) { return &rule->filter; } const char* ecs_rule_parse_vars( ecs_rule_t *rule, ecs_iter_t *it, const char *expr) { ecs_poly_assert(rule, ecs_rule_t); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL) char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = expr; bool paren = false; const char *name = NULL; if (rule->filter.entity) { name = ecs_get_name(rule->filter.world, rule->filter.entity); } ptr = ecs_parse_ws_eol(ptr); if (!ptr[0]) { return ptr; } if (ptr[0] == '(') { paren = true; ptr = ecs_parse_ws_eol(ptr + 1); if (ptr[0] == ')') { return ptr + 1; } } do { ptr = ecs_parse_ws_eol(ptr); ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } int var = ecs_rule_find_var(rule, token); if (var == -1) { ecs_parser_error(name, expr, (ptr - expr), "unknown variable '%s'", token); return NULL; } ptr = ecs_parse_ws_eol(ptr); if (ptr[0] != ':') { ecs_parser_error(name, expr, (ptr - expr), "missing ':'"); return NULL; } ptr = ecs_parse_ws_eol(ptr + 1); ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } ecs_entity_t val = ecs_lookup_fullpath(rule->filter.world, token); if (!val) { ecs_parser_error(name, expr, (ptr - expr), "unresolved entity '%s'", token); return NULL; } ecs_iter_set_var(it, var, val); ptr = ecs_parse_ws_eol(ptr); if (ptr[0] == ')') { if (!paren) { ecs_parser_error(name, expr, (ptr - expr), "unexpected closing parenthesis"); return NULL; } ptr ++; break; } else if (ptr[0] == ',') { ptr ++; } else if (!ptr[0]) { if (paren) { ecs_parser_error(name, expr, (ptr - expr), "missing closing parenthesis"); return NULL; } break; } else { ecs_parser_error(name, expr, (ptr - expr), "expected , or end of string"); return NULL; } } while (true); return ptr; error: return NULL; } #endif