sim_lib_view_spatial/
encode.rs1use std::borrow::Cow;
4use std::sync::Arc;
5
6use sim_kernel::{Cx, Error, Expr, Result, Symbol};
7use sim_lib_view::{
8 Draft, Operation, PairCodec, SurfaceCaps, SurfaceCodec, UniversalEditor, UniversalView, View,
9 codec::reduce_for_caps,
10};
11use sim_lib_view_device::{DeviceSurfaceCapsExt, GlassesClass, glasses_class};
12
13use crate::glance_map::halo_glance_scene;
14use crate::layout::{arrange_spatial_panels, layout_expr};
15use crate::rank::rank_for_profile;
16
17pub const SPATIAL_SURFACE_CODEC_ID: &str = "surface:spatial";
19
20pub fn surface_spatial_codec_symbol() -> Symbol {
22 Symbol::new(SPATIAL_SURFACE_CODEC_ID)
23}
24
25pub struct SpatialSurfaceCodec {
27 editor: PairCodec,
28}
29
30impl Default for SpatialSurfaceCodec {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl SpatialSurfaceCodec {
37 pub fn new() -> Self {
39 Self {
40 editor: PairCodec::new(
41 Arc::new(UniversalView),
42 Arc::new(UniversalEditor::writable()),
43 ),
44 }
45 }
46
47 fn source_scene(&self, cx: &mut Cx, value: &Expr) -> Result<Expr> {
48 let view_value = strip_layout_metadata(value);
49 let scene = UniversalView.encode(cx, view_value.as_ref())?;
50 validate("universal view produced invalid Scene", &scene)?;
51 Ok(scene)
52 }
53}
54
55impl SurfaceCodec for SpatialSurfaceCodec {
56 fn encode(&self, cx: &mut Cx, value: &Expr, caps: &SurfaceCaps) -> Result<Expr> {
57 let scene = self.source_scene(cx, value)?;
58 let profile = caps.device_profile();
59 let encoded = match glasses_class(&profile) {
60 Some(GlassesClass::Stereo6Dof) => rank_for_profile(
61 &arrange_spatial_panels(scene, layout_expr(value))?,
62 &profile,
63 )?,
64 Some(GlassesClass::MonoHud) => halo_glance_scene(&scene, &profile)?,
65 Some(GlassesClass::DisplayOnly) | None => reduce_for_caps(&scene, caps),
66 };
67 validate("spatial surface produced invalid Scene", &encoded)?;
68 Ok(encoded)
69 }
70
71 fn decode(&self, cx: &mut Cx, value: &Expr, intent: &Expr) -> Result<Draft> {
72 self.editor.decode(cx, value, intent)
73 }
74
75 fn commit(&self, cx: &mut Cx, draft: &Draft) -> Result<Operation> {
76 self.editor.commit(cx, draft)
77 }
78}
79
80fn validate(context: &str, scene: &Expr) -> Result<()> {
81 sim_lib_scene::validate_scene(scene)
82 .map_err(|err| Error::HostError(format!("{context}: {err}")))
83}
84
85fn strip_layout_metadata(value: &Expr) -> Cow<'_, Expr> {
86 let Expr::Map(entries) = value else {
87 return Cow::Borrowed(value);
88 };
89 let filtered = entries
90 .iter()
91 .filter(|(key, _)| !is_layout_metadata_key(key))
92 .cloned()
93 .collect::<Vec<_>>();
94 if filtered.len() == entries.len() {
95 Cow::Borrowed(value)
96 } else {
97 Cow::Owned(Expr::Map(filtered))
98 }
99}
100
101fn is_layout_metadata_key(key: &Expr) -> bool {
102 let name = match key {
103 Expr::Symbol(symbol) if symbol.namespace.is_none() => symbol.name.as_ref(),
104 Expr::String(text) => text.as_str(),
105 _ => return false,
106 };
107 matches!(
108 name,
109 "workspace-layout" | "spatial-workspace-layout" | "spatial-layout" | "layout"
110 )
111}