use std::collections::{HashMap, HashSet};
use bevy::prelude::*;
use crate::context::AttributeContext;
use crate::node::{ReduceFn, AttributeNode};
use crate::attribute_id::{global_rodeo, AttributeId};
use crate::tags::TagMask;
#[derive(Clone, Debug)]
pub(crate) struct AttributeTemplate {
pub expression: String,
pub parts: Vec<String>,
pub name: String,
pub materialized: HashSet<TagMask>,
}
#[derive(Component, Clone, Debug, Default)]
pub struct Attributes {
pub(crate) nodes: HashMap<AttributeId, AttributeNode>,
pub(crate) context: AttributeContext,
pub(crate) tag_queries: HashMap<AttributeId, (AttributeId, TagMask)>,
pub(crate) tag_query_ids: HashMap<(AttributeId, TagMask), AttributeId>,
pub(crate) templates: HashMap<AttributeId, AttributeTemplate>,
}
impl Attributes {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, id: AttributeId) -> f32 {
self.context.get(id)
}
pub fn value(&self, name: &str) -> f32 {
if let Some(spur) = global_rodeo().get(name) {
self.context.get(AttributeId(spur))
} else {
0.0
}
}
pub fn value_tagged(&self, name: &str, mask: TagMask) -> f32 {
if let Some(spur) = global_rodeo().get(name) {
self.get_tagged(AttributeId(spur), mask)
} else {
0.0
}
}
pub fn get_tagged(&self, id: AttributeId, mask: TagMask) -> f32 {
if mask.is_empty() {
return self.context.get(id);
}
if let Some(&synthetic_id) = self.tag_query_ids.get(&(id, mask)) {
self.context.get(synthetic_id)
} else {
0.0
}
}
pub fn has_attribute(&self, id: AttributeId) -> bool {
self.nodes.contains_key(&id)
}
pub fn iter(&self) -> impl Iterator<Item = (AttributeId, f32)> + '_ {
self.context.iter()
}
pub(crate) fn ensure_node(&mut self, id: AttributeId, reduce: ReduceFn) -> &mut AttributeNode {
self.nodes.entry(id).or_insert_with(|| AttributeNode::new(reduce))
}
pub(crate) fn evaluate_and_cache(&mut self, id: AttributeId) -> f32 {
let value = if let Some(&(parent_id, mask)) = self.tag_queries.get(&id) {
if let Some(node) = self.nodes.get(&parent_id) {
node.evaluate_tagged(&self.context, mask)
} else {
0.0
}
} else if let Some(node) = self.nodes.get(&id) {
node.evaluate(&self.context)
} else {
0.0
};
self.context.set(id, value);
value
}
pub(crate) fn register_tag_query(
&mut self,
parent_id: AttributeId,
mask: TagMask,
synthetic_id: AttributeId,
) {
self.tag_queries.insert(synthetic_id, (parent_id, mask));
self.tag_query_ids.insert((parent_id, mask), synthetic_id);
}
pub(crate) fn tag_query_synthetic_id(
&self,
parent_id: AttributeId,
mask: TagMask,
) -> Option<AttributeId> {
self.tag_query_ids.get(&(parent_id, mask)).copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attribute_id::Interner;
use crate::modifier::Modifier;
#[test]
fn empty_attributes() {
let interner = Interner::new();
let attrs = Attributes::new();
let id = interner.get_or_intern("Nonexistent");
assert_eq!(attrs.get(id), 0.0);
}
#[test]
fn basic_attribute_lifecycle() {
let interner = Interner::new();
let mut attrs = Attributes::new();
let id = interner.get_or_intern("Strength");
let node = attrs.ensure_node(id, ReduceFn::Sum);
node.add_modifier(Modifier::Flat(25.0));
let val = attrs.evaluate_and_cache(id);
assert_eq!(val, 25.0);
assert_eq!(attrs.get(id), 25.0);
}
#[test]
fn get_tagged_none_delegates_to_get() {
let interner = Interner::new();
let mut attrs = Attributes::new();
let id = interner.get_or_intern("Damage");
let node = attrs.ensure_node(id, ReduceFn::Sum);
node.add_modifier(Modifier::Flat(50.0));
attrs.evaluate_and_cache(id);
assert_eq!(attrs.get_tagged(id, TagMask::NONE), 50.0);
}
#[test]
fn get_tagged_unregistered_returns_zero() {
let interner = Interner::new();
let attrs = Attributes::new();
let id = interner.get_or_intern("Damage");
assert_eq!(attrs.get_tagged(id, TagMask::bit(0)), 0.0);
}
#[test]
fn tag_query_evaluate_and_cache() {
let interner = Interner::new();
let fire = TagMask::bit(0);
let physical = TagMask::bit(1);
let mut attrs = Attributes::new();
let damage_id = interner.get_or_intern("Damage");
let synthetic_id = interner.get_or_intern("\0tag:Damage:1");
let node = attrs.ensure_node(damage_id, ReduceFn::Sum);
node.add_tagged_modifier(Modifier::Flat(25.0), fire);
node.add_tagged_modifier(Modifier::Flat(10.0), physical);
node.add_modifier(Modifier::Flat(5.0));
attrs.register_tag_query(damage_id, fire, synthetic_id);
attrs.evaluate_and_cache(damage_id);
let val = attrs.evaluate_and_cache(synthetic_id);
assert_eq!(val, 30.0);
assert_eq!(attrs.get_tagged(damage_id, fire), 30.0);
}
}