pub struct StatId(/* private fields */);Expand description
Interned string identifier for stats.
Uses Arc<str> for memory efficiency and fast comparison.
Multiple StatId instances with the same string content share the same
underlying allocation.
§Examples
use zzstat::StatId;
let hp = StatId::from_str("HP");
let atk = StatId::from_str("ATK");
// Can be created from string slices or owned strings
let hp2: StatId = "HP".into();
let hp3: StatId = String::from("HP").into();
assert_eq!(hp, hp2);
assert_eq!(hp, hp3);Implementations§
Source§impl StatId
impl StatId
Sourcepub fn from_str(s: &str) -> Self
pub fn from_str(s: &str) -> Self
Create a new StatId from a string slice.
§Examples
use zzstat::StatId;
let stat_id = StatId::from_str("HP");
assert_eq!(stat_id.as_str(), "HP");Examples found in repository?
examples/basic.rs (line 17)
12fn main() -> Result<(), StatError> {
13 // Create a new stat resolver
14 let mut resolver = StatResolver::new();
15
16 // Define a stat ID
17 let hp_id = StatId::from_str("HP");
18
19 // Register multiple sources (they will be summed)
20 println!("Registering sources for HP:");
21 resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
22 println!(" - Base HP: 100");
23
24 resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
25 println!(" - Item bonus: +50");
26
27 // Register a transform (percentage modifier)
28 println!("\nRegistering transform:");
29 resolver.register_transform(hp_id.clone(), Box::new(MultiplicativeTransform::new(1.2)));
30 println!(" - 20% bonus multiplier");
31
32 // Create context (empty for this example)
33 let context = StatContext::new();
34
35 // Resolve the stat
36 println!("\nResolving HP...");
37 let resolved = resolver.resolve(&hp_id, &context)?;
38
39 // Display results
40 println!("\n=== Resolved Stat ===");
41 println!("Stat ID: {}", resolved.stat_id);
42 println!("Final Value: {:.2}", resolved.value);
43
44 println!("\nSource Breakdown:");
45 for (desc, value) in &resolved.sources {
46 println!(" {}: {:.2}", desc, value);
47 }
48
49 println!("\nTransform Breakdown:");
50 for (desc, value) in &resolved.transforms {
51 println!(" {}: {:.2}", desc, value);
52 }
53
54 println!("\nCalculation: (100 + 50) * 1.2 = {:.2}", resolved.value);
55
56 Ok(())
57}More examples
examples/dependencies.rs (line 16)
12fn main() -> Result<(), StatError> {
13 let mut resolver = StatResolver::new();
14
15 // Define stat IDs
16 let str_id = StatId::from_str("STR");
17 let dex_id = StatId::from_str("DEX");
18 let atk_id = StatId::from_str("ATK");
19 let crit_id = StatId::from_str("CRIT");
20
21 println!("=== Setting up base stats ===");
22
23 // Base stats (no dependencies)
24 resolver.register_source(str_id.clone(), Box::new(ConstantSource(10.0)));
25 println!("STR: 10 (base)");
26
27 resolver.register_source(dex_id.clone(), Box::new(ConstantSource(15.0)));
28 println!("DEX: 15 (base)");
29
30 println!("\n=== Setting up derived stats ===");
31
32 // ATK depends on STR
33 resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
34 resolver.register_transform(
35 atk_id.clone(),
36 Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
37 );
38 println!("ATK: 50 (base) + STR * 2.0");
39
40 // CRIT depends on DEX
41 resolver.register_source(crit_id.clone(), Box::new(ConstantSource(5.0)));
42 resolver.register_transform(
43 crit_id.clone(),
44 Box::new(ScalingTransform::new(dex_id.clone(), 1.5)),
45 );
46 println!("CRIT: 5 (base) + DEX * 1.5");
47
48 let context = StatContext::new();
49
50 println!("\n=== Resolving all stats ===");
51 let results = resolver.resolve_all(&context)?;
52
53 // Display results
54 println!("\n=== Results ===");
55 for (stat_id, resolved) in &results {
56 println!("\n{}:", stat_id);
57 println!(" Final Value: {:.2}", resolved.value);
58
59 if !resolved.sources.is_empty() {
60 println!(" Sources:");
61 for (desc, value) in &resolved.sources {
62 println!(" {}: {:.2}", desc, value);
63 }
64 }
65
66 if !resolved.transforms.is_empty() {
67 println!(" Transforms:");
68 for (desc, value) in &resolved.transforms {
69 println!(" {}: {:.2}", desc, value);
70 }
71 }
72 }
73
74 println!("\n=== Verification ===");
75 println!("STR: {:.2} (expected: 10.00)", results[&str_id].value);
76 println!("DEX: {:.2} (expected: 15.00)", results[&dex_id].value);
77 println!(
78 "ATK: {:.2} (expected: 70.00 = 50 + 10*2)",
79 results[&atk_id].value
80 );
81 println!(
82 "CRIT: {:.2} (expected: 27.50 = 5 + 15*1.5)",
83 results[&crit_id].value
84 );
85
86 Ok(())
87}examples/cycle_detection.rs (line 15)
11fn main() {
12 let mut resolver = StatResolver::new();
13
14 // Create stats that depend on each other in a cycle
15 let a_id = StatId::from_str("A");
16 let b_id = StatId::from_str("B");
17 let c_id = StatId::from_str("C");
18
19 println!("=== Setting up circular dependencies ===\n");
20
21 // A depends on B
22 resolver.register_source(a_id.clone(), Box::new(ConstantSource(10.0)));
23 resolver.register_transform(
24 a_id.clone(),
25 Box::new(ScalingTransform::new(b_id.clone(), 1.0)),
26 );
27 println!("A: 10 (base) + B * 1.0");
28
29 // B depends on C
30 resolver.register_source(b_id.clone(), Box::new(ConstantSource(20.0)));
31 resolver.register_transform(
32 b_id.clone(),
33 Box::new(ScalingTransform::new(c_id.clone(), 1.0)),
34 );
35 println!("B: 20 (base) + C * 1.0");
36
37 // C depends on A (creates cycle: A -> B -> C -> A)
38 resolver.register_source(c_id.clone(), Box::new(ConstantSource(30.0)));
39 resolver.register_transform(
40 c_id.clone(),
41 Box::new(ScalingTransform::new(a_id.clone(), 1.0)),
42 );
43 println!("C: 30 (base) + A * 1.0");
44
45 println!("\n=== Attempting to resolve (should detect cycle) ===\n");
46
47 let context = StatContext::new();
48 match resolver.resolve(&a_id, &context) {
49 Err(StatError::CycleDetected(cycle)) => {
50 println!("✓ Cycle detected successfully!");
51 println!("\nCycle path:");
52 for (i, stat_id) in cycle.iter().enumerate() {
53 if i < cycle.len() - 1 {
54 print!("{} -> ", stat_id);
55 } else {
56 println!("{}", stat_id);
57 }
58 }
59 }
60 Err(e) => {
61 println!("✗ Unexpected error: {}", e);
62 }
63 Ok(_) => {
64 println!("✗ ERROR: Cycle was not detected! This should not happen.");
65 }
66 }
67
68 println!("\n=== Valid dependency chain (no cycle) ===\n");
69
70 // Reset resolver
71 let mut resolver2 = StatResolver::new();
72
73 let x_id = StatId::from_str("X");
74 let y_id = StatId::from_str("Y");
75 let z_id = StatId::from_str("Z");
76
77 // Linear chain: X -> Y -> Z (no cycle)
78 resolver2.register_source(x_id.clone(), Box::new(ConstantSource(10.0)));
79 println!("X: 10 (base)");
80
81 resolver2.register_source(y_id.clone(), Box::new(ConstantSource(20.0)));
82 resolver2.register_transform(
83 y_id.clone(),
84 Box::new(ScalingTransform::new(x_id.clone(), 1.0)),
85 );
86 println!("Y: 20 (base) + X * 1.0");
87
88 resolver2.register_source(z_id.clone(), Box::new(ConstantSource(30.0)));
89 resolver2.register_transform(
90 z_id.clone(),
91 Box::new(ScalingTransform::new(y_id.clone(), 1.0)),
92 );
93 println!("Z: 30 (base) + Y * 1.0");
94
95 println!("\n=== Resolving valid chain ===\n");
96
97 let results = resolver2.resolve_all(&context).unwrap();
98
99 println!("Results:");
100 println!(" X: {:.2}", results[&x_id].value);
101 println!(" Y: {:.2} (20 + 10)", results[&y_id].value);
102 println!(" Z: {:.2} (30 + 30)", results[&z_id].value);
103
104 println!("\n✓ Valid dependency chain resolved successfully!");
105}examples/context.rs (line 15)
12fn main() -> Result<(), StatError> {
13 let mut resolver = StatResolver::new();
14
15 let atk_id = StatId::from_str("ATK");
16 let def_id = StatId::from_str("DEF");
17
18 println!("=== Setting up stats with context-dependent transforms ===\n");
19
20 // Base ATK
21 resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
22 println!("ATK base: 100");
23
24 // ATK gets +50% bonus in combat
25 let combat_bonus = ConditionalTransform::new(
26 |ctx| ctx.get::<bool>("in_combat").unwrap_or(false),
27 Box::new(MultiplicativeTransform::new(1.5)),
28 "Combat bonus +50%",
29 );
30 resolver.register_transform(atk_id.clone(), Box::new(combat_bonus));
31 println!("ATK: +50% when in combat");
32
33 // Base DEF
34 resolver.register_source(def_id.clone(), Box::new(ConstantSource(80.0)));
35 println!("DEF base: 80");
36
37 // DEF gets +25% bonus in PvP zones
38 let pvp_bonus = ConditionalTransform::new(
39 |ctx| {
40 ctx.get::<String>("zone_type")
41 .map(|z| z == "pvp")
42 .unwrap_or(false)
43 },
44 Box::new(MultiplicativeTransform::new(1.25)),
45 "PvP zone bonus +25%",
46 );
47 resolver.register_transform(def_id.clone(), Box::new(pvp_bonus));
48 println!("DEF: +25% in PvP zones");
49
50 // Scenario 1: Out of combat, normal zone
51 println!("\n=== Scenario 1: Out of combat, normal zone ===");
52 let mut context1 = StatContext::new();
53 context1.set("in_combat", false);
54 context1.set("zone_type", "normal");
55
56 let atk1 = resolver.resolve(&atk_id, &context1)?;
57 let def1 = resolver.resolve(&def_id, &context1)?;
58
59 println!("ATK: {:.2} (no bonuses)", atk1.value);
60 println!("DEF: {:.2} (no bonuses)", def1.value);
61
62 // Scenario 2: In combat, normal zone
63 println!("\n=== Scenario 2: In combat, normal zone ===");
64 let mut context2 = StatContext::new();
65 context2.set("in_combat", true);
66 context2.set("zone_type", "normal");
67
68 // Invalidate cache to force recalculation
69 resolver.invalidate(&atk_id);
70 resolver.invalidate(&def_id);
71
72 let atk2 = resolver.resolve(&atk_id, &context2)?;
73 let def2 = resolver.resolve(&def_id, &context2)?;
74
75 println!("ATK: {:.2} (combat bonus: 100 * 1.5)", atk2.value);
76 println!("DEF: {:.2} (no bonuses)", def2.value);
77
78 // Scenario 3: Out of combat, PvP zone
79 println!("\n=== Scenario 3: Out of combat, PvP zone ===");
80 let mut context3 = StatContext::new();
81 context3.set("in_combat", false);
82 context3.set("zone_type", "pvp");
83
84 resolver.invalidate(&atk_id);
85 resolver.invalidate(&def_id);
86
87 let atk3 = resolver.resolve(&atk_id, &context3)?;
88 let def3 = resolver.resolve(&def_id, &context3)?;
89
90 println!("ATK: {:.2} (no bonuses)", atk3.value);
91 println!("DEF: {:.2} (PvP bonus: 80 * 1.25)", def3.value);
92
93 // Scenario 4: In combat, PvP zone
94 println!("\n=== Scenario 4: In combat, PvP zone ===");
95 let mut context4 = StatContext::new();
96 context4.set("in_combat", true);
97 context4.set("zone_type", "pvp");
98
99 resolver.invalidate(&atk_id);
100 resolver.invalidate(&def_id);
101
102 let atk4 = resolver.resolve(&atk_id, &context4)?;
103 let def4 = resolver.resolve(&def_id, &context4)?;
104
105 println!("ATK: {:.2} (combat bonus: 100 * 1.5)", atk4.value);
106 println!("DEF: {:.2} (PvP bonus: 80 * 1.25)", def4.value);
107
108 println!("\n=== Summary ===");
109 println!("Context allows stats to vary based on game state:");
110 println!(" - Combat state affects ATK");
111 println!(" - Zone type affects DEF");
112 println!(" - Stats are recalculated when context changes");
113
114 Ok(())
115}examples/complex.rs (line 17)
13fn main() -> Result<(), StatError> {
14 let mut resolver = StatResolver::new();
15
16 // Define a character's stats
17 let str_id = StatId::from_str("STR");
18 let dex_id = StatId::from_str("DEX");
19 let int_id = StatId::from_str("INT");
20 let hp_id = StatId::from_str("HP");
21 let mp_id = StatId::from_str("MP");
22 let atk_id = StatId::from_str("ATK");
23 let crit_id = StatId::from_str("CRIT");
24 let dps_id = StatId::from_str("DPS");
25
26 println!("=== Character Stat System ===\n");
27
28 // Base attributes
29 println!("Base Attributes:");
30 resolver.register_source(str_id.clone(), Box::new(ConstantSource(20.0)));
31 println!(" STR: 20");
32
33 resolver.register_source(dex_id.clone(), Box::new(ConstantSource(15.0)));
34 println!(" DEX: 15");
35
36 resolver.register_source(int_id.clone(), Box::new(ConstantSource(25.0)));
37 println!(" INT: 25");
38
39 // HP: Base + STR scaling + item bonuses
40 println!("\nHP Calculation:");
41 resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
42 println!(" Base: 100");
43
44 resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
45 println!(" Item bonus: +50");
46
47 resolver.register_transform(
48 hp_id.clone(),
49 Box::new(ScalingTransform::new(str_id.clone(), 5.0)),
50 );
51 println!(" STR scaling: +STR * 5");
52
53 resolver.register_transform(hp_id.clone(), Box::new(MultiplicativeTransform::new(1.1)));
54 println!(" Passive: +10%");
55
56 // MP: Base + INT scaling
57 println!("\nMP Calculation:");
58 resolver.register_source(mp_id.clone(), Box::new(ConstantSource(50.0)));
59 println!(" Base: 50");
60
61 resolver.register_transform(
62 mp_id.clone(),
63 Box::new(ScalingTransform::new(int_id.clone(), 3.0)),
64 );
65 println!(" INT scaling: +INT * 3");
66
67 // ATK: Base + STR scaling + DEX scaling
68 println!("\nATK Calculation:");
69 resolver.register_source(atk_id.clone(), Box::new(ConstantSource(30.0)));
70 println!(" Base: 30");
71
72 resolver.register_transform(
73 atk_id.clone(),
74 Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
75 );
76 println!(" STR scaling: +STR * 2");
77
78 resolver.register_transform(
79 atk_id.clone(),
80 Box::new(ScalingTransform::new(dex_id.clone(), 1.0)),
81 );
82 println!(" DEX scaling: +DEX * 1");
83
84 resolver.register_transform(atk_id.clone(), Box::new(ClampTransform::new(0.0, 200.0)));
85 println!(" Clamp: [0, 200]");
86
87 // CRIT: Base + DEX scaling
88 println!("\nCRIT Calculation:");
89 resolver.register_source(crit_id.clone(), Box::new(ConstantSource(5.0)));
90 println!(" Base: 5");
91
92 resolver.register_transform(
93 crit_id.clone(),
94 Box::new(ScalingTransform::new(dex_id.clone(), 2.0)),
95 );
96 println!(" DEX scaling: +DEX * 2");
97
98 // DPS: Depends on ATK and CRIT
99 println!("\nDPS Calculation:");
100 resolver.register_source(dps_id.clone(), Box::new(ConstantSource(0.0)));
101 println!(" Base: 0");
102
103 resolver.register_transform(
104 dps_id.clone(),
105 Box::new(ScalingTransform::new(atk_id.clone(), 1.0)),
106 );
107 println!(" ATK contribution: +ATK * 1");
108
109 resolver.register_transform(
110 dps_id.clone(),
111 Box::new(ScalingTransform::new(crit_id.clone(), 0.5)),
112 );
113 println!(" CRIT contribution: +CRIT * 0.5");
114
115 let context = StatContext::new();
116
117 println!("\n=== Resolving All Stats ===\n");
118 let results = resolver.resolve_all(&context)?;
119
120 // Display final stats
121 println!("=== Final Character Stats ===\n");
122
123 let stats = vec![
124 ("STR", &str_id),
125 ("DEX", &dex_id),
126 ("INT", &int_id),
127 ("HP", &hp_id),
128 ("MP", &mp_id),
129 ("ATK", &atk_id),
130 ("CRIT", &crit_id),
131 ("DPS", &dps_id),
132 ];
133
134 for (name, id) in stats {
135 if let Some(resolved) = results.get(id) {
136 println!("{}: {:.2}", name, resolved.value);
137 }
138 }
139
140 println!("\n=== Detailed Breakdown ===\n");
141
142 // Show detailed breakdown for DPS
143 if let Some(dps) = results.get(&dps_id) {
144 println!("DPS Breakdown:");
145 println!(" Final Value: {:.2}", dps.value);
146
147 println!("\n Sources:");
148 for (desc, value) in &dps.sources {
149 println!(" {}: {:.2}", desc, value);
150 }
151
152 println!("\n Transforms:");
153 for (desc, value) in &dps.transforms {
154 println!(" {}: {:.2}", desc, value);
155 }
156 }
157
158 Ok(())
159}Trait Implementations§
Source§impl<'de> Deserialize<'de> for StatId
impl<'de> Deserialize<'de> for StatId
Source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Deserialize this value from the given Serde deserializer. Read more
Source§impl Ord for StatId
impl Ord for StatId
Source§impl PartialOrd for StatId
impl PartialOrd for StatId
impl Eq for StatId
impl StructuralPartialEq for StatId
Auto Trait Implementations§
impl Freeze for StatId
impl RefUnwindSafe for StatId
impl Send for StatId
impl Sync for StatId
impl Unpin for StatId
impl UnwindSafe for StatId
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Comparable<K> for Q
impl<Q, K> Comparable<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
Compare self to
key and return true if they are equal.