terminals_core/substrate/
layout.rs1use super::projection::ProjectionId;
8use super::splat::SplatProjection;
9use super::kuramoto::KuramotoProjection;
10use super::expert::ExpertProjection;
11use super::graph::GraphProjection;
12use super::thermal::ThermalProjection;
13use super::projection::Projection;
14
15#[derive(Debug, Clone)]
17pub struct ProjectionLayout {
18 pub entries: Vec<(ProjectionId, usize)>,
20 pub stride: usize,
22}
23
24impl ProjectionLayout {
25 pub fn from_projections(ids: &[ProjectionId]) -> Self {
28 let canonical = [
30 ProjectionId::Splat,
31 ProjectionId::Kuramoto,
32 ProjectionId::Expert,
33 ProjectionId::Graph,
34 ProjectionId::Thermal,
35 ];
36
37 let mut entries = Vec::new();
38 let mut offset = 0usize;
39
40 for &proj_id in &canonical {
41 if ids.contains(&proj_id) {
42 let size = Self::projection_byte_size(proj_id);
43 entries.push((proj_id, offset));
44 offset += size;
45 }
46 }
47
48 Self {
49 entries,
50 stride: offset,
51 }
52 }
53
54 pub fn full() -> Self {
56 Self::from_projections(&[
57 ProjectionId::Splat,
58 ProjectionId::Kuramoto,
59 ProjectionId::Expert,
60 ProjectionId::Graph,
61 ProjectionId::Thermal,
62 ])
63 }
64
65 pub fn minimal() -> Self {
67 Self::from_projections(&[ProjectionId::Splat, ProjectionId::Kuramoto])
68 }
69
70 pub fn offset_of(&self, id: ProjectionId) -> Option<usize> {
72 self.entries.iter().find(|(pid, _)| *pid == id).map(|(_, off)| *off)
73 }
74
75 pub fn has(&self, id: ProjectionId) -> bool {
77 self.entries.iter().any(|(pid, _)| *pid == id)
78 }
79
80 pub fn count(&self) -> usize {
82 self.entries.len()
83 }
84
85 fn projection_byte_size(id: ProjectionId) -> usize {
86 match id {
87 ProjectionId::Splat => SplatProjection::byte_size(),
88 ProjectionId::Kuramoto => KuramotoProjection::byte_size(),
89 ProjectionId::Expert => ExpertProjection::byte_size(),
90 ProjectionId::Graph => GraphProjection::byte_size(),
91 ProjectionId::Thermal => ThermalProjection::byte_size(),
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_full_layout_stride() {
102 let layout = ProjectionLayout::full();
103 assert_eq!(layout.stride, 1612);
105 assert_eq!(layout.count(), 5);
106 }
107
108 #[test]
109 fn test_minimal_layout() {
110 let layout = ProjectionLayout::minimal();
111 assert_eq!(layout.stride, 1548);
113 assert_eq!(layout.count(), 2);
114 assert!(layout.has(ProjectionId::Splat));
115 assert!(layout.has(ProjectionId::Kuramoto));
116 assert!(!layout.has(ProjectionId::Expert));
117 }
118
119 #[test]
120 fn test_offset_canonical_order() {
121 let layout = ProjectionLayout::full();
122 assert_eq!(layout.offset_of(ProjectionId::Splat), Some(0));
123 assert_eq!(layout.offset_of(ProjectionId::Kuramoto), Some(1536));
124 assert_eq!(layout.offset_of(ProjectionId::Expert), Some(1548));
125 assert_eq!(layout.offset_of(ProjectionId::Graph), Some(1560));
126 }
127
128 #[test]
129 fn test_order_independent() {
130 let a = ProjectionLayout::from_projections(&[ProjectionId::Graph, ProjectionId::Splat]);
132 let b = ProjectionLayout::from_projections(&[ProjectionId::Splat, ProjectionId::Graph]);
133 assert_eq!(a.stride, b.stride);
134 assert_eq!(a.offset_of(ProjectionId::Splat), b.offset_of(ProjectionId::Splat));
135 }
136}