feat: client state (save/restore)

This commit is contained in:
Nova
2023-09-28 09:55:45 -04:00
parent af75d2a451
commit fc45b4e400
10 changed files with 266 additions and 235 deletions

View File

@@ -47,7 +47,7 @@ once_cell = "1.17.1"
parking_lot = "0.12.1" parking_lot = "0.12.1"
portable-atomic = { version = "1.2.0", features = ["float", "std"] } portable-atomic = { version = "1.2.0", features = ["float", "std"] }
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal"] } tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal", "time"] }
send_wrapper = "0.6.0" send_wrapper = "0.6.0"
prisma = "0.1.1" prisma = "0.1.1"
stardust-xr = "0.14.1" stardust-xr = "0.14.1"

View File

@@ -1,20 +1,20 @@
use super::scenegraph::Scenegraph; use super::{
client_state::{ClientState, CLIENT_STATES},
scenegraph::Scenegraph,
};
use crate::{ use crate::{
core::{registry::OwnedRegistry, task}, core::{registry::OwnedRegistry, task},
nodes::{ nodes::{audio, data, drawable, fields, hmd, input, items, root::Root, spatial, Node},
audio, data, drawable, fields, hmd, input, items,
root::Root,
spatial,
startup::{self, StartupSettings, STARTUP_SETTINGS},
Node,
},
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use stardust_xr::messenger::{self, MessageSenderHandle}; use stardust_xr::{
messenger::{self, MessageSenderHandle},
schemas::flex::serialize,
};
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc}; use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
use tokio::{net::UnixStream, task::JoinHandle}; use tokio::{net::UnixStream, task::JoinHandle};
use tracing::info; use tracing::info;
@@ -34,7 +34,7 @@ lazy_static! {
scenegraph: Default::default(), scenegraph: Default::default(),
root: OnceCell::new(), root: OnceCell::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
startup_settings: None, state: Arc::new(ClientState::default()),
}); });
} }
@@ -46,9 +46,9 @@ pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
.map(|(k, v)| (k.to_string(), v.to_string())), .map(|(k, v)| (k.to_string(), v.to_string())),
)) ))
} }
pub fn startup_settings(env: &FxHashMap<String, String>) -> Option<StartupSettings> { pub fn state(env: &FxHashMap<String, String>) -> Option<Arc<ClientState>> {
let token = env.get("STARDUST_STARTUP_TOKEN")?; let token = env.get("STARDUST_STARTUP_TOKEN")?;
STARTUP_SETTINGS.lock().get(token).cloned() CLIENT_STATES.lock().get(token).cloned()
} }
pub struct Client { pub struct Client {
@@ -63,7 +63,7 @@ pub struct Client {
pub scenegraph: Arc<Scenegraph>, pub scenegraph: Arc<Scenegraph>,
pub root: OnceCell<Arc<Root>>, pub root: OnceCell<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>, pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub startup_settings: Option<StartupSettings>, pub state: Arc<ClientState>,
} }
impl Client { impl Client {
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> { pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
@@ -80,7 +80,10 @@ impl Client {
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection); let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
let scenegraph = Arc::new(Scenegraph::default()); let scenegraph = Arc::new(Scenegraph::default());
let startup_settings = env.as_ref().and_then(startup_settings); let state = env
.as_ref()
.and_then(state)
.unwrap_or_else(|| Arc::new(ClientState::default()));
let client = CLIENTS.add(Client { let client = CLIENTS.add(Client {
pid, pid,
@@ -95,7 +98,7 @@ impl Client {
scenegraph: scenegraph.clone(), scenegraph: scenegraph.clone(),
root: OnceCell::new(), root: OnceCell::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
startup_settings, state,
}); });
let _ = client.scenegraph.client.set(Arc::downgrade(&client)); let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client)?); let _ = client.root.set(Root::create(&client)?);
@@ -107,7 +110,13 @@ impl Client {
data::create_interface(&client)?; data::create_interface(&client)?;
items::create_interface(&client)?; items::create_interface(&client)?;
input::create_interface(&client)?; input::create_interface(&client)?;
startup::create_interface(&client)?;
client
.root
.get()
.unwrap()
.node
.send_remote_signal("restore_state", serialize(client.state.apply_to(&client))?)?;
let pid_printable = pid let pid_printable = pid
.map(|pid| pid.to_string()) .map(|pid| pid.to_string())
@@ -164,6 +173,27 @@ impl Client {
Ok(client) Ok(client)
} }
pub fn get_cmdline(&self) -> Option<Vec<String>> {
let pid = self.pid?;
let exe_proc_path = format!("/proc/{pid}/exe");
let cmdline_proc_path = format!("/proc/{pid}/cmdline");
let exe = std::fs::read_link(exe_proc_path).ok()?;
let cmdline = std::fs::read_to_string(cmdline_proc_path).ok()?;
let mut cmdline_split: Vec<_> = cmdline.split('\0').map(ToString::to_string).collect();
cmdline_split.pop();
*cmdline_split.get_mut(0).unwrap() = exe.to_str()?.to_string();
Some(cmdline_split)
}
pub fn get_cwd(&self) -> Option<PathBuf> {
let pid = self.pid?;
let cwd_proc_path = format!("/proc/{pid}/cwd");
std::fs::read_link(cwd_proc_path).ok()
}
pub async fn save_state(&self) -> Option<ClientState> {
let internal = self.root.get()?.save_state().await.ok()?;
Some(dbg!(ClientState::from_deserialized(self, internal)))
}
#[inline] #[inline]
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> { pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
self.scenegraph self.scenegraph

94
src/core/client_state.rs Normal file
View File

@@ -0,0 +1,94 @@
use super::client::Client;
use crate::nodes::{spatial::Spatial, Node};
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, sync::Arc};
lazy_static::lazy_static! {
pub static ref CLIENT_STATES: Mutex<FxHashMap<String, Arc<ClientState>>> = Default::default();
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientState {
pub cmdline: Option<Vec<String>>,
pub cwd: Option<PathBuf>,
pub data: Option<Vec<u8>>,
pub root: Mat4,
pub spatial_anchors: FxHashMap<String, Mat4>,
}
impl ClientState {
pub fn from_deserialized(client: &Client, state: ClientStateInternal) -> Self {
ClientState {
cmdline: client.get_cmdline(),
cwd: client.get_cwd(),
data: state.data,
root: Self::spatial_transform(client, &state.root.unwrap_or_default())
.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, path: &str) -> Option<Mat4> {
let node = client.scenegraph.get_node(path)?;
let spatial = node.spatial.get()?;
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 to_file(self) {
let state_dir = directories::ProjectDirs::from("", "", "stardust")
.unwrap()
.state_dir()
.unwrap();
// std::fs::File::create(state_dir.join(""))
}
pub fn apply_to(&self, client: &Arc<Client>) -> ClientStateInternal {
if let Some(root) = client.root.get() {
root.set_transform(self.root)
}
ClientStateInternal {
data: self.data.clone(),
root: Some("/".to_string()),
spatial_anchors: self
.spatial_anchors
.iter()
.map(|(k, v)| {
(k.clone(), {
let node = Node::create(client, "/spatial/anchor", k, true)
.add_to_scenegraph()
.unwrap();
Spatial::add_to(&node, None, *v, false).unwrap();
k.clone()
})
})
.collect(),
}
}
}
impl Default for ClientState {
fn default() -> Self {
Self {
cmdline: None,
cwd: None,
data: None,
root: Mat4::IDENTITY,
spatial_anchors: Default::default(),
}
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct ClientStateInternal {
data: Option<Vec<u8>>,
root: Option<String>,
spatial_anchors: FxHashMap<String, String>,
}

View File

@@ -1,4 +1,5 @@
pub mod client; pub mod client;
pub mod client_state;
pub mod delta; pub mod delta;
pub mod destroy_queue; pub mod destroy_queue;
pub mod eventloop; pub mod eventloop;

View File

@@ -4,6 +4,8 @@ mod objects;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
mod wayland; mod wayland;
use crate::core::client::CLIENTS;
use crate::core::client_state::ClientState;
use crate::core::destroy_queue; use crate::core::destroy_queue;
use crate::nodes::items::camera; use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, hmd, input}; use crate::nodes::{audio, drawable, hmd, input};
@@ -27,6 +29,7 @@ use stereokit::{
TextureFormat, TextureType, TextureFormat, TextureType,
}; };
use stereokit::{DisplayBlend, Sk}; use stereokit::{DisplayBlend, Sk};
use tokio::task::LocalSet;
use tokio::{runtime::Handle, sync::oneshot}; use tokio::{runtime::Handle, sync::oneshot};
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info}; use tracing::{debug_span, error, info};
@@ -353,6 +356,7 @@ async fn event_loop(
_ = stop_rx => (), _ = stop_rx => (),
}; };
} }
save_clients().await;
info!("Cleanly shut down event loop"); info!("Cleanly shut down event loop");
@@ -362,3 +366,17 @@ async fn event_loop(
Ok(()) Ok(())
} }
async fn save_clients() {
let local_set = LocalSet::new();
for client in CLIENTS.get_vec() {
local_set.spawn_local(async move {
tokio::select! {
biased;
s = client.save_state() => {s.map(ClientState::to_file);},
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
}
});
}
local_set.await;
}

View File

@@ -109,15 +109,15 @@ impl Item {
} }
let _ = node.item.set(item.clone()); let _ = node.item.set(item.clone());
if let Some(auto_acceptor) = node.get_client().and_then(|client| { // if let Some(auto_acceptor) = node.get_client().and_then(|client| {
client // client
.startup_settings // .state
.as_ref() // .as_ref()
.and_then(|settings| settings.acceptors.get(type_info)) // .and_then(|settings| settings.acceptors.get(type_info))
.and_then(|acceptor| acceptor.upgrade()) // .and_then(|acceptor| acceptor.upgrade())
}) { // }) {
capture(&item, &auto_acceptor); // capture(&item, &auto_acceptor);
} // }
item item
} }

View File

@@ -1,11 +1,11 @@
use crate::{ use crate::{
core::{ core::{
client::{get_env, startup_settings, Client, INTERNAL_CLIENT}, client::{get_env, state, Client, INTERNAL_CLIENT},
registry::Registry, registry::Registry,
}, },
nodes::{ nodes::{
drawable::{model::ModelPart, Drawable}, drawable::{model::ModelPart, Drawable},
items::{self, Item, ItemType, TypeInfo}, items::{Item, ItemType, TypeInfo},
spatial::Spatial, spatial::Spatial,
Message, Node, Message, Node,
}, },
@@ -223,7 +223,7 @@ impl<B: Backend + ?Sized> PanelItem<B> {
let startup_settings = pid let startup_settings = pid
.and_then(|pid| get_env(pid).ok()) .and_then(|pid| get_env(pid).ok())
.and_then(|env| startup_settings(&env)); .and_then(|env| state(&env));
let uid = nanoid!(); let uid = nanoid!();
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true) let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
@@ -231,7 +231,7 @@ impl<B: Backend + ?Sized> PanelItem<B> {
.unwrap(); .unwrap();
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap(); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
if let Some(startup_settings) = &startup_settings { if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(startup_settings.transform); spatial.set_local_transform(startup_settings.root);
} }
let panel_item = Arc::new(PanelItem { let panel_item = Arc::new(PanelItem {
@@ -241,23 +241,13 @@ impl<B: Backend + ?Sized> PanelItem<B> {
}); });
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone(); let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
let item = Item::add_to( Item::add_to(
&node, &node,
uid, uid,
&ITEM_TYPE_INFO_PANEL, &ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item), ItemType::Panel(generic_panel_item),
); );
if let Some(startup_settings) = &startup_settings {
if let Some(acceptor) = startup_settings
.acceptors
.get(&*ITEM_TYPE_INFO_PANEL)
.and_then(|acc| acc.upgrade())
{
items::capture(&item, &acceptor);
}
}
node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex); node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex);
node.add_local_signal("close_toplevel", Self::close_toplevel_flex); node.add_local_signal("close_toplevel", Self::close_toplevel_flex);
node.add_local_signal("auto_size_toplevel", Self::auto_size_toplevel_flex); node.add_local_signal("auto_size_toplevel", Self::auto_size_toplevel_flex);

View File

@@ -8,7 +8,6 @@ pub mod input;
pub mod items; pub mod items;
pub mod root; pub mod root;
pub mod spatial; pub mod spatial;
pub mod startup;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use nanoid::nanoid; use nanoid::nanoid;
@@ -20,6 +19,7 @@ use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::deserialize;
use std::fmt::Debug; use std::fmt::Debug;
use std::future::Future;
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::vec::Vec; use std::vec::Vec;
@@ -38,8 +38,8 @@ use self::input::{InputHandler, InputMethod};
use self::items::{Item, ItemAcceptor, ItemUI}; use self::items::{Item, ItemAcceptor, ItemUI};
use self::spatial::zone::Zone; use self::spatial::zone::Zone;
use self::spatial::Spatial; use self::spatial::Spatial;
use self::startup::StartupSettings;
#[derive(Default)]
pub struct Message { pub struct Message {
pub data: Vec<u8>, pub data: Vec<u8>,
pub fds: Vec<OwnedFd>, pub fds: Vec<OwnedFd>,
@@ -97,9 +97,6 @@ pub struct Node {
// Sound // Sound
pub sound: OnceCell<Arc<Sound>>, pub sound: OnceCell<Arc<Sound>>,
// Startup
pub startup_settings: OnceCell<Mutex<StartupSettings>>,
} }
impl Node { impl Node {
@@ -143,7 +140,6 @@ impl Node {
item_acceptor: OnceCell::new(), item_acceptor: OnceCell::new(),
item_ui: OnceCell::new(), item_ui: OnceCell::new(),
sound: OnceCell::new(), sound: OnceCell::new(),
startup_settings: OnceCell::new(),
}; };
node.add_local_signal("set_enabled", Node::set_enabled_flex); node.add_local_signal("set_enabled", Node::set_enabled_flex);
node.add_local_signal("destroy", Node::destroy_flex); node.add_local_signal("destroy", Node::destroy_flex);
@@ -302,20 +298,30 @@ impl Node {
Ok(()) Ok(())
} }
// #[instrument(level = "debug", skip_all)] // #[instrument(level = "debug", skip_all)]
// pub fn execute_remote_method( pub fn execute_remote_method(
// &self, &self,
// method: &str, method: &str,
// data: Vec<u8>, message: impl Into<Message>,
// ) -> Result<impl Future<Output = Result<Message>>> { ) -> Result<impl Future<Output = Result<Message>>> {
// let message_sender_handle = self let message = message.into();
// .message_sender_handle let message_sender_handle = self
// .as_ref() .message_sender_handle
// .ok_or(eyre!("Messenger does not exist for this node"))?; .as_ref()
.ok_or(eyre!("Messenger does not exist for this node"))?;
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?; let future =
message_sender_handle.method(self.path.as_str(), method, &message.data, message.fds)?;
// Ok(async { future.await.map_err(|e| eyre!(e)) }) Ok(async {
// } match future.await {
Ok(m) => {
let (data, fds) = m.into_components();
Ok(Message { data, fds })
}
Err(e) => Err(eyre!(e)),
}
})
}
} }
impl Debug for Node { impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@@ -1,19 +1,25 @@
use super::spatial::Spatial; use super::spatial::Spatial;
use super::{Message, Node}; use super::{Message, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::client_state::{ClientState, ClientStateInternal};
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
use crate::wayland::WAYLAND_DISPLAY;
use crate::STARDUST_INSTANCE;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use tracing::instrument; use tracing::instrument;
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
static ROOT_REGISTRY: Registry<Root> = Registry::new(); static ROOT_REGISTRY: Registry<Root> = Registry::new();
pub struct Root { pub struct Root {
node: Arc<Node>, pub node: Arc<Node>,
send_frame_event: AtomicBool, send_frame_event: AtomicBool,
} }
impl Root { impl Root {
@@ -21,17 +27,13 @@ impl Root {
let node = Node::create(client, "", "", false); let node = Node::create(client, "", "", false);
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex); node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex); node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
let node = node.add_to_scenegraph()?; node.add_local_method("state_token", Root::state_token_flex);
let _ = Spatial::add_to( node.add_local_method(
&node, "get_connection_environment",
None, get_connection_environment_flex,
client
.startup_settings
.as_ref()
.map(|settings| settings.transform)
.unwrap_or(Mat4::IDENTITY),
false,
); );
let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(&node, None, client.state.root, false);
Ok(ROOT_REGISTRY.add(Root { Ok(ROOT_REGISTRY.add(Root {
node, node,
@@ -72,6 +74,31 @@ impl Root {
*calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?; *calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
Ok(()) Ok(())
} }
fn state_token_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(|| {
let state: ClientStateInternal = deserialize(message.as_ref())?;
let token = ClientState::from_deserialized(&calling_client, state).token();
Ok(serialize(token)?.into())
})
}
pub fn set_transform(&self, transform: Mat4) {
let spatial = self.node.spatial.get().unwrap();
spatial.set_spatial_parent(None).unwrap();
spatial.set_local_transform(transform);
}
pub fn save_state(&self) -> impl Future<Output = Result<ClientStateInternal>> {
let future = self
.node
.execute_remote_method("save_state", Message::default());
async move { Ok(deserialize(&future?.await?.data)?) }
}
} }
impl Drop for Root { impl Drop for Root {
@@ -79,3 +106,33 @@ impl Drop for Root {
ROOT_REGISTRY.remove(self); ROOT_REGISTRY.remove(self);
} }
} }
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: &Node,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
var_env_insert!(env, DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
});
}

View File

@@ -1,165 +0,0 @@
#[cfg(feature = "xwayland")]
use crate::wayland::xwayland::DISPLAY;
use crate::{
core::{client::Client, scenegraph::MethodResponseSender},
wayland::WAYLAND_DISPLAY,
STARDUST_INSTANCE,
};
use super::{
items::{ItemAcceptor, TypeInfo},
spatial::find_spatial,
Message, Node,
};
use color_eyre::eyre::Result;
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
lazy_static::lazy_static! {
pub static ref STARTUP_SETTINGS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
}
#[derive(Default, Clone)]
pub struct StartupSettings {
pub transform: Mat4,
pub acceptors: FxHashMap<&'static TypeInfo, Weak<ItemAcceptor>>,
}
impl StartupSettings {
pub fn add_to(node: &Arc<Node>) {
let _ = node
.startup_settings
.set(Mutex::new(StartupSettings::default()));
}
fn set_root_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let spatial = find_spatial(
&calling_client,
"Root spatial",
deserialize(message.as_ref())?,
)?;
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
Ok(())
}
fn add_automatic_acceptor_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let acceptor_node =
calling_client.get_node("Item acceptor", deserialize(message.as_ref())?)?;
let acceptor =
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
let mut startup_settings = node.startup_settings.get().unwrap().lock();
startup_settings
.acceptors
.insert(acceptor.type_info, Arc::downgrade(acceptor));
Ok(())
}
fn generate_startup_token_flex(
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let id = nanoid::nanoid!();
let data = serialize(&id)?;
STARTUP_SETTINGS
.lock()
.insert(id, node.startup_settings.get().unwrap().lock().clone());
Ok(data.into())
});
}
}
impl Debug for StartupSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StartupSettings")
.field("transform", &self.transform)
.field(
"acceptors",
&self
.acceptors
.iter()
.map(|(k, _)| k.type_name)
.collect::<Vec<_>>(),
)
.finish()
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "startup", false);
node.add_local_signal("create_startup_settings", create_startup_settings_flex);
node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_startup_settings_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let node = Node::create(
&calling_client,
"/startup/settings",
deserialize(message.as_ref())?,
true,
)
.add_to_scenegraph()?;
StartupSettings::add_to(&node);
node.add_local_signal("set_root", StartupSettings::set_root_flex);
node.add_local_signal(
"add_automatic_acceptor",
StartupSettings::add_automatic_acceptor_flex,
);
node.add_local_method(
"generate_startup_token",
StartupSettings::generate_startup_token_flex,
);
Ok(())
}
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: &Node,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
var_env_insert!(env, DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
});
}