use crate::context::StatContext;
use crate::error::StatError;
use crate::numeric::StatValue;
use crate::stat_id::StatId;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum TransformPhase {
Additive,
Multiplicative,
Final,
Custom(u8),
}
impl TransformPhase {
pub fn value(self) -> u8 {
match self {
TransformPhase::Additive => 0,
TransformPhase::Multiplicative => 1,
TransformPhase::Final => 2,
TransformPhase::Custom(n) => n.max(3),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StackRule {
Override,
Additive,
Multiplicative,
Diminishing { k: StatValue },
Min,
Max,
MinMax,
}
impl StackRule {
pub fn priority(self) -> u8 {
match self {
StackRule::Override => 0,
StackRule::Additive => 1,
StackRule::Multiplicative => 2,
StackRule::Diminishing { .. } => 3,
StackRule::Min => 4,
StackRule::Max => 5,
StackRule::MinMax => 6,
}
}
}
impl PartialOrd for StackRule {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.priority().cmp(&other.priority()))
}
}
pub struct TransformEntry {
pub phase: TransformPhase,
pub rule: StackRule,
pub transform: Box<dyn StatTransform>,
}
impl std::fmt::Debug for TransformEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransformEntry")
.field("phase", &self.phase)
.field("rule", &self.rule)
.field("transform", &format!("<{}>", self.transform.description()))
.finish()
}
}
pub trait ClampBounds {
fn min_bound(&self) -> Option<StatValue>;
fn max_bound(&self) -> Option<StatValue>;
}
pub fn infer_stack_rule(transform: &dyn StatTransform) -> StackRule {
match transform.phase() {
TransformPhase::Additive => StackRule::Additive,
TransformPhase::Multiplicative => StackRule::Multiplicative,
TransformPhase::Final => StackRule::MinMax, TransformPhase::Custom(_) => StackRule::Additive, }
}
pub trait StatTransform: Send + Sync {
fn depends_on(&self) -> Vec<StatId>;
fn phase(&self) -> TransformPhase {
TransformPhase::Multiplicative
}
fn apply(
&self,
input: StatValue,
dependencies: &FxHashMap<StatId, StatValue>,
context: &StatContext,
) -> Result<StatValue, StatError>;
fn description(&self) -> String;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::numeric::StatNumeric;
#[test]
fn test_transform_phase_values() {
assert_eq!(TransformPhase::Additive.value(), 0);
assert_eq!(TransformPhase::Multiplicative.value(), 1);
assert_eq!(TransformPhase::Final.value(), 2);
assert_eq!(TransformPhase::Custom(10).value(), 10);
assert_eq!(TransformPhase::Custom(1).value(), 3);
assert_eq!(TransformPhase::Custom(2).value(), 3);
}
#[test]
fn test_stack_rule_priority() {
let rules = [
StackRule::Override,
StackRule::Additive,
StackRule::Multiplicative,
StackRule::Diminishing {
k: StatValue::from_f64(1.0),
},
StackRule::Min,
StackRule::Max,
StackRule::MinMax,
];
for i in 0..rules.len() - 1 {
assert!(
rules[i] < rules[i + 1],
"Rule {:?} should be less than {:?}",
rules[i],
rules[i + 1]
);
}
}
}