From 9c2f8e3e7f5f638040c3070226808d081c0a6af6 Mon Sep 17 00:00:00 2001 From: Tom Stellard Date: Tue, 23 Mar 2021 14:12:07 -0700 Subject: [PATCH] Parse strategy conditions once when loading them from xml This way strategy_live_game_check() only needs to evaluate the condition and does not need to reparse it over and over again. This reduces the number of instructions reported by ./test/benchmark.sh --benchmark by ~35%. --- src/free.c | 4 + src/misc.c | 400 ++++++++++++++++++++++++++++++++++++++++++ src/misc.h | 6 + src/strategy.c | 2 +- src/strategy.h | 1 - src/strategy_struct.h | 27 +++ src/xml_strategy.c | 8 +- 7 files changed, 444 insertions(+), 4 deletions(-) diff --git a/src/free.c b/src/free.c index 11c15e53..899043ab 100644 --- a/src/free.c +++ b/src/free.c @@ -1020,6 +1020,10 @@ free_strategies(void) g_array_index( g_array_index( strategies, Strategy, i).match_action, StrategyMatchAction, j).condition); + g_array_free( + g_array_index( + g_array_index( + strategies, Strategy, i).match_action, StrategyMatchAction, j).parsed_condition, TRUE); g_free( g_array_index( g_array_index( diff --git a/src/misc.c b/src/misc.c index 17d12cd5..9aa3092b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -29,6 +29,7 @@ #include "maths.h" #include "misc.h" #include "option.h" +#include "strategy_struct.h" #include "variables.h" /** @@ -812,3 +813,402 @@ misc_alphabetic_compare(gconstpointer a, gconstpointer b) return 0; } + +static gboolean +misc_condition_evaluate_var(const StratCondPart *var, GArray *operands, GPtrArray **token_rep) +{ + gint i; + for (i = 0; i < token_rep[0]->len; i++) { + gint value; + const gchar *token_name = g_ptr_array_index(token_rep[0], i); + const gchar *token_value = g_ptr_array_index(token_rep[1], i); + if (strncmp(var->value, token_name, var->len)) + continue; + value = g_ascii_strtoll(token_value, NULL, 0); + g_array_append_val(operands, value); + return TRUE; + } + + /* I think it would be good to warn here that the variable was not found, + * however the the previous implementation considered the whole condition + * to be FALSE when a variable was not found, so for now we need to + * replicate the behavior. + */ + return FALSE; +} + +static gboolean +misc_parse_condition_is_binary_op(const StratCondPart *part) +{ + switch (part->token) { + default: + return FALSE; + case STRAT_COND_EQ: + case STRAT_COND_NE: + case STRAT_COND_GT: + case STRAT_COND_GE: + case STRAT_COND_LT: + case STRAT_COND_LE: + case STRAT_COND_AND: + case STRAT_COND_OR: + return TRUE; + } +} + +static gboolean +misc_parse_condition_is_compare_op(const StratCondPart *part) +{ + switch (part->token) { + default: + return FALSE; + case STRAT_COND_EQ: + case STRAT_COND_NE: + case STRAT_COND_GT: + case STRAT_COND_GE: + case STRAT_COND_LT: + case STRAT_COND_LE: + return TRUE; + } +} + +static gboolean +misc_condition_evaluate(const StratCondPart *operation, GArray *operands) +{ + gint op0, op1, result; + if (operands->len < 2) + return FALSE; + + op0 = g_array_index(operands, gint, operands->len - 2); + op1 = g_array_index(operands, gint, operands->len - 1); + + switch (operation->token) { + default: + g_critical("Unknown token: %d\n", operation->token); + return FALSE; + case STRAT_COND_EQ: + result = op0 == op1; + break; + case STRAT_COND_NE: + result = op0 != op1; + break; + case STRAT_COND_GT: + result = op0 > op1; + break; + case STRAT_COND_GE: + result = op0 >= op1; + break; + case STRAT_COND_LT: + result = op0 < op1; + break; + case STRAT_COND_LE: + result = op0 <= op1; + break; + case STRAT_COND_AND: + result = op0 && op1; + break; + case STRAT_COND_OR: + result = op0 || op1; + break; + } + + g_array_remove_range(operands, operands->len - 2, 2); + g_array_append_val(operands, result); + return TRUE; +} + +static const StratCondPart * +misc_parse_value_expression(GArray *stack, const StratCondPart *input) +{ + if (input->token == STRAT_COND_INT || input->token == STRAT_COND_VAR) { + g_array_append_val(stack, *input); + return input + 1; + } + return input; +} + +static const StratCondPart * +misc_parse_compare_expression(GArray *stack, const StratCondPart *input) +{ + input = misc_parse_value_expression(stack, input); + if (misc_parse_condition_is_compare_op(input)) { + const StratCondPart *compare_op = input; + input = misc_parse_value_expression(stack, input + 1); + g_array_append_val(stack, *compare_op); + } + return input; +} + +static const StratCondPart * +misc_parse_and_expression(GArray *stack, const StratCondPart *input) +{ + input = misc_parse_compare_expression(stack, input); + if (input->token == STRAT_COND_AND) { + const StratCondPart *and = input; + input = misc_parse_and_expression(stack, input + 1); + g_array_append_val(stack, *and); + } + return input; +} + +static const StratCondPart * +misc_parse_or_expression(GArray *stack, const StratCondPart *input) +{ + if (input->token == STRAT_COND_OPEN_PAREN) { + input = misc_parse_or_expression(stack, input + 1); + if (input->token != STRAT_COND_CLOSE_PAREN); + return NULL; + return input + 1; + } + + input = misc_parse_and_expression(stack, input); + if (input->token == STRAT_COND_OR) { + const StratCondPart *or = input; + input = misc_parse_or_expression(stack, input + 1); + g_array_append_val(stack, *or); + } + return input; +} + +static const StratCondPart * +misc_parse_paren_expression(GArray *stack, const StratCondPart *input) +{ + if (input->token == STRAT_COND_OPEN_PAREN) { + input = misc_parse_or_expression(stack, input + 1); + if (input->token != STRAT_COND_CLOSE_PAREN) { + return NULL; + } + return input + 1; + } + return misc_parse_or_expression(stack, input); +} + +/** + * This functions parses a condition into a stack of operations in postfix + * notation, where the operands preceeded the operation. + * Lexer Tokens: + * + * TOKEN = COND | INT | VARIABLE | LOGIC_OP | OPEN_PAREN | CLOSE_PAREN + * COND = '=' | '!=' | GT | GE | LT | LE + * GT = 'G' | '>' + * GE = 'GE' | '>=' + * LT = 'L' | '<' + * LE = 'LE | '<=' + * INT = [-]*[0-9]+ + * VARIABLE = _[A-Z]+_ + * LOGIC_OP = 'and' | 'or' + * OPEN_PAREN = '(' + * CLOSE_PAREN = ')' + * + * BNF: + * + * ::= + * + * ::= ( ) + * | + * + * ::= or + * | + * | ( And Expression ) + * + * ::= and + * | + * + * ::= compare_op + * | + * ::= int + * | variable + */ +GArray * +misc_parse_condition_fast(const gchar *condition) +{ + GError *error = NULL; + + GRegex *int_regex = g_regex_new("-*[0-9]+", 0, 0, &error); + GRegex *var_regex = g_regex_new("_[A-Z]+_", 0, 0, &error); + const gchar *iter = condition; + GArray *tokens = g_array_new(TRUE, FALSE, sizeof(StratCondPart)); + GMatchInfo *match_info = NULL; + GArray *stack; + + /* Lexer */ + + while (*iter) { + StratCondPart part; + part.value = iter; + switch (*iter) { + case ' ': + iter++; + continue; + case '(': + part.token = STRAT_COND_OPEN_PAREN; + part.len = 1; + break; + case ')': + part.token = STRAT_COND_CLOSE_PAREN; + part.len = 1; + break; + case '=': + part.token = STRAT_COND_EQ; + part.len = 1; + break; + case '!': + if (iter[1] != '=') + goto lexer_done; + part.token = STRAT_COND_NE; + part.len = 2; + break; + case 'G': + if (iter[1] == 'E') { + part.token = STRAT_COND_GE; + part.len = 2; + break; + } + part.token = STRAT_COND_GT; + part.len = 1; + break; + case '>': + if (iter[1] == '=') { + part.token = STRAT_COND_GE; + part.len = 2; + break; + } + part.token = STRAT_COND_GT; + part.len = 1; + break; + case 'L': + if (iter[1] == 'E') { + part.token = STRAT_COND_LE; + part.len = 2; + break; + } + part.token = STRAT_COND_LT; + part.len = 1; + break; + case '<': + if (iter[1] == '=') { + part.token = STRAT_COND_LE; + part.len = 2; + break; + } + part.token = STRAT_COND_LT; + part.len = 1; + break; + case 'a': + if (!g_strrstr_len(iter, 3, "and")) + goto lexer_done; + part.token = STRAT_COND_AND; + part.len = 3; + break; + case 'o': + if (iter[1] != 'r') + goto lexer_done; + part.token = STRAT_COND_OR; + part.len = 2; + break; + case '_': { + gint start_pos = -1, end_pos = -1; + if (!g_regex_match(var_regex, iter, 0, &match_info)) + goto lexer_done; + if (!g_match_info_fetch_pos(match_info, 0, &start_pos, &end_pos)) + goto lexer_done; + if (start_pos != 0) + goto lexer_done; + part.token = STRAT_COND_VAR; + part.len = end_pos; + break; + } + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + gint start_pos = -1, end_pos = -1; + if (!g_regex_match(int_regex, iter, 0, &match_info)) + goto lexer_done; + if (!g_match_info_fetch_pos(match_info, 0, &start_pos, &end_pos)) + goto lexer_done; + if (start_pos != 0) + goto lexer_done; + part.token = STRAT_COND_INT; + part.len = end_pos; + part.value = GINT_TO_POINTER(g_ascii_strtoll(part.value, NULL, 0)); + break; + } + default: + goto lexer_done; + } + + iter += part.len; + g_array_append_val(tokens, part); + } + +lexer_done: + g_regex_unref(int_regex); + g_regex_unref(var_regex); + + if (match_info) + g_match_info_free(match_info); + + if (*iter) { + /* Handle error */ + g_array_unref(tokens); + g_critical("Failed to lex strategy condition %s at char %d (%c)\n", + condition, (gint)(iter - condition), *iter); + return NULL; + } + + /* Parser */ + stack = g_array_new(FALSE, FALSE, sizeof(StratCondPart)); + if (!misc_parse_paren_expression(stack, (StratCondPart*)tokens->data)) { + g_critical("Failed to parse strategy condiiton %s\n", condition); + g_array_unref(stack); + stack = NULL; + } + + g_array_unref(tokens); + return stack; +} + + + +gboolean +misc_evaluate_condition(const GArray *condition, GPtrArray **token_rep) +{ + GArray *operands = g_array_new(FALSE, FALSE, sizeof(gint)); + gboolean result = FALSE; + gint i; + /* Execute the stack. */ + for (i = 0; i < condition->len; i++) { + const StratCondPart *part = &g_array_index(condition, StratCondPart, i); + switch (part->token) { + case STRAT_COND_VAR: + if (!misc_condition_evaluate_var(part, operands, token_rep)) { + /* In order to match the behavior of the previous + * implementation, we need to return FALSE if we + * failed to replace a variable. */ + goto done; + } + break; + case STRAT_COND_INT: { + int value = GPOINTER_TO_INT(part->value); + g_array_append_val(operands, value); + break; + } + default: + misc_condition_evaluate(part, operands); + break; + } + } + + result = g_array_index(operands, gint, 0); + + done: + g_array_free(operands, TRUE); + return result; +} diff --git a/src/misc.h b/src/misc.h index 2808fc4f..9ed3b4f4 100644 --- a/src/misc.h +++ b/src/misc.h @@ -138,4 +138,10 @@ misc_token_add_bool(GPtrArray **token_rep, gint token_idx, gint misc_alphabetic_compare(gconstpointer a, gconstpointer b); +GArray * +misc_parse_condition_fast(const gchar *condition); + +gboolean +misc_evaluate_condition(const GArray *condition, GPtrArray **token_rep); + #endif diff --git a/src/strategy.c b/src/strategy.c index a3ee4ad4..a3188a66 100644 --- a/src/strategy.c +++ b/src/strategy.c @@ -691,7 +691,7 @@ strategy_live_game_check(LiveGame *match, gint team_idx) if((match->subs_left[team_idx] > 0 || action->sub_in_pos == -1) && !query_misc_integer_is_in_g_array(action->id, match->action_ids[team_idx]) && (action->condition == NULL || - misc_parse_condition(action->condition, token_strat))) + misc_evaluate_condition(action->parsed_condition, token_strat))) { strategy_live_game_apply_action(match, team_idx, action); break; diff --git a/src/strategy.h b/src/strategy.h index 375dd682..0c468495 100644 --- a/src/strategy.h +++ b/src/strategy.h @@ -93,5 +93,4 @@ gint strategy_compare_positions(gconstpointer a, gconstpointer b, gpointer user_data); - #endif diff --git a/src/strategy_struct.h b/src/strategy_struct.h index d1e03014..8526796b 100644 --- a/src/strategy_struct.h +++ b/src/strategy_struct.h @@ -37,6 +37,31 @@ enum StratLineupType STRAT_LINEUP_END }; +/** Tokens that represent parts of a strategy condition. */ +enum StratCondToken { + STRAT_COND_NONE, + STRAT_COND_EQ, + STRAT_COND_NE, + STRAT_COND_GT, + STRAT_COND_GE, + STRAT_COND_LT, + STRAT_COND_LE, + STRAT_COND_INT, + STRAT_COND_VAR, + STRAT_COND_AND, + STRAT_COND_OR, + STRAT_COND_OPEN_PAREN, + STRAT_COND_CLOSE_PAREN, + STRAT_CON_LAST +}; + +typedef struct +{ + enum StratCondToken token; + const gchar *value; + gint len; +} StratCondPart; + /** A struct describing the pre-match strategy settings of a CPU team. */ typedef struct @@ -56,6 +81,8 @@ typedef struct { /** A condition describing when the action should be taken. */ gchar *condition, *sub_condition; + /** A lexed version of condition for faster processing. */ + GArray *parsed_condition; /** New boost and style values. */ gint boost, style; /** Substitution specifiers (position and property). diff --git a/src/xml_strategy.c b/src/xml_strategy.c index c027675f..38a6d339 100644 --- a/src/xml_strategy.c +++ b/src/xml_strategy.c @@ -28,6 +28,7 @@ #include "main.h" #include "misc.h" #include "strategy_struct.h" +#include "strategy.h" #include "xml_strategy.h" #define TAG_STRATEGY "strategy" @@ -176,6 +177,7 @@ xml_strategy_read_start_element (GMarkupParseContext *context, new_match_action.sub_condition = NULL; new_match_action.condition = NULL; + new_match_action.parsed_condition = NULL; new_match_action.boost = new_match_action.style = -100; new_match_action.sub_in_pos = -1; @@ -184,10 +186,12 @@ xml_strategy_read_start_element (GMarkupParseContext *context, while(attribute_names[atidx] != NULL) { if(strcmp(attribute_names[atidx], ATT_NAME_COND) == 0 && - new_match_action.condition == NULL) + new_match_action.condition == NULL) { new_match_action.condition = g_strdup(attribute_values[atidx]); - else + new_match_action.parsed_condition = + misc_parse_condition_fast(new_match_action.condition); + } else debug_print_message("xml_strategy_read_start_element: unknown attribute %s\n", attribute_names[atidx]);