1use elara_core::{DegradationLevel, NodeId, StateId, StateTime};
6
7use crate::{FaceState, PoseState, SceneState};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct VisualStateId(pub u64);
12
13impl VisualStateId {
14 pub fn new(id: u64) -> Self {
15 Self(id)
16 }
17}
18
19pub const STATE_TYPE_VISUAL: u16 = 0x0020;
20pub const STATE_TYPE_LIVESTREAM: u16 = 0x0021;
21
22pub fn visual_state_id(node_id: NodeId) -> StateId {
23 StateId::from_type_instance(STATE_TYPE_VISUAL, node_id.0)
24}
25
26pub fn stream_visual_state_id(stream_id: u64) -> StateId {
27 StateId::from_type_instance(STATE_TYPE_VISUAL, stream_id)
28}
29
30pub fn livestream_state_id(stream_id: u64) -> StateId {
31 StateId::from_type_instance(STATE_TYPE_LIVESTREAM, stream_id)
32}
33
34#[derive(Debug, Clone)]
39pub struct VisualState {
40 pub id: VisualStateId,
42
43 pub source: NodeId,
45
46 pub timestamp: StateTime,
48
49 pub face: Option<FaceState>,
51
52 pub pose: Option<PoseState>,
54
55 pub scene: Option<SceneState>,
57
58 pub degradation: DegradationLevel,
60
61 pub is_keyframe: bool,
63
64 pub keyframe_ref: Option<VisualStateId>,
66
67 pub sequence: u64,
69}
70
71impl VisualState {
72 pub fn keyframe(source: NodeId, timestamp: StateTime, sequence: u64) -> Self {
74 Self {
75 id: VisualStateId::new(sequence),
76 source,
77 timestamp,
78 face: None,
79 pose: None,
80 scene: None,
81 degradation: DegradationLevel::L0_FullPerception,
82 is_keyframe: true,
83 keyframe_ref: None,
84 sequence,
85 }
86 }
87
88 pub fn delta(
90 source: NodeId,
91 timestamp: StateTime,
92 sequence: u64,
93 keyframe: VisualStateId,
94 ) -> Self {
95 Self {
96 id: VisualStateId::new(sequence),
97 source,
98 timestamp,
99 face: None,
100 pose: None,
101 scene: None,
102 degradation: DegradationLevel::L0_FullPerception,
103 is_keyframe: false,
104 keyframe_ref: Some(keyframe),
105 sequence,
106 }
107 }
108
109 pub fn with_face(mut self, face: FaceState) -> Self {
111 self.face = Some(face);
112 self
113 }
114
115 pub fn with_pose(mut self, pose: PoseState) -> Self {
117 self.pose = Some(pose);
118 self
119 }
120
121 pub fn with_scene(mut self, scene: SceneState) -> Self {
123 self.scene = Some(scene);
124 self
125 }
126
127 pub fn with_degradation(mut self, level: DegradationLevel) -> Self {
129 self.degradation = level;
130 self
131 }
132
133 pub fn complexity(&self) -> f32 {
136 let mut score: f32 = 0.0;
137
138 if self.face.is_some() {
139 score += 0.3;
140 }
141 if self.pose.is_some() {
142 score += 0.3;
143 }
144 if self.scene.is_some() {
145 score += 0.4;
146 }
147
148 if self.is_keyframe {
149 score *= 1.5; }
151
152 score.min(1.0)
153 }
154
155 pub fn degrade(&self, target: DegradationLevel) -> VisualState {
158 let mut degraded = self.clone();
159 degraded.degradation = target;
160
161 match target {
162 DegradationLevel::L0_FullPerception => {
163 }
165 DegradationLevel::L1_DistortedPerception => {
166 if let Some(ref mut scene) = degraded.scene {
168 scene.reduce_detail(0.7);
169 }
170 }
171 DegradationLevel::L2_FragmentedPerception => {
172 degraded.scene = None;
174 }
175 DegradationLevel::L3_SymbolicPresence => {
176 degraded.scene = None;
178 degraded.pose = None;
179 }
180 DegradationLevel::L4_MinimalPresence => {
181 degraded.scene = None;
183 degraded.pose = None;
184 if let Some(ref mut face) = degraded.face {
185 face.reduce_to_minimal();
186 }
187 }
188 DegradationLevel::L5_LatentPresence => {
189 degraded.scene = None;
191 degraded.pose = None;
192 degraded.face = degraded.face.map(|f| f.to_latent());
193 }
194 }
195
196 degraded
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct LivestreamState {
203 pub broadcaster: NodeId,
205
206 pub visual: VisualState,
208
209 pub viewer_count: u32,
211
212 pub is_live: bool,
214
215 pub title: String,
217
218 pub started_at: StateTime,
220}
221
222impl LivestreamState {
223 pub fn new(broadcaster: NodeId, title: String, started_at: StateTime) -> Self {
225 Self {
226 broadcaster,
227 visual: VisualState::keyframe(broadcaster, started_at, 0),
228 viewer_count: 0,
229 is_live: true,
230 title,
231 started_at,
232 }
233 }
234
235 pub fn update_visual(&mut self, visual: VisualState) -> Result<(), &'static str> {
237 if visual.source != self.broadcaster {
238 return Err("Only broadcaster can update visual state");
239 }
240 self.visual = visual;
241 Ok(())
242 }
243
244 pub fn add_viewer(&mut self) {
246 self.viewer_count += 1;
247 }
248
249 pub fn remove_viewer(&mut self) {
251 self.viewer_count = self.viewer_count.saturating_sub(1);
252 }
253
254 pub fn end_stream(&mut self) {
256 self.is_live = false;
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_visual_state_keyframe() {
266 let node = NodeId::new(1);
267 let time = StateTime::from_millis(1000);
268 let state = VisualState::keyframe(node, time, 1);
269
270 assert!(state.is_keyframe);
271 assert!(state.keyframe_ref.is_none());
272 assert_eq!(state.sequence, 1);
273 }
274
275 #[test]
276 fn test_visual_state_delta() {
277 let node = NodeId::new(1);
278 let time = StateTime::from_millis(1000);
279 let keyframe_id = VisualStateId::new(1);
280 let state = VisualState::delta(node, time, 2, keyframe_id);
281
282 assert!(!state.is_keyframe);
283 assert_eq!(state.keyframe_ref, Some(keyframe_id));
284 assert_eq!(state.sequence, 2);
285 }
286
287 #[test]
288 fn test_visual_state_degradation() {
289 let node = NodeId::new(1);
290 let time = StateTime::from_millis(1000);
291 let state = VisualState::keyframe(node, time, 1);
292
293 let degraded = state.degrade(DegradationLevel::L3_SymbolicPresence);
294 assert_eq!(degraded.degradation, DegradationLevel::L3_SymbolicPresence);
295 assert!(degraded.scene.is_none());
296 assert!(degraded.pose.is_none());
297 }
298
299 #[test]
300 fn test_livestream_state() {
301 let broadcaster = NodeId::new(1);
302 let time = StateTime::from_millis(0);
303 let mut stream = LivestreamState::new(broadcaster, "Test Stream".to_string(), time);
304
305 assert!(stream.is_live);
306 assert_eq!(stream.viewer_count, 0);
307
308 stream.add_viewer();
309 stream.add_viewer();
310 assert_eq!(stream.viewer_count, 2);
311
312 stream.remove_viewer();
313 assert_eq!(stream.viewer_count, 1);
314
315 stream.end_stream();
316 assert!(!stream.is_live);
317 }
318}