urt-rs / crates /urt-gameplay /src /method_stats.rs
nmarshall's picture
gameplay: port method counter updates
b0607b0
Raw History Blame Contribute Delete
5 kB
//! Numeric278733/278834 counters; aggregation/reporting remains caller work.
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Side {
Victim,
Actor,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Plan {
pub increments: Vec<(Side, u32)>,
pub aggregate_pair: bool,
pub report: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatsError {
MissingCounter,
}
pub fn slot(method: i32) -> Option<u8> {
Some(match method {
14 => 9,
15 => 8,
16 => 11,
17 => 5,
18 => 4,
19 => 2,
20 => 0,
21 => 7,
28 => 6,
30 => 1,
36 => 10,
38 => 3,
39 => 12,
40 => 13,
41 => 14,
42 => 15,
43 => 16,
44 => 17,
45 => 18,
_ => return None,
})
}
/// All participants are existing clients with valid source IDs. Self aliases
/// resolve to the actor's storage; an absent actor exits before any requests.
pub fn plan(method: i32, self_hit: bool, actor_absent: bool) -> Plan {
use Side::{Actor, Victim};
let mut result = Plan {
increments: vec![],
aggregate_pair: false,
report: !actor_absent,
};
if actor_absent {
return result;
}
let victim = if self_hit { Actor } else { Victim };
if let Some(slot) = slot(method) {
result.increments = vec![
(Actor, 916 + 76 * u32::from(slot)),
(victim, 920 + 76 * u32::from(slot)),
];
result.aggregate_pair = true;
return result;
}
let single = match method {
0..=5 | 8 | 9 => Some(2512),
6 => Some(2488),
7 => Some(2492),
31 => Some(2504),
32 | 33 | 35 => Some(2508),
47 => Some(2496),
22 if self_hit => Some(2460),
25 if self_hit => Some(2452),
_ => None,
};
if let Some(offset) = single {
result.increments.push((victim, offset));
return result;
}
let pair = match method {
12 => Some((2396, 2468)),
13 => Some((2400, 2472)),
22 => Some((2384, 2456)),
23 => Some((2408, 2480)),
24 => Some((2404, 2476)),
25 => Some((2376, 2448)),
34 => Some((2428, 2500)),
37 => Some((2392, 2464)),
48 => Some((2412, 2484)),
_ => None,
};
if let Some((a, v)) = pair {
result.increments = vec![(Actor, a), (victim, v)];
result.aggregate_pair = method != 34;
}
result
}
/// Validate storage before mutation, then apply source wrapping increments.
/// The returned plan describes reporting intent without executing callbacks.
pub fn apply(
method: i32,
self_hit: bool,
actor_absent: bool,
actor: &mut BTreeMap<u32, u32>,
victim: &mut BTreeMap<u32, u32>,
) -> Result<Plan, StatsError> {
let p = plan(method, self_hit, actor_absent);
for (side, offset) in &p.increments {
if !(if *side == Side::Actor {
&*actor
} else {
&*victim
})
.contains_key(offset)
{
return Err(StatsError::MissingCounter);
}
}
for (side, offset) in &p.increments {
let counters = if *side == Side::Actor {
&mut *actor
} else {
&mut *victim
};
let v = counters.get_mut(offset).unwrap();
*v = v.wrapping_add(1);
}
Ok(p)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_slots_and_invalid_signed_extremes() {
let slots: std::collections::BTreeSet<_> = (0..50).filter_map(slot).collect();
assert_eq!(slots, (0..19).collect());
assert_eq!(slot(i32::MIN), None);
assert_eq!(slot(i32::MAX), None);
}
#[test]
fn self_alias_and_fallback_exceptions() {
assert_eq!(plan(22, true, false).increments, vec![(Side::Actor, 2460)]);
assert_eq!(plan(25, true, false).increments, vec![(Side::Actor, 2452)]);
assert!(!plan(34, false, false).aggregate_pair);
assert!(plan(20, false, false).aggregate_pair);
}
#[test]
fn apply_wraps_and_keeps_other_counters() {
let mut actor = BTreeMap::from([(916, u32::MAX), (999, 17)]);
let mut victim = BTreeMap::from([(920, 3)]);
apply(20, false, false, &mut actor, &mut victim).unwrap();
assert_eq!(actor, BTreeMap::from([(916, 0), (999, 17)]));
assert_eq!(victim[&920], 4);
}
#[test]
fn absent_actor_and_missing_storage_do_not_partially_mutate() {
let mut a = BTreeMap::from([(916, 4)]);
let mut v = BTreeMap::new();
assert_eq!(
apply(20, false, false, &mut a, &mut v),
Err(StatsError::MissingCounter)
);
assert_eq!(a[&916], 4);
let p = apply(20, false, true, &mut a, &mut v).unwrap();
assert!(!p.report);
assert!(p.increments.is_empty());
assert_eq!(a[&916], 4);
}
}