feat: client state (save/restore)
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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
94
src/core/client_state.rs
Normal 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>,
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user