410 lines
12 KiB
C
410 lines
12 KiB
C
/**
|
|
* @file addons/rules/api.c
|
|
* @brief User facing API for rules.
|
|
*/
|
|
|
|
#include "rules.h"
|
|
#include <ctype.h>
|
|
|
|
#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
|