diff --git a/include/rc_runtime_types.h b/include/rc_runtime_types.h index b57f3443..b198b16d 100644 --- a/include/rc_runtime_types.h +++ b/include/rc_runtime_types.h @@ -144,23 +144,16 @@ RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand); /* types */ enum { - /* NOTE: this enum is ordered to optimize the switch statements in rc_test_condset_internal. the values may change between releases */ - - /* non-combining conditions (third switch) */ RC_CONDITION_STANDARD, /* this should always be 0 */ RC_CONDITION_PAUSE_IF, RC_CONDITION_RESET_IF, RC_CONDITION_MEASURED_IF, RC_CONDITION_TRIGGER, - RC_CONDITION_MEASURED, /* measured also appears in the first switch, so place it at the border between them */ - - /* modifiers (first switch) */ - RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */ + RC_CONDITION_MEASURED, + RC_CONDITION_ADD_SOURCE, RC_CONDITION_SUB_SOURCE, RC_CONDITION_ADD_ADDRESS, RC_CONDITION_REMEMBER, - - /* logic flags (second switch) */ RC_CONDITION_ADD_HITS, RC_CONDITION_SUB_HITS, RC_CONDITION_RESET_NEXT_IF, diff --git a/src/rcheevos/condset.c b/src/rcheevos/condset.c index 36297d3e..d7cf2843 100644 --- a/src/rcheevos/condset.c +++ b/src/rcheevos/condset.c @@ -264,7 +264,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { case RC_CONDITION_ADD_SOURCE: case RC_CONDITION_SUB_SOURCE: case RC_CONDITION_REMEMBER: - /* these conditions don't require a right hand size (implied *1) */ + /* these conditions don't require a right hand side (implied *1) */ break; case RC_CONDITION_MEASURED: @@ -390,64 +390,25 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) { return self; } -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; - - measured_value.type = RC_VALUE_TYPE_NONE; - measured_from_hits = 0; - can_measure = 1; - total_hits = 0; - - set_valid = 1; - and_next = 1; - or_next = 0; - reset_next = 0; - - for (; condition < condition_end; ++condition) { - /* STEP 1: process modifier conditions */ - switch (condition->type) { - case RC_CONDITION_ADD_SOURCE: - case RC_CONDITION_SUB_SOURCE: - case RC_CONDITION_ADD_ADDRESS: - case RC_CONDITION_REMEMBER: - /* these are all managed by rc_modified_memref_t now */ - continue; - - case RC_CONDITION_MEASURED: - if (condition->required_hits == 0 && can_measure) { - /* Measured condition without a hit target measures the value of the left operand */ - rc_evaluate_operand(&measured_value, &condition->operand1, eval_state); - } - break; - - default: - break; - } - - /* STEP 2: evaluate the current condition */ - condition->is_true = (uint8_t)rc_test_condition(condition, eval_state); +static uint8_t rc_condset_evaluate_condition_no_add_hits(rc_condition_t* condition, rc_eval_state_t* eval_state) { + /* evaluate the current condition */ + uint8_t cond_valid = (uint8_t)rc_test_condition(condition, eval_state); + condition->is_true = cond_valid; - /* apply logic flags and reset them for the next condition */ - cond_valid = condition->is_true; - cond_valid &= and_next; - cond_valid |= or_next; - and_next = 1; - or_next = 0; + if (eval_state->reset_next) { + /* previous ResetNextIf resets the hit count on this condition and prevents it from being true */ + eval_state->was_cond_reset |= (condition->current_hits != 0); - if (reset_next) { - /* previous ResetNextIf resets the hit count on this condition and prevents it from being true */ - if (condition->current_hits) - eval_state->was_cond_reset = 1; + condition->current_hits = 0; + cond_valid = 0; + } + else { + /* apply chained logic flags */ + cond_valid &= eval_state->and_next; + cond_valid |= eval_state->or_next; - condition->current_hits = 0; - cond_valid = 0; - } - else if (cond_valid) { - /* true conditions should update hit count */ + if (cond_valid) { + /* true conditions should update their hit count */ eval_state->has_hits = 1; if (condition->required_hits == 0) { @@ -468,137 +429,215 @@ static int rc_test_condset_internal(rc_condition_t* condition, uint32_t num_cond eval_state->has_hits = 1; cond_valid = (condition->current_hits == condition->required_hits); } + } - /* STEP 3: handle logic flags */ - switch (condition->type) { - case RC_CONDITION_ADD_HITS: - eval_state->add_hits += condition->current_hits; - reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */ - continue; + /* reset chained logic flags for the next condition */ + eval_state->and_next = 1; + eval_state->or_next = 0; - case RC_CONDITION_SUB_HITS: - eval_state->add_hits -= condition->current_hits; - reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */ - continue; + return cond_valid; +} - case RC_CONDITION_RESET_NEXT_IF: - reset_next = cond_valid; - continue; +static uint32_t rc_condset_evaluate_total_hits(rc_condition_t* condition, rc_eval_state_t* eval_state) { + uint32_t total_hits = condition->current_hits; - case RC_CONDITION_AND_NEXT: - and_next = cond_valid; - continue; + if (condition->required_hits != 0) { + /* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */ + const int32_t signed_hits = (int32_t)condition->current_hits + eval_state->add_hits; + total_hits = (signed_hits >= 0) ? (uint32_t)signed_hits : 0; + } + else { + /* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it. + complex condition will only be true if the current condition is true */ + } - case RC_CONDITION_OR_NEXT: - or_next = cond_valid; - continue; + eval_state->add_hits = 0; - default: - break; - } + return total_hits; +} - /* reset logic flags for next condition */ - reset_next = 0; +static uint8_t rc_condset_evaluate_condition(rc_condition_t* condition, rc_eval_state_t* eval_state) { + uint8_t cond_valid = rc_condset_evaluate_condition_no_add_hits(condition, eval_state); - /* STEP 4: calculate total hits */ - total_hits = condition->current_hits; + if (eval_state->add_hits != 0 && condition->required_hits != 0) { + uint32_t total_hits = rc_condset_evaluate_total_hits(condition, eval_state); + cond_valid = (total_hits >= condition->required_hits); + } - if (eval_state->add_hits) { - if (condition->required_hits != 0) { - /* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */ - const int signed_hits = (int)condition->current_hits + eval_state->add_hits; - total_hits = (signed_hits >= 0) ? (unsigned)signed_hits : 0; - cond_valid = (total_hits >= condition->required_hits); - } - else { - /* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it. - complex condition will only be true if the current condition is true */ - } + /* reset logic flags for the next condition */ + eval_state->reset_next = 0; - eval_state->add_hits = 0; - } + return cond_valid; +} - /* STEP 5: handle special flags */ - switch (condition->type) { - case RC_CONDITION_PAUSE_IF: - /* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */ - if (cond_valid) - return 1; +static void rc_condset_evaluate_standard(rc_condition_t* condition, rc_eval_state_t* eval_state) { + const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state); - /* if we make it to the end of the function, make sure we indicate that nothing matched. if we do find - a later PauseIf match, it'll automatically return true via the previous condition. */ - set_valid = 0; + eval_state->is_true &= cond_valid; + eval_state->is_primed &= cond_valid; - if (condition->required_hits == 0) { - /* PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match */ - condition->current_hits = 0; - } - else { - /* PauseIf has a HitCount that hasn't been met, ignore it for now. */ - } + if (!cond_valid && eval_state->can_short_curcuit) + eval_state->stop_processing = 1; +} - continue; +static void rc_condset_evaluate_pause_if(rc_condition_t* condition, rc_eval_state_t* eval_state) { + const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state); - case RC_CONDITION_RESET_IF: - if (cond_valid) { - eval_state->was_reset = 1; /* let caller know to reset all hit counts */ + if (cond_valid) { + eval_state->is_paused = 1; - if (can_short_circuit) - return 0; + /* set cannot be valid if it's paused */ + eval_state->is_true = eval_state->is_primed = 0; - set_valid = 0; /* cannot be valid if we've hit a reset condition */ - } - continue; + /* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */ + eval_state->stop_processing = 1; + } + else if (condition->required_hits == 0) { + /* PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match */ + condition->current_hits = 0; + } + else { + /* PauseIf has a HitCount that hasn't been met, ignore it for now. */ + } +} - case RC_CONDITION_MEASURED: - if (condition->required_hits != 0) { - /* if there's a hit target, capture the current hits for recording Measured value later */ - measured_from_hits = 1; - if (can_measure) { - measured_value.value.u32 = total_hits; - measured_value.type = RC_VALUE_TYPE_UNSIGNED; - } - } - break; +static void rc_condset_evaluate_reset_if(rc_condition_t* condition, rc_eval_state_t* eval_state) { + const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state); - case RC_CONDITION_MEASURED_IF: - if (!cond_valid) { - measured_value.value.u32 = 0; - measured_value.type = RC_VALUE_TYPE_UNSIGNED; - can_measure = 0; - } - break; + if (cond_valid) { + /* set cannot be valid if we've hit a reset condition */ + eval_state->is_true = eval_state->is_primed = 0; - case RC_CONDITION_TRIGGER: - if (!cond_valid && can_short_circuit) - return 0; + /* let caller know to reset all hit counts */ + eval_state->was_reset = 1; - /* update truthiness of set, but do not update truthiness of primed state */ - set_valid &= cond_valid; - continue; + /* can stop processing once an active ResetIf is encountered */ + eval_state->stop_processing = 1; + } +} - default: - break; - } +static void rc_condset_evaluate_trigger(rc_condition_t* condition, rc_eval_state_t* eval_state) { + const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state); - /* STEP 5: update overall truthiness of set and primed state */ - eval_state->primed &= cond_valid; - set_valid &= cond_valid; + eval_state->is_true &= cond_valid; +} - if (!cond_valid && can_short_circuit) - return 0; +static void rc_condset_evaluate_measured(rc_condition_t* condition, rc_eval_state_t* eval_state) { + if (condition->required_hits == 0) { + rc_condset_evaluate_standard(condition, eval_state); + + /* Measured condition without a hit target measures the value of the left operand */ + rc_evaluate_operand(&eval_state->measured_value, &condition->operand1, eval_state); + eval_state->measured_from_hits = 0; + } + else { + /* this largely mimicks rc_condset_evaluate_condition, but captures the total_hits */ + uint8_t cond_valid = rc_condset_evaluate_condition_no_add_hits(condition, eval_state); + const uint32_t total_hits = rc_condset_evaluate_total_hits(condition, eval_state); + + cond_valid = (total_hits >= condition->required_hits); + eval_state->is_true &= cond_valid; + eval_state->is_primed &= cond_valid; + + /* if there is a hit target, capture the current hits */ + eval_state->measured_value.value.u32 = total_hits; + eval_state->measured_value.type = RC_VALUE_TYPE_UNSIGNED; + eval_state->measured_from_hits = 1; + + /* reset logic flags for the next condition */ + eval_state->reset_next = 0; } +} - if (measured_value.type != RC_VALUE_TYPE_NONE) { - /* if no previous Measured value was captured, or the new one is greater, keep the new one */ - if (eval_state->measured_value.type == RC_VALUE_TYPE_NONE || - rc_typed_value_compare(&measured_value, &eval_state->measured_value, RC_OPERATOR_GT)) { - memcpy(&eval_state->measured_value, &measured_value, sizeof(measured_value)); - eval_state->measured_from_hits = (uint8_t)measured_from_hits; +static void rc_condset_evaluate_measured_if(rc_condition_t* condition, rc_eval_state_t* eval_state) { + rc_condset_evaluate_standard(condition, eval_state); + + eval_state->can_measure &= condition->is_true; +} + +static void rc_condset_evaluate_add_hits(rc_condition_t* condition, rc_eval_state_t* eval_state) { + rc_condset_evaluate_condition_no_add_hits(condition, eval_state); + + eval_state->add_hits += (int32_t)condition->current_hits; + + /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */ + eval_state->reset_next = 0; +} + +static void rc_condset_evaluate_sub_hits(rc_condition_t* condition, rc_eval_state_t* eval_state) { + rc_condset_evaluate_condition_no_add_hits(condition, eval_state); + + eval_state->add_hits -= (int32_t)condition->current_hits; + + /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */ + eval_state->reset_next = 0; +} + +static void rc_condset_evaluate_reset_next_if(rc_condition_t* condition, rc_eval_state_t* eval_state) { + eval_state->reset_next = rc_condset_evaluate_condition_no_add_hits(condition, eval_state); +} + +static void rc_condset_evaluate_and_next(rc_condition_t* condition, rc_eval_state_t* eval_state) { + eval_state->and_next = rc_condset_evaluate_condition_no_add_hits(condition, eval_state); +} + +static void rc_condset_evaluate_or_next(rc_condition_t* condition, rc_eval_state_t* eval_state) { + eval_state->or_next = rc_condset_evaluate_condition_no_add_hits(condition, eval_state); +} + +static void 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; + for (; condition < condition_end; ++condition) { + switch (condition->type) { + case RC_CONDITION_STANDARD: + rc_condset_evaluate_standard(condition, eval_state); + break; + case RC_CONDITION_PAUSE_IF: + rc_condset_evaluate_pause_if(condition, eval_state); + break; + case RC_CONDITION_RESET_IF: + rc_condset_evaluate_reset_if(condition, eval_state); + break; + case RC_CONDITION_TRIGGER: + rc_condset_evaluate_trigger(condition, eval_state); + break; + case RC_CONDITION_MEASURED: + rc_condset_evaluate_measured(condition, eval_state); + break; + case RC_CONDITION_MEASURED_IF: + rc_condset_evaluate_measured_if(condition, eval_state); + break; + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_SUB_SOURCE: + case RC_CONDITION_ADD_ADDRESS: + case RC_CONDITION_REMEMBER: + /* these are handled by rc_modified_memref_t */ + break; + case RC_CONDITION_ADD_HITS: + rc_condset_evaluate_add_hits(condition, eval_state); + break; + case RC_CONDITION_SUB_HITS: + rc_condset_evaluate_sub_hits(condition, eval_state); + break; + case RC_CONDITION_RESET_NEXT_IF: + rc_condset_evaluate_reset_next_if(condition, eval_state); + break; + case RC_CONDITION_AND_NEXT: + rc_condset_evaluate_and_next(condition, eval_state); + break; + case RC_CONDITION_OR_NEXT: + rc_condset_evaluate_or_next(condition, eval_state); + break; + default: + eval_state->stop_processing = 1; + eval_state->is_true = eval_state->is_primed = 0; + break; } - } - return set_valid; + if (eval_state->stop_processing && can_short_circuit) + break; + } } rc_condition_t* rc_condset_get_conditions(rc_condset_t* self) { @@ -610,20 +649,31 @@ rc_condition_t* rc_condset_get_conditions(rc_condset_t* self) { int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { rc_condition_t* conditions; - int result = 1; /* true until proven otherwise */ - eval_state->primed = 1; + /* reset the processing state before processing each condset. do not reset the result state. */ + eval_state->measured_value.type = RC_VALUE_TYPE_NONE; eval_state->add_hits = 0; - + eval_state->is_true = 1; + eval_state->is_primed = 1; + eval_state->is_paused = 0; + eval_state->can_measure = 1; + eval_state->measured_from_hits = 0; + eval_state->and_next = 1; + eval_state->or_next = 0; + eval_state->reset_next = 0; + eval_state->stop_processing = 0; + + /* the conditions array is allocated immediately after the rc_condset_t, without a separate pointer */ 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); + /* one or more Pause conditions exist. if any of them are true (eval_state->is_paused), + * stop processing this group */ + rc_test_condset_internal(conditions, self->num_pause_conditions, eval_state, 1); + self->is_paused = eval_state->is_paused; if (self->is_paused) { /* condset is paused. stop processing immediately. */ - eval_state->primed = 0; return 0; } @@ -631,36 +681,49 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) { } 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); + /* one or more Reset conditions exists. if any of them are true (eval_state->was_reset), + * we'll skip some of the later steps */ + 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 */ + /* 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); + 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); + /* the measured value must be calculated every frame, even if hit counts will be reset */ + rc_test_condset_internal(conditions, self->num_measured_conditions, eval_state, 0); conditions += self->num_measured_conditions; + + if (eval_state->measured_value.type != RC_VALUE_TYPE_NONE) { + /* if a MeasuredIf was false (!eval_state->can_measure), or the measured + * value is a hitcount and a ResetIf is true, zero out the measured value */ + if (!eval_state->can_measure || + (eval_state->measured_from_hits && eval_state->was_reset)) { + eval_state->measured_value.type = RC_VALUE_TYPE_UNSIGNED; + eval_state->measured_value.value.u32 = 0; + } + } + + conditions += self->num_pause_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); + /* the remaining conditions only need to be evaluated if the rest of the condset is true */ + if (eval_state->is_true) + 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); + rc_test_condset_internal(conditions, self->num_other_conditions, eval_state, eval_state->can_short_curcuit); } - return result; + return eval_state->is_true; } void rc_reset_condset(rc_condset_t* self) { diff --git a/src/rcheevos/rc_internal.h b/src/rcheevos/rc_internal.h index beb54380..bfdc9fa6 100644 --- a/src/rcheevos/rc_internal.h +++ b/src/rcheevos/rc_internal.h @@ -143,18 +143,30 @@ enum { #define RC_OPERAND_NONE 0xFF typedef struct { - int32_t add_hits; /* AddHits */ - + /* memory accessors */ rc_peek_t peek; void* peek_userdata; lua_State* L; - rc_typed_value_t measured_value; /* Measured */ - uint8_t was_reset; /* ResetIf triggered */ - uint8_t has_hits; /* one of more hit counts is non-zero */ - uint8_t primed; /* true if all non-Trigger conditions are true */ + /* processing state */ + rc_typed_value_t measured_value; /* captured Measured value */ + int32_t add_hits; /* AddHits/SubHits accumulator */ + uint8_t is_true; /* true if all conditions are true */ + uint8_t is_primed; /* true if all non-Trigger conditions are true */ + uint8_t is_paused; /* true if one or more PauseIf conditions is true */ + uint8_t can_measure; /* false if the measured value should be ignored */ 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 and_next; /* true if the previous condition was AndNext true */ + uint8_t or_next; /* true if the previous condition was OrNext true */ + uint8_t reset_next; /* true if the previous condition was ResetNextIf true */ + uint8_t stop_processing; /* true to abort the processing loop */ + + /* result state */ + uint8_t has_hits; /* true if one of more hit counts is non-zero */ + uint8_t was_reset; /* true if one or more ResetIf conditions is true */ + uint8_t was_cond_reset; /* true if one or more ResetNextIf conditions is true */ + + /* control settings */ uint8_t can_short_curcuit; /* allows logic processing to stop as soon as a false condition is encountered */ } rc_eval_state_t; diff --git a/src/rcheevos/trigger.c b/src/rcheevos/trigger.c index 681190e7..83ddd668 100644 --- a/src/rcheevos/trigger.c +++ b/src/rcheevos/trigger.c @@ -126,6 +126,8 @@ static void rc_reset_trigger_hitcounts(rc_trigger_t* self) { int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) { rc_eval_state_t eval_state; rc_condset_t* condset; + rc_typed_value_t measured_value; + int measured_from_hits = 0; int ret; char is_paused; char is_primed; @@ -158,10 +160,17 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* eval_state.peek_userdata = ud; eval_state.L = L; + measured_value.type = RC_VALUE_TYPE_NONE; + if (self->requirement != NULL) { ret = rc_test_condset(self->requirement, &eval_state); - is_paused = self->requirement->is_paused; - is_primed = eval_state.primed; + is_paused = eval_state.is_paused; + is_primed = eval_state.is_primed; + + if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) { + memcpy(&measured_value, &eval_state.measured_value, sizeof(measured_value)); + measured_from_hits = eval_state.measured_from_hits; + } } else { ret = 1; is_paused = 0; @@ -176,8 +185,17 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* do { sub |= rc_test_condset(condset, &eval_state); - sub_paused &= condset->is_paused; - sub_primed |= eval_state.primed; + sub_paused &= eval_state.is_paused; + sub_primed |= eval_state.is_primed; + + if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) { + /* if no previous Measured value was captured, or the new one is greater, keep the new one */ + if (measured_value.type == RC_VALUE_TYPE_NONE || + rc_typed_value_compare(&eval_state.measured_value, &measured_value, RC_OPERATOR_GT)) { + memcpy(&measured_value, &eval_state.measured_value, sizeof(measured_value)); + measured_from_hits = eval_state.measured_from_hits; + } + } condset = condset->next; } while (condset); @@ -192,15 +210,15 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* if paused, the measured value may not be captured, keep the old value */ if (!is_paused) { - rc_typed_value_convert(&eval_state.measured_value, RC_VALUE_TYPE_UNSIGNED); - self->measured_value = eval_state.measured_value.value.u32; + rc_typed_value_convert(&measured_value, RC_VALUE_TYPE_UNSIGNED); + self->measured_value = measured_value.value.u32; } /* if any ResetIf condition was true, reset the hit counts */ if (eval_state.was_reset) { /* if the measured value came from a hit count, reset it. do this before calling * rc_reset_trigger_hitcounts in case we need to call rc_condset_is_measured_from_hitcount */ - if (eval_state.measured_from_hits) { + if (measured_from_hits) { self->measured_value = 0; } else if (is_paused && self->measured_value) { diff --git a/src/rcheevos/value.c b/src/rcheevos/value.c index edfbb7d2..c29f6a20 100644 --- a/src/rcheevos/value.c +++ b/src/rcheevos/value.c @@ -299,25 +299,21 @@ int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t * NOTE: ResetIf only affects the current condset when used in values! */ rc_reset_condset(condset); - - /* if the measured value came from a hit count, reset it too */ - if (eval_state.measured_from_hits) { - eval_state.measured_value.value.u32 = 0; - eval_state.measured_value.type = RC_VALUE_TYPE_UNSIGNED; - } } - if (!valid) { - /* capture the first valid measurement */ - memcpy(value, &eval_state.measured_value, sizeof(*value)); - valid = 1; - } - else { - /* multiple condsets are currently only used for the MAX_OF operation. - * only keep the condset's value if it's higher than the current highest value. - */ - if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT)) + if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) { + if (!valid) { + /* capture the first valid measurement, which may be negative */ memcpy(value, &eval_state.measured_value, sizeof(*value)); + valid = 1; + } + else { + /* multiple condsets are currently only used for the MAX_OF operation. + * only keep the condset's value if it's higher than the current highest value. + */ + if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT)) + memcpy(value, &eval_state.measured_value, sizeof(*value)); + } } } diff --git a/test/rcheevos/test_condset.c b/test/rcheevos/test_condset.c index fa143e01..f9f56455 100644 --- a/test/rcheevos/test_condset.c +++ b/test/rcheevos/test_condset.c @@ -4006,6 +4006,43 @@ static void test_addaddress_indirect_pointer_with_delta() assert_hit_count(condset, 3, 2); } +static void test_addaddress_indirect_pointer_from_memory() { + uint8_t ram[] = { 0x01, 0x01, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_condset_t* condset; + rc_condset_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* byte(byte(0x0000) + byte(0x0001)) == 22 --- byte(0x0000) = pointer, byte(0x0001) = index */ + assert_parse_condset(&condset, &memrefs, buffer, "I:0xH0000+0xH0001_0xH0000=22"); + + /* initially, byte(1 + 1) == 22, false */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* pointed-at value is correct */ + ram[2] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* point to new value */ + ram[0] = 2; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* new pointed-at value is correct */ + ram[3] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 1); + + /* point to new value */ + ram[1] = 2; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* new pointed-at value is correct */ + ram[4] = 22; + assert_evaluate_condset(condset, memrefs, &memory, 1); +} + static void test_addaddress_indirect_constant() { uint8_t ram[] = { 0x01, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; @@ -4524,6 +4561,7 @@ void test_condset(void) { TEST(test_addaddress_indirect_pointer_out_of_range); TEST(test_addaddress_indirect_pointer_multiple); TEST(test_addaddress_indirect_pointer_with_delta); + TEST(test_addaddress_indirect_pointer_from_memory); TEST(test_addaddress_indirect_constant); TEST(test_addaddress_pointer_data_size_differs_from_pointer_size); TEST(test_addaddress_double_indirection); diff --git a/test/rcheevos/test_richpresence.c b/test/rcheevos/test_richpresence.c index 05947be9..82d05648 100644 --- a/test/rcheevos/test_richpresence.c +++ b/test/rcheevos/test_richpresence.c @@ -506,6 +506,26 @@ static void test_macro_value_divide_by_self() { assert_richpresence_output(richpresence, &memory, "Result is 0"); } +static void test_macro_value_remember_recall() { + uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; + memory_t memory; + rc_richpresence_t* richpresence; + char buffer[1024]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* sneaky trick to turn any non-zero value into 1 */ + assert_parse_richpresence(&richpresence, buffer, "Format:Value\nFormatType=VALUE\n\nDisplay:\nResult is @Value(A:0xH02*2_K:1_M:{recall}*3)"); + assert_richpresence_output(richpresence, &memory, "Result is 315"); + + ram[2] = 1; + assert_richpresence_output(richpresence, &memory, "Result is 9"); + + ram[2] = 0; + assert_richpresence_output(richpresence, &memory, "Result is 3"); +} + static void test_macro_hundreds() { uint8_t ram[] = { 0x00, 0x12, 0x34, 0xAB, 0x56 }; memory_t memory; @@ -1334,6 +1354,7 @@ void test_richpresence(void) { TEST(test_macro_value_from_indirect); TEST(test_macro_value_divide_by_zero); TEST(test_macro_value_divide_by_self); + TEST(test_macro_value_remember_recall); /* hundreds macro */ TEST(test_macro_hundreds);