use std::sync::Arc; use dashmap::DashMap; use crate::shape_forge::ShapeId; /// 属性布局模板:记录某个 shape 下属性 `prop_name` 应落在对象存储中的 `position`。 /// /// `generation` 用于并发更新时的版本裁决(新代覆盖旧代)。 #[derive(Debug, Clone)] pub struct PropTemplate { pub shape_id: ShapeId, pub prop_name: u32, pub position: u32, pub generation: u32, } /// 属性模板缓存:`shape_id → 属性布局` 的共享映射。 /// /// 供 IC(inline cache)与属性写入路径查询,避免每次写属性都回溯 shape 父链算槽位。 pub struct PropForge { templates: DashMap>, } impl PropForge { /// 创建空缓存。 pub fn new() -> Self { Self { templates: DashMap::new() } } /// 取指定 shape 的属性模板;未缓存时返回 `None`。 pub fn get_template(&self, shape_id: ShapeId) -> Option> { self.templates.get(&shape_id).map(|r| Arc::clone(&*r)) } /// 无条件写入(覆盖)该 shape 的模板。 pub fn upsert(&self, shape_id: ShapeId, template: PropTemplate) { self.templates.insert(shape_id, Arc::new(template)); } /// 仅当新模板 `generation` 更高时覆盖已有模板;空缺则直接写入。 pub fn upsert_if_better(&self, shape_id: ShapeId, template: PropTemplate) { use dashmap::mapref::entry::Entry; match self.templates.entry(shape_id) { Entry::Occupied(mut e) => { if e.get().generation < template.generation { e.insert(Arc::new(template)); } } Entry::Vacant(e) => { e.insert(Arc::new(template)); } } } /// 清空全部模板缓存。 pub fn clear(&self) { self.templates.clear(); } /// 缓存的模板数量。 pub fn len(&self) -> usize { self.templates.len() } /// 缓存是否为空。 pub fn is_empty(&self) -> bool { self.templates.is_empty() } } impl Default for PropForge { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_nonexistent() { let forge = PropForge::new(); assert!(forge.get_template(999).is_none()); } #[test] fn test_upsert_and_get() { let forge = PropForge::new(); let t = PropTemplate { shape_id: 1, prop_name: 11, position: 3, generation: 10, }; forge.upsert(1, t); let got = forge.get_template(1).unwrap(); assert_eq!(got.shape_id, 1); assert_eq!(got.prop_name, 11); assert_eq!(got.position, 3); assert_eq!(got.generation, 10); } #[test] fn test_upsert_if_better_high_gen_wins() { let forge = PropForge::new(); forge.upsert_if_better( 1, PropTemplate { shape_id: 1, prop_name: 11, position: 3, generation: 10, }, ); forge.upsert_if_better( 1, PropTemplate { shape_id: 1, prop_name: 12, position: 7, generation: 5, }, ); let got = forge.get_template(1).unwrap(); assert_eq!(got.generation, 10); } #[test] fn test_upsert_if_better_low_replaced() { let forge = PropForge::new(); forge.upsert_if_better( 1, PropTemplate { shape_id: 1, prop_name: 11, position: 3, generation: 5, }, ); forge.upsert_if_better( 1, PropTemplate { shape_id: 1, prop_name: 12, position: 7, generation: 10, }, ); let got = forge.get_template(1).unwrap(); assert_eq!(got.generation, 10); assert_eq!(got.position, 7); } }