use super::client::{Client, get_env}; use crate::nodes::{Node, root::ClientState, spatial::Spatial}; use glam::Mat4; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::{ path::{Path, PathBuf}, process::Command, sync::Arc, }; lazy_static::lazy_static! { pub static ref CLIENT_STATES: Mutex>> = Default::default(); } #[derive(Debug, Serialize, Deserialize)] pub struct LaunchInfo { pub cmdline: Vec, pub cwd: PathBuf, pub env: FxHashMap, } impl LaunchInfo { fn from_client(client: &Client) -> Option { Some(LaunchInfo { cmdline: client.get_cmdline()?, cwd: client.get_cwd()?, env: get_env(client.pid?).ok()?, }) } } #[derive(Debug, Serialize, Deserialize)] pub struct ClientStateParsed { pub launch_info: Option, pub data: Vec, pub root: Mat4, pub spatial_anchors: FxHashMap, } impl ClientStateParsed { pub fn from_deserialized(client: &Client, state: ClientState) -> Self { ClientStateParsed { launch_info: LaunchInfo::from_client(client), data: state.data.unwrap_or_default(), root: Self::spatial_transform(client, state.root).unwrap_or_default(), spatial_anchors: state .spatial_anchors .into_iter() .filter_map(|(k, v)| Some((k, Self::spatial_transform(client, v)?))) .collect(), } } fn spatial_transform(client: &Client, id: u64) -> Option { let node = client.scenegraph.get_node(id)?; let spatial = node.get_aspect::().ok()?; Some(spatial.global_transform()) } pub fn token(self) -> String { let token = nanoid::nanoid!(); CLIENT_STATES.lock().insert(token.clone(), Arc::new(self)); token } pub fn from_file(file: &Path) -> Option { let file_string = std::fs::read_to_string(file).ok()?; toml::from_str(&file_string).ok() } pub fn to_file(&self, directory: &Path) { let app_name = self .launch_info .as_ref() .map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap()) .unwrap_or("unknown"); let state_file_path = directory .join(format!("{app_name}-{}", nanoid::nanoid!())) .with_extension("toml"); std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap(); } pub fn apply_to(&self, client: &Arc) -> ClientState { if let Some(root) = client.root.get() { root.set_transform(self.root) } ClientState { data: Some(self.data.clone()), root: 0, spatial_anchors: self .spatial_anchors .iter() .map(|(k, v)| { let node = Node::generate(client, true).add_to_scenegraph().unwrap(); Spatial::add_to(&node, None, *v, false); (k.clone(), node.get_id()) }) .collect(), } } pub fn launch_command(self) -> Option { let launch_info = self.launch_info.as_ref()?; let mut cmdline = launch_info.cmdline.iter(); let mut command = Command::new(cmdline.next()?); command.args(cmdline); command.current_dir(&launch_info.cwd); command.envs(launch_info.env.iter()); command.env("STARDUST_STARTUP_TOKEN", self.token()); Some(command) } } impl Default for ClientStateParsed { fn default() -> Self { Self { launch_info: None, data: Default::default(), root: Mat4::IDENTITY, spatial_anchors: Default::default(), } } }