diff --git a/include/rc_runtime_types.h b/include/rc_runtime_types.h index 0092013b..b57f3443 100644 --- a/include/rc_runtime_types.h +++ b/include/rc_runtime_types.h @@ -29,7 +29,7 @@ typedef struct rc_value_t rc_value_t; * num_bytes is greater than 1, the value is read in little-endian from * memory. */ -typedef uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); +typedef uint32_t(RC_CCONV* rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); /*****************************************************************************\ | Memory References | @@ -210,9 +210,6 @@ struct rc_condition_t { /* The comparison operator to use. (RC_OPERATOR_*) */ uint8_t oper; /* operator is a reserved word in C++. */ - /* Set if the condition needs to processed as part of the "check if paused" pass. (bool) */ - uint8_t pause; - /* Whether or not the condition evaluated true on the last check. (bool) */ uint8_t is_true; @@ -233,9 +230,27 @@ struct rc_condset_t { /* The first condition in this condition set. Then follow ->next chain. */ rc_condition_t* conditions; - /* True if any condition in the set is a pause condition. */ - uint8_t has_pause; + /* The number of pause conditions in this condition set. */ + /* The first pause condition is at "this + RC_ALIGN(sizeof(this)). */ + uint16_t num_pause_conditions; + + /* The number of reset conditions in this condition set. */ + uint16_t num_reset_conditions; + + /* The number of hittarget conditions in this condition set. */ + uint16_t num_hittarget_conditions; + /* The number of non-hittarget measured conditions in this condition set. */ + uint16_t num_measured_conditions; + + /* The number of other conditions in this condition set. */ + uint16_t num_other_conditions; + + /* The number of indirect conditions in this condition set. */ + uint16_t num_indirect_conditions; + + /* True if any condition in the set is a pause condition. */ + uint8_t has_pause; /* DEPRECATED - just check num_pause_conditions != 0 */ /* True if the set is currently paused. */ uint8_t is_paused; }; @@ -300,7 +315,7 @@ struct rc_value_t { /* The current value of the variable. */ rc_memref_value_t value; - /* The list of conditions to evaluate. */ + /* The list of possible values (traverse next chain, pick max). */ rc_condset_t* conditions; /* The memory references required by the variable. */ @@ -409,7 +424,7 @@ struct rc_richpresence_display_part_t { rc_richpresence_display_part_t* next; const char* text; rc_richpresence_lookup_t* lookup; - rc_memref_value_t *value; + rc_memref_value_t* value; uint8_t display_type; }; diff --git a/src/rcheevos/alloc.c b/src/rcheevos/alloc.c index c71b478d..597892da 100644 --- a/src/rcheevos/alloc.c +++ b/src/rcheevos/alloc.c @@ -35,11 +35,11 @@ void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment if (pointer != 0) { /* valid buffer, grab the next chunk */ - ptr = (void*)((char*)pointer + *offset); + ptr = (void*)((uint8_t*)pointer + *offset); } else if (scratch != 0 && scratch_object_pointer_offset < sizeof(scratch->objs)) { /* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */ - void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset); + void** scratch_object_pointer = (void**)((uint8_t*)&scratch->objs + scratch_object_pointer_offset); ptr = *scratch_object_pointer; if (!ptr) { int32_t used; diff --git a/src/rcheevos/condition.c b/src/rcheevos/condition.c index fbef1abe..15e3a82d 100644 --- a/src/rcheevos/condition.c +++ b/src/rcheevos/condition.c @@ -184,16 +184,19 @@ void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand } rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse) { - rc_condition_t* self; + rc_condition_t * self = RC_ALLOC(rc_condition_t, parse); + rc_parse_condition_internal(self, memaddr, parse); + return (parse->offset < 0) ? NULL : self; +} + +void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_parse_state_t* parse) { const char* aux; int result; int can_modify = 0; aux = *memaddr; - self = RC_ALLOC(rc_condition_t, parse); self->current_hits = 0; self->is_true = 0; - self->pause = 0; self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT; if (*aux != 0 && aux[1] == ':') { @@ -216,8 +219,11 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse parse->measured_as_percent = 1; self->type = RC_CONDITION_MEASURED; break; + /* e f h j l s u v w x y */ - default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0; + default: + parse->offset = RC_INVALID_CONDITION_TYPE; + return; } aux += 2; @@ -229,69 +235,55 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse result = rc_parse_operand(&self->operand1, &aux, parse); if (result < 0) { parse->offset = result; - return 0; + return; } result = rc_parse_operator(&aux); if (result < 0) { parse->offset = result; - return 0; + return; } - self->oper = (char)result; - switch (self->oper) { - case RC_OPERATOR_NONE: - /* non-modifying statements must have a second operand */ - if (!can_modify) { - /* measured does not require a second operand when used in a value */ - if (self->type != RC_CONDITION_MEASURED) { - parse->offset = RC_INVALID_OPERATOR; - return 0; - } + self->oper = (uint8_t)result; + + if (self->oper == RC_OPERATOR_NONE) { + /* non-modifying statements must have a second operand */ + if (!can_modify) { + /* measured does not require a second operand when used in a value */ + if (self->type != RC_CONDITION_MEASURED) { + parse->offset = RC_INVALID_OPERATOR; + return; } + } - /* provide dummy operand of '1' and no required hits */ - rc_operand_set_const(&self->operand2, 1); - self->required_hits = 0; - *memaddr = aux; - return self; - - case RC_OPERATOR_MULT: - case RC_OPERATOR_DIV: - case RC_OPERATOR_AND: - case RC_OPERATOR_XOR: - case RC_OPERATOR_MOD: - case RC_OPERATOR_ADD: - case RC_OPERATOR_SUB: - /* modifying operators are only valid on modifying statements */ - if (can_modify) - break; - /* fallthrough */ + /* provide dummy operand of '1' and no required hits */ + rc_operand_set_const(&self->operand2, 1); + self->required_hits = 0; + *memaddr = aux; + return; + } - default: - /* comparison operators are not valid on modifying statements */ - if (can_modify) { - switch (self->type) { - case RC_CONDITION_ADD_SOURCE: - case RC_CONDITION_SUB_SOURCE: - case RC_CONDITION_ADD_ADDRESS: - case RC_CONDITION_REMEMBER: - /* prevent parse errors on legacy achievements where a condition was present before changing the type */ - self->oper = RC_OPERATOR_NONE; - break; + if (can_modify && !rc_operator_is_modifying(self->oper)) { + /* comparison operators are not valid on modifying statements */ + switch (self->type) { + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_ADD_ADDRESS: + case RC_CONDITION_REMEMBER: + /* prevent parse errors on legacy achievements where a condition was present before changing the type */ + self->oper = RC_OPERATOR_NONE; + break; - default: - parse->offset = RC_INVALID_OPERATOR; - return 0; - } - } - break; + default: + parse->offset = RC_INVALID_OPERATOR; + return; + } } result = rc_parse_operand(&self->operand2, &aux, parse); if (result < 0) { parse->offset = result; - return 0; + return; } if (self->oper == RC_OPERATOR_NONE) { @@ -305,7 +297,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse if (end == aux || *end != ')') { parse->offset = RC_INVALID_REQUIRED_HITS; - return 0; + return; } /* if operator is none, explicitly clear out the required hits */ @@ -322,7 +314,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse if (end == aux || *end != '.') { parse->offset = RC_INVALID_REQUIRED_HITS; - return 0; + return; } /* if operator is none, explicitly clear out the required hits */ @@ -341,7 +333,6 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->optimized_comparator = rc_condition_determine_comparator(self); *memaddr = aux; - return self; } void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse) { diff --git a/src/rcheevos/condset.c b/src/rcheevos/condset.c index 250fa4b9..36297d3e 100644 --- a/src/rcheevos/condset.c +++ b/src/rcheevos/condset.c @@ -2,26 +2,131 @@ #include /* memcpy */ -static void rc_update_condition_pause(rc_condition_t* condition) { - rc_condition_t* subclause = condition; - - while (condition) { - if (condition->type == RC_CONDITION_PAUSE_IF) { - while (subclause != condition) { - subclause->pause = 1; - subclause = subclause->next; - } - condition->pause = 1; - } - else { - condition->pause = 0; +enum { + RC_CONDITION_CLASSIFICATION_COMBINING, + RC_CONDITION_CLASSIFICATION_PAUSE, + RC_CONDITION_CLASSIFICATION_RESET, + RC_CONDITION_CLASSIFICATION_HITTARGET, + RC_CONDITION_CLASSIFICATION_MEASURED, + RC_CONDITION_CLASSIFICATION_OTHER, + RC_CONDITION_CLASSIFICATION_INDIRECT +}; + +static int rc_classify_condition(const rc_condition_t* cond) { + switch (cond->type) { + case RC_CONDITION_PAUSE_IF: + return RC_CONDITION_CLASSIFICATION_PAUSE; + + case RC_CONDITION_RESET_IF: + return RC_CONDITION_CLASSIFICATION_RESET; + + case RC_CONDITION_ADD_ADDRESS: + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_SUB_SOURCE: + /* these are handled by rc_modified_memref_t */ + return RC_CONDITION_CLASSIFICATION_INDIRECT; + + case RC_CONDITION_ADD_HITS: + case RC_CONDITION_AND_NEXT: + case RC_CONDITION_OR_NEXT: + case RC_CONDITION_REMEMBER: + case RC_CONDITION_RESET_NEXT_IF: + case RC_CONDITION_SUB_HITS: + return RC_CONDITION_CLASSIFICATION_COMBINING; + + case RC_CONDITION_MEASURED: + case RC_CONDITION_MEASURED_IF: + /* even if not measuring a hit target, we still want to evaluate it every frame */ + return RC_CONDITION_CLASSIFICATION_MEASURED; + + default: + if (cond->required_hits != 0) + return RC_CONDITION_CLASSIFICATION_HITTARGET; + + return RC_CONDITION_CLASSIFICATION_OTHER; + } +} + +static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr) { + rc_parse_state_t parse; + rc_memref_t* memrefs; + rc_condition_t condition; + int classification; + uint32_t index = 0; + uint32_t chain_length = 1; + + rc_init_parse_state(&parse, NULL, NULL, 0); + rc_init_parse_state_memrefs(&parse, &memrefs); + + do { + rc_parse_condition_internal(&condition, &memaddr, &parse); + + if (parse.offset < 0) + return parse.offset; + + ++index; + + classification = rc_classify_condition(&condition); + switch (classification) { + case RC_CONDITION_CLASSIFICATION_COMBINING: + ++chain_length; + continue; + + case RC_CONDITION_CLASSIFICATION_INDIRECT: + ++self->num_indirect_conditions; + continue; + + case RC_CONDITION_CLASSIFICATION_PAUSE: + self->num_pause_conditions += chain_length; + break; + + case RC_CONDITION_CLASSIFICATION_RESET: + self->num_reset_conditions += chain_length; + break; + + case RC_CONDITION_CLASSIFICATION_HITTARGET: + self->num_hittarget_conditions += chain_length; + break; + + case RC_CONDITION_CLASSIFICATION_MEASURED: + self->num_measured_conditions += chain_length; + break; + + default: + self->num_other_conditions += chain_length; + break; } - if (!rc_condition_is_combining(condition)) - subclause = condition->next; + chain_length = 1; + } while (*memaddr++ == '_'); - condition = condition->next; - } + return index; +} + +static int rc_find_next_classification(const char* memaddr) { + rc_parse_state_t parse; + rc_memref_t* memrefs; + rc_condition_t condition; + int classification; + + rc_init_parse_state(&parse, NULL, NULL, 0); + rc_init_parse_state_memrefs(&parse, &memrefs); + + do { + rc_parse_condition_internal(&condition, &memaddr, &parse); + + classification = rc_classify_condition(&condition); + switch (classification) { + case RC_CONDITION_CLASSIFICATION_COMBINING: + case RC_CONDITION_CLASSIFICATION_INDIRECT: + break; + + default: + return classification; + } + } while (*memaddr++ == '_'); + + return RC_CONDITION_CLASSIFICATION_OTHER; } static void rc_condition_update_recall_operand(rc_operand_t* operand, const rc_operand_t* remember) @@ -40,14 +145,17 @@ static void rc_condition_update_recall_operand(rc_operand_t* operand, const rc_o } } -static void rc_update_condition_pause_remember(rc_condition_t* conditions) { +static void rc_update_condition_pause_remember(rc_condset_t* self) { rc_operand_t* pause_remember = NULL; rc_condition_t* condition; + rc_condition_t* pause_conditions; + const rc_condition_t* end_pause_condition; - for (condition = conditions; condition; condition = condition->next) { - if (!condition->pause) - continue; + /* ASSERT: pause conditions are first conditions */ + pause_conditions = rc_condset_get_conditions(self); + end_pause_condition = pause_conditions + self->num_pause_conditions; + for (condition = pause_conditions; condition < end_pause_condition; ++condition) { if (condition->type == RC_CONDITION_REMEMBER) { pause_remember = &condition->operand1; } @@ -66,8 +174,8 @@ static void rc_update_condition_pause_remember(rc_condition_t* conditions) { } if (pause_remember) { - for (condition = conditions; condition; condition = condition->next) { - if (!condition->pause) { + for (condition = self->conditions; condition; condition = condition->next) { + if (condition >= end_pause_condition) { /* if we didn't find a remember for a non-pause condition, use the last pause remember */ rc_condition_update_recall_operand(&condition->operand1, pause_remember); rc_condition_update_recall_operand(&condition->operand2, pause_remember); @@ -81,33 +189,77 @@ static void rc_update_condition_pause_remember(rc_condition_t* conditions) { } rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { + rc_condset_with_trailing_conditions_t* condset_with_conditions; + rc_condset_t local_condset; rc_condset_t* self; + rc_condition_t condition; + rc_condition_t* conditions; rc_condition_t** next; - rc_condition_t* condition; + rc_condition_t* pause_conditions = NULL; + rc_condition_t* reset_conditions = NULL; + rc_condition_t* hittarget_conditions = NULL; + rc_condition_t* measured_conditions = NULL; + rc_condition_t* other_conditions = NULL; + rc_condition_t* indirect_conditions = NULL; + int classification, combining_classification = RC_CONDITION_CLASSIFICATION_COMBINING; uint32_t measured_target = 0; - - self = RC_ALLOC(rc_condset_t, parse); - self->has_pause = self->is_paused = 0; - next = &self->conditions; + int32_t result; if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) { /* empty group - editor allows it, so we have to support it */ - *next = 0; + self = RC_ALLOC(rc_condset_t, parse); + memset(self, 0, sizeof(*self)); return self; } + memset(&local_condset, 0, sizeof(local_condset)); + result = rc_classify_conditions(&local_condset, *memaddr); + if (result < 0) { + parse->offset = result; + return NULL; + } + + condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t, + rc_condition_t, conditions, result, parse); + if (parse->offset < 0) + return NULL; + + self = (rc_condset_t*)condset_with_conditions; + memcpy(self, &local_condset, sizeof(local_condset)); + conditions = &condset_with_conditions->conditions[0]; + + if (parse->buffer) { + pause_conditions = conditions; + conditions += self->num_pause_conditions; + + reset_conditions = conditions; + conditions += self->num_reset_conditions; + + hittarget_conditions = conditions; + conditions += self->num_hittarget_conditions; + + measured_conditions = conditions; + conditions += self->num_measured_conditions; + + other_conditions = conditions; + conditions += self->num_other_conditions; + + indirect_conditions = conditions; + } + + next = &self->conditions; + /* each condition set has a functionally new recall accumulator */ parse->remember.type = RC_OPERAND_NONE; for (;;) { - condition = rc_parse_condition(memaddr, parse); - *next = condition; + rc_parse_condition_internal(&condition, memaddr, parse); if (parse->offset < 0) - return 0; + return NULL; - if (condition->oper == RC_OPERATOR_NONE) { - switch (condition->type) { + if (condition.oper == RC_OPERATOR_NONE) { + switch (condition.type) { case RC_CONDITION_ADD_ADDRESS: case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: @@ -123,55 +275,42 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { default: parse->offset = RC_INVALID_OPERATOR; - return 0; + return NULL; } } - switch (condition->type) { + switch (condition.type) { case RC_CONDITION_MEASURED: if (measured_target != 0) { /* multiple Measured flags cannot exist in the same group */ parse->offset = RC_MULTIPLE_MEASURED; - return 0; + return NULL; } else if (parse->is_value) { - measured_target = (unsigned)-1; - switch (condition->oper) { - case RC_OPERATOR_AND: - case RC_OPERATOR_XOR: - case RC_OPERATOR_DIV: - case RC_OPERATOR_MULT: - case RC_OPERATOR_MOD: - case RC_OPERATOR_ADD: - case RC_OPERATOR_SUB: - case RC_OPERATOR_NONE: - /* measuring value. leave required_hits at 0 */ - break; - - default: - /* comparison operator, measuring hits. set required_hits to MAX_INT */ - condition->required_hits = measured_target; - break; + measured_target = (uint32_t)-1; + if (!rc_operator_is_modifying(condition.oper)) { + /* measuring comparison in a value results in a tally (hit count). set target to MAX_INT */ + condition.required_hits = measured_target; } } - else if (condition->required_hits != 0) { - measured_target = condition->required_hits; + else if (condition.required_hits != 0) { + measured_target = condition.required_hits; } - else if (condition->operand2.type == RC_OPERAND_CONST) { - measured_target = condition->operand2.value.num; + else if (condition.operand2.type == RC_OPERAND_CONST) { + measured_target = condition.operand2.value.num; } - else if (condition->operand2.type == RC_OPERAND_FP) { - measured_target = (unsigned)condition->operand2.value.dbl; + else if (condition.operand2.type == RC_OPERAND_FP) { + measured_target = (unsigned)condition.operand2.value.dbl; } else { parse->offset = RC_INVALID_MEASURED_TARGET; - return 0; + return NULL; } if (parse->measured_target && measured_target != parse->measured_target) { /* multiple Measured flags in separate groups must have the same target */ parse->offset = RC_MULTIPLE_MEASURED; - return 0; + return NULL; } parse->measured_target = measured_target; @@ -182,16 +321,59 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { /* these flags are not allowed in value expressions */ if (parse->is_value) { parse->offset = RC_INVALID_VALUE_FLAG; - return 0; + return NULL; } break; } - rc_condition_update_parse_state(condition, parse); + rc_condition_update_parse_state(&condition, parse); + + if (parse->buffer) { + classification = rc_classify_condition(&condition); + if (classification == RC_CONDITION_CLASSIFICATION_COMBINING) { + if (combining_classification == RC_CONDITION_CLASSIFICATION_COMBINING) + combining_classification = rc_find_next_classification(&(*memaddr)[1]); /* skip over '_' */ + + classification = combining_classification; + } + else { + combining_classification = RC_CONDITION_CLASSIFICATION_COMBINING; + } + + switch (classification) { + case RC_CONDITION_CLASSIFICATION_PAUSE: + memcpy(pause_conditions, &condition, sizeof(condition)); + *next = pause_conditions++; + break; - self->has_pause |= condition->type == RC_CONDITION_PAUSE_IF; + case RC_CONDITION_CLASSIFICATION_RESET: + memcpy(reset_conditions, &condition, sizeof(condition)); + *next = reset_conditions++; + break; - next = &condition->next; + case RC_CONDITION_CLASSIFICATION_HITTARGET: + memcpy(hittarget_conditions, &condition, sizeof(condition)); + *next = hittarget_conditions++; + break; + + case RC_CONDITION_CLASSIFICATION_MEASURED: + memcpy(measured_conditions, &condition, sizeof(condition)); + *next = measured_conditions++; + break; + + case RC_CONDITION_CLASSIFICATION_INDIRECT: + memcpy(indirect_conditions, &condition, sizeof(condition)); + *next = indirect_conditions++; + break; + + default: + memcpy(other_conditions, &condition, sizeof(condition)); + *next = other_conditions++; + break; + } + + next = &(*next)->next; + } if (**memaddr != '_') break; @@ -199,20 +381,18 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { (*memaddr)++; } - *next = 0; - - if (parse->buffer && self->has_pause) { - rc_update_condition_pause(self->conditions); + *next = NULL; - if (parse->remember.type != RC_OPERATOR_NONE) - rc_update_condition_pause_remember(self->conditions); - } + self->has_pause = self->num_pause_conditions > 0; + if (self->has_pause && parse->buffer && parse->remember.type != RC_OPERAND_NONE) + rc_update_condition_pause_remember(self); return self; } -static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc_eval_state_t* eval_state) { - rc_condition_t* condition; +static int rc_test_condset_internal(rc_condition_t* condition, uint32_t num_conditions, + rc_eval_state_t* eval_state, int can_short_circuit) { + const rc_condition_t* condition_end = condition + num_conditions; int set_valid, cond_valid, and_next, or_next, reset_next, measured_from_hits, can_measure; rc_typed_value_t measured_value; uint32_t total_hits; @@ -222,17 +402,12 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc can_measure = 1; total_hits = 0; - eval_state->primed = 1; set_valid = 1; and_next = 1; or_next = 0; reset_next = 0; - eval_state->add_hits = 0; - - for (condition = self->conditions; condition != 0; condition = condition->next) { - if (condition->pause != processing_pause) - continue; + for (; condition < condition_end; ++condition) { /* STEP 1: process modifier conditions */ switch (condition->type) { case RC_CONDITION_ADD_SOURCE: @@ -367,6 +542,10 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc case RC_CONDITION_RESET_IF: if (cond_valid) { eval_state->was_reset = 1; /* let caller know to reset all hit counts */ + + if (can_short_circuit) + return 0; + set_valid = 0; /* cannot be valid if we've hit a reset condition */ } continue; @@ -391,6 +570,9 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc break; case RC_CONDITION_TRIGGER: + if (!cond_valid && can_short_circuit) + return 0; + /* update truthiness of set, but do not update truthiness of primed state */ set_valid &= cond_valid; continue; @@ -402,6 +584,9 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc /* STEP 5: update overall truthiness of set and primed state */ eval_state->primed &= cond_valid; set_valid &= cond_valid; + + if (!cond_valid && can_short_circuit) + return 0; } if (measured_value.type != RC_VALUE_TYPE_NONE) { @@ -416,22 +601,66 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc return set_valid; } +rc_condition_t* rc_condset_get_conditions(rc_condset_t* self) { + if (self->conditions) + return RC_GET_TRAILING(self, rc_condset_with_trailing_conditions_t, rc_condition_t, conditions); + + return NULL; +} + int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { - if (self->conditions == 0) { - /* important: empty group must evaluate true */ - return 1; - } + rc_condition_t* conditions; + int result = 1; /* true until proven otherwise */ + + eval_state->primed = 1; + eval_state->add_hits = 0; + + conditions = rc_condset_get_conditions(self); + + if (self->num_pause_conditions) { + /* one or more Pause conditions exists. if any of them are true, stop processing this group */ + self->is_paused = (char)rc_test_condset_internal(conditions, self->num_pause_conditions, eval_state, 0); - if (self->has_pause) { - /* one or more Pause conditions exists, if any of them are true, stop processing this group */ - self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state); if (self->is_paused) { + /* condset is paused. stop processing immediately. */ eval_state->primed = 0; return 0; } + + conditions += self->num_pause_conditions; + } + + if (self->num_reset_conditions) { + /* one or more Reset conditions exists. if any of them are true, rc_test_condset_internal + * will return false. clear hits and stop processing this group */ + result &= rc_test_condset_internal(conditions, self->num_reset_conditions, eval_state, eval_state->can_short_curcuit); + conditions += self->num_reset_conditions; + } + + if (self->num_hittarget_conditions) { + /* one or more hit target conditions exists. these must be processed every frame, unless their hit count is going to be reset */ + if (!eval_state->was_reset) + result &= rc_test_condset_internal(conditions, self->num_hittarget_conditions, eval_state, 0); + + conditions += self->num_hittarget_conditions; + } + + if (self->num_measured_conditions) { + /* measured value must be calculated every frame, even if hit counts will be reset */ + result &= rc_test_condset_internal(conditions, self->num_measured_conditions, eval_state, 0); + conditions += self->num_measured_conditions; + } + + if (self->num_other_conditions) { + /* remaining conditions only need to be evaluated if the rest of the condset is true */ + if (result) + result &= rc_test_condset_internal(conditions, self->num_other_conditions, eval_state, eval_state->can_short_curcuit); + /* something else is false. if we can't short circuit, and there wasn't a reset, we still need to evaluate these */ + else if (!eval_state->can_short_curcuit && !eval_state->was_reset) + result &= rc_test_condset_internal(conditions, self->num_other_conditions, eval_state, eval_state->can_short_curcuit); } - return rc_test_condset_internal(self, 0, eval_state); + return result; } void rc_reset_condset(rc_condset_t* self) { diff --git a/src/rcheevos/operand.c b/src/rcheevos/operand.c index 3a09a3b0..d36f51d2 100644 --- a/src/rcheevos/operand.c +++ b/src/rcheevos/operand.c @@ -404,6 +404,23 @@ int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) { } } +int rc_operator_is_modifying(int oper) { + switch (oper) { + case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: + case RC_OPERATOR_DIV: + case RC_OPERATOR_MULT: + case RC_OPERATOR_MOD: + case RC_OPERATOR_ADD: + case RC_OPERATOR_SUB: + case RC_OPERATOR_NONE: /* NONE operator implies "* 1" */ + return 1; + + default: + return 0; + } +} + static int rc_memsize_is_float(uint8_t size) { switch (size) { case RC_MEMSIZE_FLOAT: @@ -570,7 +587,7 @@ void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t n modifier.type = parse->addsource_parent.type = RC_OPERAND_ADDRESS; modified_memref = rc_alloc_modified_memref(parse, - new_size, &parse->addsource_parent, parse->addsource_oper, &modifier); + new_size, &parse->addsource_parent, parse->addsource_oper, &modifier); } else { modified_memref = rc_alloc_modified_memref(parse, diff --git a/src/rcheevos/rc_internal.h b/src/rcheevos/rc_internal.h index 150cae94..beb54380 100644 --- a/src/rcheevos/rc_internal.h +++ b/src/rcheevos/rc_internal.h @@ -39,7 +39,8 @@ typedef struct __rc_value_list_t { rc_value_t* first_value; } __rc_value_list_t; typedef struct __rc_trigger_state_enum_t { uint8_t value; } __rc_trigger_state_enum_t; typedef struct __rc_lboard_state_enum_t { uint8_t value; } __rc_lboard_state_enum_t; -#define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; }; +#define RC_ALLOW_ALIGN(T) struct __align_ ## T { uint8_t ch; T t; }; + RC_ALLOW_ALIGN(rc_condition_t) RC_ALLOW_ALIGN(rc_condset_t) RC_ALLOW_ALIGN(rc_modified_memref_t) @@ -57,11 +58,17 @@ RC_ALLOW_ALIGN(rc_value_t) RC_ALLOW_ALIGN(char) #define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T)) -#define RC_OFFSETOF(o, t) (int)((char*)&(o.t) - (char*)&(o)) +#define RC_OFFSETOF(o, t) (int)((uint8_t*)&(o.t) - (uint8_t*)&(o)) #define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t))) #define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t))) +#define RC_ALLOC_WITH_TRAILING(container_type, trailing_type, trailing_field, trailing_count, parse) ((container_type*)rc_alloc(\ + (parse)->buffer, &(parse)->offset, \ + RC_OFFSETOF((*(container_type*)NULL),trailing_field) + trailing_count * sizeof(trailing_type), \ + RC_ALIGNOF(container_type), &(parse)->scratch, 0)) +#define RC_GET_TRAILING(container_pointer, container_type, trailing_type, trailing_field) (trailing_type*)(&((container_type*)(container_pointer))->trailing_field) + /* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */ #define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1)) @@ -148,6 +155,7 @@ typedef struct { uint8_t primed; /* true if all non-Trigger conditions are true */ uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */ uint8_t was_cond_reset; /* ResetNextIf triggered */ + uint8_t can_short_curcuit; /* allows logic processing to stop as soon as a false condition is encountered */ } rc_eval_state_t; @@ -202,9 +210,16 @@ uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud) void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse); int rc_trigger_state_active(int state); +typedef struct rc_condset_with_trailing_conditions_t { + rc_condset_t condset; + rc_condition_t conditions[2]; +} rc_condset_with_trailing_conditions_t; +RC_ALLOW_ALIGN(rc_condset_with_trailing_conditions_t) + rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse); int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state); void rc_reset_condset(rc_condset_t* self); +rc_condition_t* rc_condset_get_conditions(rc_condset_t* self); enum { RC_PROCESSING_COMPARE_DEFAULT = 0, @@ -223,6 +238,7 @@ enum { }; rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse); +void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_parse_state_t* parse); void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse); int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state); void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state); @@ -231,6 +247,7 @@ void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse); void rc_evaluate_operand(rc_typed_value_t* value, const rc_operand_t* self, rc_eval_state_t* eval_state); +int rc_operator_is_modifying(int oper); int rc_operand_is_float_memref(const rc_operand_t* self); int rc_operand_is_float(const rc_operand_t* self); int rc_operand_is_recall(const rc_operand_t* self); diff --git a/src/rcheevos/rc_runtime_types.natvis b/src/rcheevos/rc_runtime_types.natvis index d620e544..d535db06 100644 --- a/src/rcheevos/rc_runtime_types.natvis +++ b/src/rcheevos/rc_runtime_types.natvis @@ -52,7 +52,7 @@ {RC_MEMREF_TYPE_MEMREF} {RC_MEMREF_TYPE_MODIFIED_MEMREF} - + RC_MEMREF_TYPE_VALUE unknown ({value}) @@ -247,6 +247,7 @@ + {{count={num_pause_conditions+num_reset_conditions+num_hittarget_conditions+num_measured_conditions+num_other_conditions+num_indirect_conditions}}} conditions diff --git a/src/rcheevos/trigger.c b/src/rcheevos/trigger.c index f8c176f9..681190e7 100644 --- a/src/rcheevos/trigger.c +++ b/src/rcheevos/trigger.c @@ -21,9 +21,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars else { self->requirement = rc_parse_condset(&aux, parse); - if (parse->offset < 0) { + if (parse->offset < 0) return; - } self->requirement->next = 0; } diff --git a/src/rcheevos/value.c b/src/rcheevos/value.c index 13a2ef9a..edfbb7d2 100644 --- a/src/rcheevos/value.c +++ b/src/rcheevos/value.c @@ -5,8 +5,6 @@ #include /* FLT_EPSILON */ #include /* fmod */ - - int rc_is_valid_variable_character(char ch, int is_first) { if (is_first) { if (!isalpha((unsigned char)ch)) @@ -52,134 +50,164 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse (*next_clause)->next = 0; } -void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { +static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { + rc_condset_with_trailing_conditions_t* condset_with_conditions; rc_condition_t** next; rc_condset_t** next_clause; + rc_condset_t* condset; + rc_condition_t local_cond; rc_condition_t* cond; + uint32_t num_measured_conditions; char buffer[64] = "A:"; const char* buffer_ptr; char* ptr; + char c; /* convert legacy format into condset */ - self->conditions = RC_ALLOC(rc_condset_t, parse); - memset(self->conditions, 0, sizeof(rc_condset_t)); + next_clause = &self->conditions; + do { + num_measured_conditions = 0; + + /* count the number of joiners and add one to determine the number of clauses. */ + num_measured_conditions = 1; + buffer_ptr = *memaddr; + while ((c = *buffer_ptr++) && c != '$') { + if (c == '_') { + ++num_measured_conditions; + buffer[0] = 'A'; /* reset to AddSource */ + } + else if (c == '*' && *buffer_ptr == '-') { + /* multiplication by a negative number will convert to SubSource */ + ++buffer_ptr; + buffer[0] = 'B'; + } + } - next = &self->conditions->conditions; - next_clause = &self->conditions->next; + /* if last condition is SubSource, we'll need to add a dummy condition for the Measured */ + if (buffer[0] == 'B') + ++num_measured_conditions; - for (;; ++(*memaddr)) { - buffer[0] = 'A'; /* reset to AddSource */ - ptr = &buffer[2]; + condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t, + rc_condition_t, conditions, num_measured_conditions, parse); + if (parse->offset < 0) + return; - /* extract the next clause */ - for (;; ++(*memaddr)) { - switch (**memaddr) { - case '_': /* add next */ - case '$': /* maximum of */ - case '\0': /* end of string */ - case ':': /* end of leaderboard clause */ - case ')': /* end of rich presence macro */ - *ptr = '\0'; - break; + condset = (rc_condset_t*)condset_with_conditions; + memset(condset, 0, sizeof(*condset)); + condset->num_measured_conditions = num_measured_conditions; + cond = &condset_with_conditions->conditions[0]; - case '*': - *ptr++ = '*'; - - buffer_ptr = *memaddr + 1; - if (*buffer_ptr == '-') { - buffer[0] = 'B'; /* change to SubSource */ - ++(*memaddr); /* don't copy sign */ - ++buffer_ptr; /* ignore sign when doing floating point check */ - } - else if (*buffer_ptr == '+') { - ++buffer_ptr; /* ignore sign when doing floating point check */ - } - - /* if it looks like a floating point number, add the 'f' prefix */ - while (isdigit((unsigned char)*buffer_ptr)) - ++buffer_ptr; - if (*buffer_ptr == '.') - *ptr++ = 'f'; - continue; + next = &condset->conditions; - default: - *ptr++ = **memaddr; - continue; - } + for (;; ++(*memaddr)) { + buffer[0] = 'A'; /* reset to AddSource */ + ptr = &buffer[2]; + + /* extract the next clause */ + for (;; ++(*memaddr)) { + switch (**memaddr) { + case '_': /* add next */ + case '$': /* maximum of */ + case '\0': /* end of string */ + case ':': /* end of leaderboard clause */ + case ')': /* end of rich presence macro */ + *ptr = '\0'; + break; + + case '*': + *ptr++ = '*'; + + buffer_ptr = *memaddr + 1; + if (*buffer_ptr == '-') { + buffer[0] = 'B'; /* change to SubSource */ + ++(*memaddr); /* don't copy sign */ + ++buffer_ptr; /* ignore sign when doing floating point check */ + } + else if (*buffer_ptr == '+') { + ++buffer_ptr; /* ignore sign when doing floating point check */ + } + + /* if it looks like a floating point number, add the 'f' prefix */ + while (isdigit((unsigned char)*buffer_ptr)) + ++buffer_ptr; + if (*buffer_ptr == '.') + *ptr++ = 'f'; + continue; + + default: + *ptr++ = **memaddr; + continue; + } - break; - } + break; + } - /* process the clause */ - buffer_ptr = buffer; - cond = rc_parse_condition(&buffer_ptr, parse); - if (parse->offset < 0) - return; + /* process the clause */ + if (!parse->buffer) + cond = &local_cond; - if (*buffer_ptr) { - /* whatever we copied as a single condition was not fully consumed */ - parse->offset = RC_INVALID_COMPARISON; - return; - } + buffer_ptr = buffer; + rc_parse_condition_internal(cond, &buffer_ptr, parse); + if (parse->offset < 0) + return; - switch (cond->oper) { - case RC_OPERATOR_MULT: - case RC_OPERATOR_DIV: - case RC_OPERATOR_AND: - case RC_OPERATOR_XOR: - case RC_OPERATOR_MOD: - case RC_OPERATOR_ADD: - case RC_OPERATOR_SUB: - case RC_OPERATOR_NONE: - break; + if (*buffer_ptr) { + /* whatever we copied as a single condition was not fully consumed */ + parse->offset = RC_INVALID_COMPARISON; + return; + } - default: + if (!rc_operator_is_modifying(cond->oper)) { parse->offset = RC_INVALID_OPERATOR; return; - } + } - *next = cond; + *next = cond; + next = &cond->next; + + if (**memaddr != '_') /* add next */ + break; - if (**memaddr == '_') { - /* add next */ rc_condition_update_parse_state(cond, parse); - next = &cond->next; - continue; + ++cond; } + /* end of clause */ if (cond->type == RC_CONDITION_SUB_SOURCE) { /* cannot change SubSource to Measured. add a dummy condition */ rc_condition_update_parse_state(cond, parse); - next = &cond->next; + if (parse->buffer) + ++cond; + buffer_ptr = "A:0"; - cond = rc_parse_condition(&buffer_ptr, parse); + rc_parse_condition_internal(cond, &buffer_ptr, parse); *next = cond; + next = &cond->next; } /* convert final AddSource condition to Measured */ cond->type = RC_CONDITION_MEASURED; - cond->next = 0; + cond->next = NULL; rc_condition_update_parse_state(cond, parse); + /* finalize clause */ + *next_clause = condset; + next_clause = &condset->next; + if (**memaddr != '$') { /* end of valid string */ - *next_clause = 0; + *next_clause = NULL; break; } /* max of ($), start a new clause */ - *next_clause = RC_ALLOC(rc_condset_t, parse); - - if (parse->buffer) /* don't clear in sizing mode or pointer will break */ - memset(*next_clause, 0, sizeof(rc_condset_t)); - - next = &(*next_clause)->conditions; - next_clause = &(*next_clause)->next; - } + ++(*memaddr); + } while (1); } void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) { const uint8_t was_value = parse->is_value; + const rc_condition_t* condition; parse->is_value = 1; /* if it starts with a condition flag (M: A: B: C:), parse the conditions */ @@ -188,10 +216,27 @@ void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_st else rc_parse_legacy_value(self, memaddr, parse); - self->name = "(unnamed)"; - self->value.value = self->value.prior = 0; - self->value.changed = 0; - self->next = 0; + if (parse->offset >= 0) { + self->name = "(unnamed)"; + self->value.value = self->value.prior = 0; + self->value.memref_type = RC_MEMREF_TYPE_VALUE; + self->value.changed = 0; + self->next = NULL; + + for (condition = self->conditions->conditions; condition; condition = condition->next) { + if (condition->type == RC_CONDITION_MEASURED) { + if (rc_operand_is_float(&condition->operand1)) { + self->value.size = RC_MEMSIZE_FLOAT; + self->value.type = RC_VALUE_TYPE_FLOAT; + } + else { + self->value.size = RC_MEMSIZE_32_BITS; + self->value.type = RC_VALUE_TYPE_UNSIGNED; + } + break; + } + } + } parse->is_value = was_value; } @@ -391,16 +436,9 @@ void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_Sta } void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref) { + /* raw value is always u32, type can mark it as something else */ value->value.u32 = memref->value; - - if (memref->size == RC_MEMSIZE_VARIABLE) { - /* a variable can be any of the supported types, but the raw data was copied into u32 */ - value->type = memref->type; - } - else { - /* not a variable, only u32 is supported */ - value->type = RC_VALUE_TYPE_UNSIGNED; - } + value->type = memref->type; } void rc_typed_value_convert(rc_typed_value_t* value, char new_type) { @@ -661,56 +699,56 @@ void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amo switch (amount->type) { - case RC_VALUE_TYPE_UNSIGNED: - if (amount->value.u32 == 0) { /* divide by zero */ - value->type = RC_VALUE_TYPE_NONE; - return; - } + case RC_VALUE_TYPE_UNSIGNED: + if (amount->value.u32 == 0) { /* divide by zero */ + value->type = RC_VALUE_TYPE_NONE; + return; + } - switch (value->type) { - case RC_VALUE_TYPE_UNSIGNED: /* integer math */ - value->value.u32 %= amount->value.u32; - return; - case RC_VALUE_TYPE_SIGNED: /* integer math */ - value->value.i32 %= (int)amount->value.u32; - return; - case RC_VALUE_TYPE_FLOAT: - amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); + switch (value->type) { + case RC_VALUE_TYPE_UNSIGNED: /* integer math */ + value->value.u32 %= amount->value.u32; + return; + case RC_VALUE_TYPE_SIGNED: /* integer math */ + value->value.i32 %= (int)amount->value.u32; + return; + case RC_VALUE_TYPE_FLOAT: + amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); + break; + default: + value->type = RC_VALUE_TYPE_NONE; + return; + } break; - default: - value->type = RC_VALUE_TYPE_NONE; - return; - } - break; - case RC_VALUE_TYPE_SIGNED: - if (amount->value.i32 == 0) { /* divide by zero */ - value->type = RC_VALUE_TYPE_NONE; - return; - } + case RC_VALUE_TYPE_SIGNED: + if (amount->value.i32 == 0) { /* divide by zero */ + value->type = RC_VALUE_TYPE_NONE; + return; + } + + switch (value->type) { + case RC_VALUE_TYPE_SIGNED: /* integer math */ + value->value.i32 %= amount->value.i32; + return; + case RC_VALUE_TYPE_UNSIGNED: /* integer math */ + value->value.u32 %= (unsigned)amount->value.i32; + return; + case RC_VALUE_TYPE_FLOAT: + amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); + break; + default: + value->type = RC_VALUE_TYPE_NONE; + return; + } + break; - switch (value->type) { - case RC_VALUE_TYPE_SIGNED: /* integer math */ - value->value.i32 %= amount->value.i32; - return; - case RC_VALUE_TYPE_UNSIGNED: /* integer math */ - value->value.u32 %= (unsigned)amount->value.i32; - return; case RC_VALUE_TYPE_FLOAT: - amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT); break; + default: value->type = RC_VALUE_TYPE_NONE; return; - } - break; - - case RC_VALUE_TYPE_FLOAT: - break; - - default: - value->type = RC_VALUE_TYPE_NONE; - return; } if (amount->value.f32 == 0.0) { /* divide by zero */ diff --git a/test/Makefile b/test/Makefile index a192d38f..3d82a4e1 100644 --- a/test/Makefile +++ b/test/Makefile @@ -74,6 +74,7 @@ OBJ=$(RC_SRC)/rc_compat.o \ rcheevos/test_richpresence.o \ rcheevos/test_runtime.o \ rcheevos/test_runtime_progress.o \ + rcheevos/test_timing.o \ rcheevos/test_trigger.o \ rcheevos/test_value.o \ rurl/test_url.o \ diff --git a/test/rcheevos/test_rc_validate.c b/test/rcheevos/test_rc_validate.c index 85833494..337e548a 100644 --- a/test/rcheevos/test_rc_validate.c +++ b/test/rcheevos/test_rc_validate.c @@ -354,6 +354,7 @@ void test_conflicting_conditions() { TEST_PARAMS2(test_validate_trigger, "N:0xH0000=1_0xH0001=1_N:0xH0000=2_0xH0001=2", ""); TEST_PARAMS2(test_validate_trigger, "N:0xH0000=1_0xH0001=1_N:0xH0000=2_0xH0001=1", ""); /* technically conflicting, but hard to detect */ TEST_PARAMS2(test_validate_trigger, "N:0xH0000=1_0xH0001=1_N:0xH0000=1_0xH0001=2", "Condition 4: Conflicts with Condition 2"); + TEST_PARAMS2(test_validate_trigger, "0xH0000=0_N:0xH0000!=0_0xH0000=2", ""); } void test_redundant_conditions() { diff --git a/test/rcheevos/test_value.c b/test/rcheevos/test_value.c index b5067b65..e4657e0b 100644 --- a/test/rcheevos/test_value.c +++ b/test/rcheevos/test_value.c @@ -554,8 +554,9 @@ void test_value(void) { TEST_PARAMS2(test_evaluate_value, "0xH0001*100_0xH0002*0.5_0xL0003", 0x12 * 100 + 0x34 / 2 + 0x0B); TEST_PARAMS2(test_evaluate_value, "0xH0001$0xH0002", 0x34); TEST_PARAMS2(test_evaluate_value, "0xH0001_0xH0004*3$0xH0002*0xL0003", 0x34 * 0x0B); - TEST_PARAMS2(test_evaluate_value, "0xH001_V-20", 0x12 - 20); + TEST_PARAMS2(test_evaluate_value, "0xH0001_V-20", 0x12 - 20); TEST_PARAMS2(test_evaluate_value, "0xH0001_H10", 0x12 + 0x10); + TEST_PARAMS2(test_evaluate_value, "100-0xH0002", 100 - 0x34); TEST_PARAMS2(test_evaluate_value, "0xh0000*-1_99_0xh0001*-100_5900", 4199); TEST_PARAMS2(test_evaluate_value, "v5900_0xh0000*-1.0_0xh0001*-100.0", 4100); TEST_PARAMS2(test_evaluate_value, "v5900_0xh0000*v-1_0xh0001*v-100", 4100); diff --git a/test/test.c b/test/test.c index 6ffe5bd7..3142c9eb 100644 --- a/test/test.c +++ b/test/test.c @@ -17,6 +17,7 @@ #define TIMING_TEST 0 +#if !defined(TIMING_TEST) || TIMING_TEST == 0 static void test_lua(void) { { /*------------------------------------------------------------------------ @@ -49,6 +50,7 @@ static void test_lua(void) { #endif /* RC_DISABLE_LUA */ } } +#endif extern void test_timing();