From 56dade8bab5cd3ca13d3a145b65ab852daca67ac Mon Sep 17 00:00:00 2001 From: Reboot-Codes Date: Sun, 17 Aug 2025 13:15:39 -0700 Subject: [PATCH] Support statically configured virtual displays --- .../components/video/displays/models.rs | 4 +- .../src/server/modman/components/video/mod.rs | 4 +- .../modman/gestures/command_generator.rs | 1 + clover-hub/src/server/modman/gestures/mod.rs | 127 +++++++++++ clover-hub/src/server/modman/ipc/gestures.rs | 65 ------ clover-hub/src/server/modman/mod.rs | 1 + clover-hub/src/server/renderer/mod.rs | 13 +- clover-hub/src/server/renderer/models.rs | 51 +++++ .../src/server/renderer/system_ui/mod.rs | 17 +- .../system_ui/systems/displays/mod.rs | 200 ++++++++++-------- .../systems/simulated_controls/mod.rs | 6 +- .../src/server/warehouse/config/models.rs | 3 + .../default/gesture-pack.clover.json | 26 +++ 13 files changed, 355 insertions(+), 163 deletions(-) create mode 100644 clover-hub/src/server/modman/gestures/command_generator.rs create mode 100644 clover-hub/src/server/modman/gestures/mod.rs create mode 100644 clover-hub/src/server/renderer/models.rs diff --git a/clover-hub/src/server/modman/components/video/displays/models.rs b/clover-hub/src/server/modman/components/video/displays/models.rs index ce12c4c..fbc1131 100644 --- a/clover-hub/src/server/modman/components/video/displays/models.rs +++ b/clover-hub/src/server/modman/components/video/displays/models.rs @@ -34,7 +34,7 @@ pub struct PhysicalDisplayComponent { impl DisplayComponent for PhysicalDisplayComponent {} -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Component, Serialize, Deserialize)] pub struct VirtualDisplayComponent { /// Position and Component ID of the displays that make up this Virtual Display pub displays: HashMap, @@ -45,7 +45,7 @@ pub struct VirtualDisplayComponent { impl DisplayComponent for VirtualDisplayComponent {} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DisplayPosition { pub x: f64, pub y: f64, diff --git a/clover-hub/src/server/modman/components/video/mod.rs b/clover-hub/src/server/modman/components/video/mod.rs index 9a7bc46..bbfb238 100644 --- a/clover-hub/src/server/modman/components/video/mod.rs +++ b/clover-hub/src/server/modman/components/video/mod.rs @@ -8,6 +8,6 @@ use std::num::NonZero; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VideoResolution { - pub height: NonZero, - pub width: NonZero, + pub height: NonZero, + pub width: NonZero, } diff --git a/clover-hub/src/server/modman/gestures/command_generator.rs b/clover-hub/src/server/modman/gestures/command_generator.rs new file mode 100644 index 0000000..b56b5f9 --- /dev/null +++ b/clover-hub/src/server/modman/gestures/command_generator.rs @@ -0,0 +1 @@ +pub async fn gesture_command_generator() {} diff --git a/clover-hub/src/server/modman/gestures/mod.rs b/clover-hub/src/server/modman/gestures/mod.rs new file mode 100644 index 0000000..64d45f7 --- /dev/null +++ b/clover-hub/src/server/modman/gestures/mod.rs @@ -0,0 +1,127 @@ +// TODO: Manifests specify bodies? Allow globs. +// TODO: Each area has default applicable gestures +/* +Default area specification + - com.reboot-codes.clover.CORE.human + - torso + - core + - back + - spine + - upper/middle/bottom + - front + - chest + - font + - right/center/left + - back + - right/center/left + - arms + - right/left + - shoulder + - biceps + - forearm + - hand + - fingers + - thumb + - index + - middle + - ring + - pinky + - head + - neck + - face + - jaw + - mouth + - nose + - eyes + - right/left + - eye + - eyelids + - upper/lower + - eyebrow + - ears + right/left + - top + - back + - pelivs + - waist + - left/right/upper/lower + - front + - upper/lower + - bottom + - rear + - right/left + - legs + - right/left + - thigh + - shin + - foot + - toes + - hallux + - index + - middle + - ring + - pinky +*/ + +pub mod command_generator; + +use std::sync::Arc; + +use log::{ + debug, + error, +}; +use tokio_util::sync::CancellationToken; + +use crate::server::modman::models::{ + GestureState, + ModManStore, +}; + +pub async fn gesture_command_generator_manager( + store: Arc, + cancellation_token: CancellationToken, +) { + while !cancellation_token.is_cancelled() { + let gestures_store_guards = vec![ + store.background_gesture_priority.lock().await, + store.foreground_gesture_priority.lock().await, + ]; + + for mut gestures_store_guard in gestures_store_guards { + let mut gestures_to_remove = vec![]; + let mut gesture_index_plus_one: usize = 0; + + for gesture_id in gestures_store_guard.iter() { + gesture_index_plus_one += 1; + + match store.gesture_states.lock().await.get(gesture_id) { + Some(gesture_state) => match gesture_state.current_state { + GestureState::Begin { + intensity, + speed, + background, + } => todo!(), + GestureState::Pause => todo!(), + _ => { + // Not covered by this function, see `super::ipc::gestures`! + } + }, + None => { + error!("Gesture: {}, is not in state map, removing from background gesture array! This state de-sync is not permissable and should be reported as a bug!", gesture_id.clone()); + gestures_to_remove.push((gesture_index_plus_one - 1, gesture_id.clone())); + continue; + } + } + } + + for (gesture_index, gesture_id) in gestures_to_remove { + debug!( + "Removing gesture: {} (@ index: {})...", + gesture_id, gesture_index + ); + gestures_store_guard.remove(gesture_index); + } + } + } +} diff --git a/clover-hub/src/server/modman/ipc/gestures.rs b/clover-hub/src/server/modman/ipc/gestures.rs index e04225b..4a080c3 100644 --- a/clover-hub/src/server/modman/ipc/gestures.rs +++ b/clover-hub/src/server/modman/ipc/gestures.rs @@ -1,68 +1,3 @@ -// TODO: Manifests specify bodies? Allow globs. -// TODO: Each area has default applicable gestures -/* -Default area specification - - com.reboot-codes.clover.CORE.human - - torso - - core - - back - - spine - - upper/middle/bottom - - front - - chest - - font - - right/center/left - - back - - right/center/left - - arms - - right/left - - shoulder - - biceps - - forearm - - hand - - fingers - - thumb - - index - - middle - - ring - - pinky - - head - - neck - - face - - jaw - - mouth - - nose - - eyes - - right/left - - eye - - eyelids - - upper/lower - - eyebrow - - ears - right/left - - top - - back - - pelivs - - waist - - left/right/upper/lower - - front - - upper/lower - - bottom - - rear - - right/left - - legs - - right/left - - thigh - - shin - - foot - - toes - - hallux - - index - - middle - - ring - - pinky -*/ - use std::sync::Arc; use log::{ diff --git a/clover-hub/src/server/modman/mod.rs b/clover-hub/src/server/modman/mod.rs index f75a5c8..9c37783 100644 --- a/clover-hub/src/server/modman/mod.rs +++ b/clover-hub/src/server/modman/mod.rs @@ -1,5 +1,6 @@ pub mod busses; pub mod components; +pub mod gestures; pub mod ipc; pub mod models; pub mod modules; diff --git a/clover-hub/src/server/renderer/mod.rs b/clover-hub/src/server/renderer/mod.rs index 3bdd794..468b1d3 100644 --- a/clover-hub/src/server/renderer/mod.rs +++ b/clover-hub/src/server/renderer/mod.rs @@ -1,4 +1,5 @@ pub mod ipc; +pub mod models; pub mod system_ui; use self::system_ui::system_ui_main; @@ -16,8 +17,8 @@ use nexus::{ use queues::*; use std::sync::Arc; use system_ui::{ - CustomBevyIPC, ExitState, + SystemUIIPC, }; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; @@ -65,13 +66,19 @@ pub async fn renderer_main( info!("Starting Renderer..."); let (bevy_cancel_tx, bevy_cancel_rx) = std::sync::mpsc::channel(); - let bevy_cancel_ipc = CustomBevyIPC { + let mut system_ui_ipc = SystemUIIPC { exit_channel: RecvSync(bevy_cancel_rx), display_registration_queue: queue![], }; + for (display_id, display) in store.config.lock().await.renderer.virtual_displays.clone() { + system_ui_ipc + .display_registration_queue + .add(system_ui::AnyDisplayComponent::Virtual(display)); + } + // TODO: Add this as a CLI/Config option! - std::thread::spawn(|| system_ui_main(bevy_cancel_ipc, Some(true))); + std::thread::spawn(|| system_ui_main(system_ui_ipc, Some(false))); // let display_handles = Arc::new(HashMap::new()); diff --git a/clover-hub/src/server/renderer/models.rs b/clover-hub/src/server/renderer/models.rs new file mode 100644 index 0000000..2e2030f --- /dev/null +++ b/clover-hub/src/server/renderer/models.rs @@ -0,0 +1,51 @@ +use std::{ + collections::HashMap, + num::NonZero, +}; + +use serde::{ + Deserialize, + Serialize, +}; + +use crate::server::modman::components::video::{ + displays::models::{ + DisplayPosition, + VirtualDisplayComponent, + }, + VideoResolution, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RendererConfig { + pub virtual_displays: HashMap, +} + +impl Default for RendererConfig { + /// Ensure that there is a display if the compositor was compiled in + /// and there wasn't a display defined in the config/disabled explicitly. + fn default() -> Self { + let mut virtual_displays = HashMap::new(); + + #[cfg(feature = "compositor")] + { + let mut displays = HashMap::new(); + + displays.insert("0".to_string(), DisplayPosition { x: 0.0, y: 0.0 }); + + virtual_displays.insert( + "debug_display".to_string(), + VirtualDisplayComponent { + displays, + resolution: VideoResolution { + height: NonZero::new(200).unwrap(), + width: NonZero::new(500).unwrap(), + }, + gesture_config: None, + }, + ); + } + + Self { virtual_displays } + } +} diff --git a/clover-hub/src/server/renderer/system_ui/mod.rs b/clover-hub/src/server/renderer/system_ui/mod.rs index b496153..4148358 100644 --- a/clover-hub/src/server/renderer/system_ui/mod.rs +++ b/clover-hub/src/server/renderer/system_ui/mod.rs @@ -1,7 +1,10 @@ pub mod plugins; pub mod systems; -use crate::server::modman::components::video::displays::models::PhysicalDisplayComponent; +use crate::server::modman::components::video::displays::models::{ + PhysicalDisplayComponent, + VirtualDisplayComponent, +}; use crate::utils::RecvSync; use bevy::{ app::{ @@ -30,13 +33,19 @@ pub enum ExitState { unsafe impl Sync for ExitState {} +#[derive(Debug, Clone)] +pub enum AnyDisplayComponent { + Physical(PhysicalDisplayComponent), + Virtual(VirtualDisplayComponent), +} + #[derive(Resource)] -pub struct CustomBevyIPC { +pub struct SystemUIIPC { pub exit_channel: RecvSync, - pub display_registration_queue: Queue, + pub display_registration_queue: Queue, } -pub fn system_ui_main(custom_bevy_ipc: CustomBevyIPC, disable_winit: Option) { +pub fn system_ui_main(custom_bevy_ipc: SystemUIIPC, disable_winit: Option) { let mut app = App::new(); app diff --git a/clover-hub/src/server/renderer/system_ui/systems/displays/mod.rs b/clover-hub/src/server/renderer/system_ui/systems/displays/mod.rs index 729c9a2..61be1fe 100644 --- a/clover-hub/src/server/renderer/system_ui/systems/displays/mod.rs +++ b/clover-hub/src/server/renderer/system_ui/systems/displays/mod.rs @@ -1,10 +1,15 @@ -use crate::server::renderer::system_ui::CustomBevyIPC; +use crate::server::renderer::system_ui::{ + AnyDisplayComponent, + SystemUIIPC, +}; use crate::server::{ modman::components::video::displays::models::VirtualDisplayComponent, renderer::system_ui::systems::view_management::Composition, }; +use bevy::math::f32; use bevy::prelude::*; use bevy::render::camera::RenderTarget; +use bevy::window::WindowResolution; #[cfg(feature = "compositor")] use bevy::window::{ Monitor, @@ -14,113 +19,138 @@ use bevy::window::{ use log::warn; use queues::*; -pub fn vdisplay_registrar(mut commands: Commands, mut ipc: ResMut) {} +pub fn vdisplay_registrar(mut commands: Commands, mut ipc: ResMut) {} pub fn display_registrar( mut commands: Commands, - mut ipc: ResMut, + mut ipc: ResMut, #[cfg(feature = "compositor")] monitor_entities_query: Query<(Entity, &Monitor)>, vdisplay_query: Query<(Entity, &mut VirtualDisplayComponent)>, ) { let display_queue = &mut ipc.display_registration_queue; - let num_displays = display_queue.size(); + match display_queue.remove() { + Ok(any_display) => { + debug!("Found display to register: {:?}", any_display.clone()); - if num_displays > 0 { - let mut displays = vec![]; - for _ in 0..(num_displays - 1) { - match display_queue.remove() { - Ok(display) => displays.push(display.clone()), - Err(_) => {} - } - } + match any_display { + AnyDisplayComponent::Physical(physical_display_component) => { + match physical_display_component.connection { + #[cfg(feature = "compositor")] + crate::server::modman::components::video::displays::models::ConnectionType::Direct( + direct_connection, + ) => { + let monitor = if direct_connection.display_id.clone() == "@primary" { + MonitorSelection::Primary + } else { + let mut chosen_monitor = None; + for (monitor_eid, monitor) in &monitor_entities_query { + if monitor.name == Some(direct_connection.display_id.clone()) { + chosen_monitor = Some(monitor_eid); + break; + } + } - for display in displays { - match display.connection { - #[cfg(feature = "compositor")] - crate::server::modman::components::video::displays::models::ConnectionType::Direct( - direct_connection, - ) => { - let monitor = if direct_connection.display_id.clone() == "@primary" { - MonitorSelection::Primary - } else { - let mut chosen_monitor = None; - for (monitor_eid, monitor) in &monitor_entities_query { - if monitor.name == Some(direct_connection.display_id.clone()) { - chosen_monitor = Some(monitor_eid); - break; - } - } + match chosen_monitor { + Some(monitor) => MonitorSelection::Entity(monitor), + None => { + // TODO: Is this really the best behaviour? + warn!("Requested display not found, defaulting to primary display..."); + MonitorSelection::Primary + } + } + }; - match chosen_monitor { - Some(monitor) => MonitorSelection::Entity(monitor), - None => { - // TODO: Is this really the best behaviour? - warn!("Requested display not found, defaulting to primary display..."); - MonitorSelection::Primary - } - } - }; + let windowed = if direct_connection.windowed { + WindowMode::Windowed + } else { + WindowMode::BorderlessFullscreen(monitor.clone()) + }; + let position = if direct_connection.windowed { + WindowPosition::Centered(monitor.clone()) + } else { + WindowPosition::Automatic + }; + + match physical_display_component.virtual_display { + Some(vdisplay_id) => { + match vdisplay_query.get(Entity::from_bits(vdisplay_id)) { + Ok(vdisplay) => { + // TODO: Create a camera for this display. - let windowed = if direct_connection.windowed { - WindowMode::Windowed - } else { - WindowMode::BorderlessFullscreen(monitor.clone()) - }; - let position = if direct_connection.windowed { - WindowPosition::Centered(monitor.clone()) - } else { - WindowPosition::Automatic - }; + commands.get_entity(vdisplay.0).unwrap().insert(Window { + title: "Clover SystemUI".into(), + mode: windowed, + position, + ..Default::default() + }); + } + Err(e) => {} + } + } + None => { + // TODO: Spawn a composition. Create a camera for this display. - match display.virtual_display { - Some(vdisplay_id) => { - match vdisplay_query.get(Entity::from_bits(vdisplay_id)) { - Ok(vdisplay) => { - // TODO: Create a camera for this display. + let window = commands + .spawn(Window { + title: format!("Clover SystemUI {}", direct_connection.display_id.clone()), + mode: windowed, + position, + ..Default::default() + }) + .id(); - commands.get_entity(vdisplay.0).unwrap().insert(Window { - title: "Clover SystemUI".into(), - mode: windowed, - position, - ..Default::default() - }); + commands.spawn(( + Camera3d::default(), + Camera { + target: RenderTarget::Window(WindowRef::Entity(window)), + ..default() + }, + Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + )); } - Err(e) => {} } - } - None => { - // TODO: Spawn a composition. Create a camera for this display. - - let window = commands - .spawn(Window { - title: format!("Clover SystemUI {}", direct_connection.display_id.clone()), - mode: windowed, - position, - ..Default::default() - }) - .id(); - commands.spawn(( - Camera3d::default(), - Camera { - target: RenderTarget::Window(WindowRef::Entity(window)), - ..default() - }, - Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), - )); + // TODO: Show new window! } + crate::server::modman::components::video::displays::models::ConnectionType::ModManProxy( + proxied_connection, + ) => {} + crate::server::modman::components::video::displays::models::ConnectionType::Stream( + stream_config, + ) => {} } + } + AnyDisplayComponent::Virtual(virtual_display_component) => { + debug!("Spawing VDisplay: {:?}", virtual_display_component.clone()); + // TODO: Spawn a composition. Create a camera for this display. + + let window = commands + .spawn(Window { + title: "Clover SystemUI: Virtual Display".to_owned(), + resolution: WindowResolution::new( + virtual_display_component.resolution.width.get() as f32, + virtual_display_component.resolution.height.get() as f32, + ), + ..Default::default() + }) + .id(); + + commands.spawn(( + Camera3d::default(), + Camera { + target: RenderTarget::Window(WindowRef::Entity(window)), + ..default() + }, + Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + )); // TODO: Show new window! + + debug!("Done spawning VDisplay!"); } - crate::server::modman::components::video::displays::models::ConnectionType::ModManProxy( - proxied_connection, - ) => {} - crate::server::modman::components::video::displays::models::ConnectionType::Stream( - stream_config, - ) => {} } } + Err(_e) => {} } } diff --git a/clover-hub/src/server/renderer/system_ui/systems/simulated_controls/mod.rs b/clover-hub/src/server/renderer/system_ui/systems/simulated_controls/mod.rs index 7fb7e57..d72104f 100644 --- a/clover-hub/src/server/renderer/system_ui/systems/simulated_controls/mod.rs +++ b/clover-hub/src/server/renderer/system_ui/systems/simulated_controls/mod.rs @@ -10,14 +10,16 @@ use bevy::{ window::Window, }; -pub fn make_visible(mut window: Query<&mut Window>, frames: Res) { +pub fn make_visible(mut windows: Query<&mut Window>, frames: Res) { // info!("Frame: {}", frames.0); // The delay may be different for your app or system. if frames.0 == 3 { // At this point the gpu is ready to show the app so we can make the window visible. // Alternatively, you could toggle the visibility in Startup. // It will work, but it will have one white frame before it starts rendering - window.get_single_mut().unwrap().visible = true; + for mut window in &mut windows { + window.visible = true; + } } } diff --git a/clover-hub/src/server/warehouse/config/models.rs b/clover-hub/src/server/warehouse/config/models.rs index 2eff31d..53e2520 100644 --- a/clover-hub/src/server/warehouse/config/models.rs +++ b/clover-hub/src/server/warehouse/config/models.rs @@ -11,6 +11,7 @@ use std::{ }; use crate::server::modman::models::ModManConfig; +use crate::server::renderer::models::RendererConfig; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { @@ -24,6 +25,7 @@ pub struct Config { /// Default gesture pack to use pub default_gesture_pack: String, pub modman: ModManConfig, + pub renderer: RendererConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,6 +48,7 @@ impl Default for Config { primary_api_key: utils::gen_api_key(), default_gesture_pack: "com.reboot-codes.clover.CORE.default".to_string(), modman: Default::default(), + renderer: Default::default(), } } } diff --git a/core/gesture-packs/default/gesture-pack.clover.json b/core/gesture-packs/default/gesture-pack.clover.json index 3bc48d8..634787a 100644 --- a/core/gesture-packs/default/gesture-pack.clover.json +++ b/core/gesture-packs/default/gesture-pack.clover.json @@ -1,3 +1,29 @@ { "name": "Default Gesture Pack", + "description": "Default/fallback gesture pack.", + "gestures": { + "@base.happy": { + "name": "Happy", + "configs": { + "*": { + "torso.head.face.eyes.*": { + "eyelids.*": 1.0, + "eyebrow": 0.75 + } + }, + "@base.furry.canine*": { + "torso.core.back.spine.tail": { + "@animation": { + "type": "bounce", + "speed": 1000.0, + "keyframes": [ + { "x": -45.0, "y": "@inital" }, + { "x": 45.0, "y": "@inital" } + ] + } + } + } + } + } + } } -- 2.51.2