Core Concepts
Attributes and Derived Attributes
Store mutable numeric channels and calculate values that depend on runtime state.
An Attribute stores a signed numeric value for each object. It is appropriate
for state that should be directly mutated: current health, mana, shield, base
stats, threat, or score.
use flexweave::{Attribute, AttributeMutationResult, AttributeSet, ObjectStore};
let mut objects = ObjectStore::new();
let subject = objects.create();
let mut value = Attribute::new();
value.attach(subject, 5.0);
assert_eq!(value.get(subject), Some(5.0));
let result = AttributeSet::new(subject, 8.0).run(&mut value);
assert!(matches!(result, AttributeMutationResult::Committed(_)));
assert_eq!(value.get(subject), Some(8.0));AttributeSet reports what happened:
Committedmeans the stored value changed.Unchangedmeans the final value matched the stored value.Rejectedmeans a mutation hook blocked the write.
Mutation hooks give the caller a policy layer around writes:
use flexweave::{
AttributeMutationDecision, AttributeMutationHooks, AttributeMutationRequest,
};
#[derive(Clone, Copy)]
struct Bounds {
minimum: f64,
maximum: f64,
}
let mut hooks = AttributeMutationHooks::<Bounds, &'static str>::new();
hooks.add_pre_hook(|mutation| {
AttributeMutationDecision::Transform(
mutation
.current
.clamp(mutation.context.minimum, mutation.context.maximum),
)
});
let bounds = Bounds {
minimum: 0.0,
maximum: 10.0,
};
let result = AttributeSet::request(AttributeMutationRequest {
id: subject,
requested: 15.0,
})
.with_hooks(&bounds, &mut hooks)
.run(&mut value);
assert!(matches!(result, AttributeMutationResult::Committed(_)));
assert_eq!(value.get(subject), Some(10.0));DerivedAttribute calculates a value from caller-owned runtime state. It is
appropriate when the value depends on other stores, active effects, equipment,
tags, or local policy.
use flexweave::{DataStore, DerivedAttribute, DerivedAttributeRefresh};
use std::cell::RefCell;
use std::rc::Rc;
let base = Rc::new(RefCell::new(Attribute::new()));
let bonuses = Rc::new(RefCell::new(DataStore::<f64>::new()));
base.borrow_mut().attach(subject, 100.0);
bonuses.borrow_mut().attach(subject, 25.0);
let base_for_calc = Rc::clone(&base);
let bonuses_for_calc = Rc::clone(&bonuses);
let mut total = DerivedAttribute::new(move |id| {
let base_value = base_for_calc.borrow().get(id)?;
let bonus = bonuses_for_calc
.borrow()
.get(id)
.copied()
.unwrap_or(0.0);
Some(base_value + bonus)
});
assert_eq!(total.sync(subject), Some(125.0));
bonuses.borrow_mut().attach(subject, 40.0);
assert_eq!(DerivedAttributeRefresh::new(subject).run(&mut total), Some(140.0));Derived attributes do not automatically refresh. The runtime calls
DerivedAttributeRefresh after relevant state changes and chooses how to react
to changes.