103 Commits

Author SHA1 Message Date
Nova
643986e03d feat: version increment 2024-02-10 18:15:00 -05:00
Nova
a2b061ed43 fix(panel_item): fix set_toplevel_size signal 2024-02-10 18:12:29 -05:00
Nova
c458624157 fix(idl): update to main branch of core 2024-02-07 02:59:13 -05:00
Nova
1d2b149395 feat(cargo.toml): rust-version 2024-02-06 15:15:06 -05:00
Nova
c84848ea9c fix(spatial/bounds): move proper center bounds 2024-02-06 11:30:58 -05:00
Nova
b34e5f7a19 refactor(spatial): use clearer to understand global transform 2024-02-06 09:00:21 -05:00
Nova
af3d49c9ef fix(input/method): alias info server methods + create_path 2024-02-06 09:00:05 -05:00
Nova
d5d63b2f89 fix(fields): ray marching direction normalize 2024-02-06 08:59:40 -05:00
Nova
d4b7c3f61a refactor: use typemap for aspects! 2024-02-05 05:09:48 -05:00
Nova
36dacb3322 fix(audio+model): drop sk asset to the destroy queue 2024-02-04 20:22:15 -05:00
Nova
eec38dd60f fix(data): handle pulse receiver methods 2024-02-04 19:21:33 -05:00
Nova
1e0ea6ae92 refactor(node): use idl 2024-02-04 18:42:33 -05:00
Nova
31f45760f0 refactor(data): switch to using idl 2024-02-04 18:38:56 -05:00
Nova
f76863a79b refactor(audio): make audio use idl 2024-02-04 17:42:39 -05:00
Nova
43b3499ed7 refactor(drawable): use idl 2024-02-04 17:36:30 -05:00
Nova
dfaaa2a3a9 fix(codegen): make enums u32s over the wire 2024-02-04 17:10:38 -05:00
Nova
b8d17ac7ca refactor(fields): use idl 2024-02-04 11:04:16 -05:00
Nova
ed28914f86 refactor(idl): create_inteface macro 2024-02-04 09:55:39 -05:00
Nova
f8fab6bf5a refactor(idl,spatial): make zone use idl 2024-02-04 09:26:24 -05:00
Nova
1b37d77304 feat(spatial): use codegen 2024-02-03 14:14:27 -05:00
Nova
6eb36516b0 feat: initial IDL 2024-02-03 04:53:19 -05:00
Nova
f0200be990 feat(drawable/model): passthrough holdout material 2023-12-06 18:32:39 -05:00
Nova
cfd3d0016b feat(input/mouse_pointer): back/forward click grab 2023-12-06 17:06:07 -05:00
Nova
fbf0f4f672 fix(spatial): add spatial to children registry of parent on add_to 2023-11-25 10:43:08 -05:00
Nova
387fa96a60 feat(lines): multiple lines to a node 2023-11-25 06:25:44 -05:00
Nova
d549018024 feat: xwayland rootful and rootless (both broken) 2023-11-20 03:33:36 -05:00
Nova
5cd92f2c03 feat: upgrade smithay 2023-11-17 08:59:10 -05:00
Matthew Croughan
2450411e21 fix(flake): use ref instead of branch (#17)
Sometimes, for example when making a release, there is no branch, only a
ref like refs/head/v1, which means branch is set to null
2023-11-01 17:57:31 -04:00
Nova
b53bfde23b refactor(input): remove unnecessary limit break 2023-10-24 22:10:33 -04:00
Nova
15eb73af41 feat: 2x scaling 2023-10-24 22:09:09 -04:00
Nova
3c82ae309c fix(input): don't log anything 2023-10-24 01:18:39 -04:00
Nova
4adcfca2fe fix(input): don't debug captures 2023-10-24 00:54:27 -04:00
Nova
f893491bed feat(input): captured bool 2023-10-24 00:41:17 -04:00
Nova
9d1181aaca feat: todo on fields 2023-10-19 15:00:18 -04:00
Nova
b0f9bf24cf feat: initial stardust themes!! 2023-10-17 07:12:06 -04:00
Nova
3527ce2507 fix(pointer): proper distance 2023-10-08 19:18:51 -04:00
Nova
f045bfb93d feat: optimization 2023-10-08 18:44:52 -04:00
Nova
3d4ab27a14 fix(data): lesser key with empty vector being treated as different than typed vector 2023-10-07 13:38:06 -04:00
Nova
54ff87c146 remove: sk.kmp 2023-10-01 21:54:56 -04:00
Nova
2bc988fe3d fix: scroll 2023-10-01 21:47:13 -04:00
Nova
051893858b fix(wayland): don't log key events :p 2023-10-01 01:35:52 -04:00
Nova
1ac211c23f fix(wayland): keyboard input 2023-10-01 01:35:04 -04:00
Nova
4874f010dd fix: use core version on git 2023-10-01 00:40:05 -04:00
Nova
da894143f9 feat: proper ctrl+c stop 2023-09-29 00:26:53 -04:00
Nova
109affec81 feat: save client states on sigint 2023-09-28 12:08:45 -04:00
Nova
665e6b034f feat(state): save state to ~/.local/state/stardust/<uid> on graceful disconnect 2023-09-28 10:31:23 -04:00
Nova
fc45b4e400 feat: client state (save/restore) 2023-09-28 09:55:45 -04:00
Nova
af75d2a451 refactor(core): remove dashmap 2023-09-28 01:30:26 -04:00
Nova
3136a8f2b7 feat(suis/hand): distance per joint 2023-09-27 01:16:44 -04:00
Nova
e97368f3e2 feat: upgrade smithay 2023-09-25 22:38:00 -04:00
Nova
4da7ed1ccf feat(wayland): touch support 2023-09-24 11:44:26 -04:00
Nova
bf248e192f fix(objects/input): disable_controller still lets hands exist 2023-09-24 11:44:16 -04:00
Nova
167c3d1cbf fix(item): remove acceptor arg from release 2023-09-16 13:53:15 -04:00
Nova
b39d8f37f4 fix(panel): remove start data local signal 2023-09-16 13:52:53 -04:00
Nova
1198797db8 fix(wayland): don't clone topleveldata 2023-09-16 13:52:31 -04:00
Nova
823a71a286 fix(main): don't enable eye pointer for flatscreen mode with hands 2023-09-16 04:05:34 -04:00
Nova
f78da4b198 refactor(wayland/seat): boolean for keypress instead of u32 2023-09-08 20:23:40 -04:00
Nova
558fb1aa4e fix(flake.lock): update flatland 2023-09-08 12:31:10 -04:00
Nova
abe32a8dbd fix(panel): remove unnecessary inverse global transform on startup settings 2023-09-08 12:28:55 -04:00
Nova
d9e040bf8b fix(flake.lock): update flatland 2023-09-08 00:37:15 -04:00
Nova
0b8da1d280 feat: version bump 2023-09-04 17:12:42 -04:00
Nova
455f3a0e9c refactor: disable xwayland by default 2023-09-04 13:18:06 -04:00
Nova
411b30294b fix(xdg_shell): indicate fullscreen active 2023-09-04 13:17:48 -04:00
Nova
1d54b75a53 fix(wayland): remove unwraps 2023-09-04 12:15:48 -04:00
Nova
4c56c31bfc fix(object/mouse pointer): update keyboard protocol 2023-09-04 11:40:44 -04:00
Nova
b21e031668 fix(wayland): no more external dmabufs 2023-09-03 17:55:22 -04:00
Nova
7c6b2f5949 fix(wayland): closing toplevels 2023-09-03 11:02:42 -04:00
Nova
1f66d7dee4 fix(wayland): new API 2023-09-03 10:38:31 -04:00
Nova
53b035ef92 feat(wayland+xwayland): initial window setup 2023-09-03 10:38:31 -04:00
Nova
237b084a65 refactor(panel item): nuke unnecessary functions 2023-09-03 10:38:31 -04:00
Nova
707e56cb6f refactor: panel items 2023-09-03 10:38:31 -04:00
Nova
6f4da69e36 feat: async methods 2023-09-02 19:49:53 -04:00
Stephen Christie
5634729445 feat: Start converting method calls to async 2023-08-27 17:11:20 -04:00
matthewcroughan
b9603dc0a1 nix: refactor
- Remove unnecessary fenix input
- Remove unnecessary arguments to buildRustPackage
- Remove obsolete/unnecessary hacks
- Minimize code duplication and maximize re-use by using flake.parts
- Split out stardust-xr-server derivation into its own nix file in nix/stardust-xr-server
- Automatically get name of package from Cargo.toml
- Advertise support for riscv64-linux in flake outputs
2023-08-24 14:39:14 +01:00
Nova
0323fbfb86 fix(data): ignore types for masks if one is null 2023-08-23 13:57:42 -04:00
Nova
433568da63 fix: panel items not dropping on toplevel close 2023-08-14 03:05:22 -04:00
matthewcroughan
4e199dd43f nix: add new runtime dependencies, xwayland and bash 2023-08-08 07:15:48 +01:00
matthewcroughan
a4430b9a95 flake.lock: Update
Flake lock file updates:

• Updated input 'fenix':
    'github:nix-community/fenix/5816c7bbcc385d2e65877631497df3f7d66b354a' (2023-05-11)
  → 'github:nix-community/fenix/bd0c7ee0836a814751c3fcf66eaadfbe1a35b715' (2023-08-07)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf' (2023-05-09)
  → 'github:rust-lang/rust-analyzer/baee6b338b0ea076cd7a9f18d47f175dd2ba0e5d' (2023-08-06)
• Updated input 'flatland':
    'github:StardustXR/flatland/24613a496841bdf38e5f136608d5295860a75fce' (2023-05-11)
  → 'github:StardustXR/flatland/da6e286300c2a1f6e0ba103f9a79c53b9c3e70dc' (2023-08-06)
• Updated input 'hercules-ci-effects':
    'github:hercules-ci/hercules-ci-effects/15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0' (2023-04-19)
  → 'github:hercules-ci/hercules-ci-effects/0a63bfa3f00a3775ea3a6722b247880f1ffe91ce' (2023-07-15)
• Updated input 'hercules-ci-effects/flake-parts':
    'github:hercules-ci/flake-parts/c13d60b89adea3dc20704c045ec4d50dd964d447' (2023-03-09)
  → 'github:hercules-ci/flake-parts/8e8d955c22df93dbe24f19ea04f47a74adbdc5ec' (2023-07-04)
• Updated input 'hercules-ci-effects/flake-parts/nixpkgs-lib':
    'github:NixOS/nixpkgs/130fa0baaa2b93ec45523fdcde942f6844ee9f6e?dir=lib' (2023-03-09)
  → 'github:NixOS/nixpkgs/4bc72cae107788bf3f24f30db2e2f685c9298dc9?dir=lib' (2023-06-29)
• Updated input 'hercules-ci-effects/hercules-ci-agent':
    'github:hercules-ci/hercules-ci-agent/0b90d1a87c117a5861785cb85833dd1c9df0b6ef' (2023-03-10)
  → 'github:hercules-ci/hercules-ci-agent/367dd8cd649b57009a6502e878005a1e54ad78c5' (2023-07-05)
• Updated input 'hercules-ci-effects/hercules-ci-agent/flake-parts':
    'github:hercules-ci/flake-parts/c13d60b89adea3dc20704c045ec4d50dd964d447' (2023-03-09)
  → 'github:hercules-ci/flake-parts/8e8d955c22df93dbe24f19ea04f47a74adbdc5ec' (2023-07-04)
• Updated input 'hercules-ci-effects/hercules-ci-agent/haskell-flake':
    'github:hercules-ci/haskell-flake/1e1660e6dd00838ba73bc7952e6e73be67da18d1' (2023-03-06)
  → 'github:srid/haskell-flake/74210fa80a49f1b6f67223debdbf1494596ff9f2' (2023-05-22)
• Removed input 'hercules-ci-effects/hercules-ci-agent/nix-darwin'
• Removed input 'hercules-ci-effects/hercules-ci-agent/nix-darwin/nixpkgs'
• Updated input 'hercules-ci-effects/hercules-ci-agent/nixpkgs':
    'github:NixOS/nixpkgs/c90c4025bb6e0c4eaf438128a3b2640314b1c58d' (2023-03-08)
  → 'github:NixOS/nixpkgs/0fbe93c5a7cac99f90b60bdf5f149383daaa615f' (2023-07-02)
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/flake-compat'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/flake-utils'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/gitignore'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/gitignore/nixpkgs'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/nixpkgs'
• Removed input 'hercules-ci-effects/hercules-ci-agent/pre-commit-hooks-nix/nixpkgs-stable'
• Updated input 'hercules-ci-effects/nixpkgs':
    'github:NixOS/nixpkgs/1544ef240132d4357d9a39a40c8e6afd1678b052' (2023-03-15)
  → 'github:NixOS/nixpkgs/27fcd46fa18df36d270174246e7bd8f1787129ff' (2023-07-15)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/897876e4c484f1e8f92009fd11b7d988a121a4e7' (2023-05-06)
  → 'github:NixOS/nixpkgs/5a8e9243812ba528000995b294292d3b5e120947' (2023-08-07)
2023-08-08 07:15:43 +01:00
Nova
136383326e fix(panel): pressed/released in the right order 2023-08-07 21:44:44 -04:00
Nova
5a86f11beb fix(fields): remove radius from normal/closest point 2023-08-07 16:45:37 -04:00
Nova
6ab2bb2d52 fix: controller 2023-08-06 19:27:40 -04:00
Nova
ce8877b67e refactor(wayland): replace popups with child surfaces 2023-08-06 18:32:43 -04:00
Nova
74a2f7a249 fix: properly destroy xwayland 2023-08-06 11:19:42 -04:00
Nova
11ecb0aebe feat: camera item 2023-08-06 10:11:06 -04:00
Nova
281f5e91ff feat(registry): is_empty 2023-08-06 10:11:01 -04:00
Nova
5dc7cfbe83 refactor(drawable): remove sendwrapper 2023-08-06 10:10:50 -04:00
Nova
3432c63a6e fix(main): don't pass through std anything from child processes 2023-08-05 20:20:57 -04:00
Nova
02ac96b0dc feat(wayland): cleanup 2023-08-04 21:20:32 -04:00
Nova
0736f99631 feat: fd passing 2023-07-31 23:44:17 -04:00
Nova
4bbe3ad8d0 refactor(objects): overhaul input 2023-07-26 08:56:34 -04:00
Nova
1cf9d0f8c5 refactor (wayland): move seat to client 2023-07-25 14:46:03 -04:00
Nova
51d0cab832 refactor: trait away panel item backends 2023-07-23 19:59:35 -04:00
Nova
062c63af2b feat(xwayland): xwayland feature 2023-07-23 09:04:22 -04:00
Nova
3ce3fadb8d feat: todo fuure plans 2023-07-23 08:30:09 -04:00
Nova
f0e39195b7 fix: deadlock on close stereokit 2023-07-23 08:30:09 -04:00
Nova
0d639760e9 refactor(wayland): less crashy 2023-07-23 08:30:09 -04:00
Nova
6109a6bde6 feat(xwayland): x window capabilities 2023-07-23 08:30:09 -04:00
Nova
000b633767 feat(xwayland): first x window 2023-07-23 08:30:09 -04:00
Nova
e3b1276d77 feat(xwayland): serialize start 2023-07-23 08:30:09 -04:00
Nova
1cb8e1b7a4 refactor(wayland): separate backend from panel item 2023-07-23 08:30:09 -04:00
Nova
e879b724ec feat(wayland): initial xwayland support 2023-07-23 08:30:09 -04:00
Nova
08010efa46 fix: data uses flexbuffer type instead of value for mask map 2023-07-23 08:29:53 -04:00
Nova
e682931e3e fix: wayland stability 2023-07-23 08:29:04 -04:00
74 changed files with 6739 additions and 4561 deletions

3
.gitignore vendored
View File

@@ -11,4 +11,5 @@
*result* *result*
/libs/ /libs/
*.AppImage *.AppImage
*.blend1

1100
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,17 @@
[package] [package]
edition = "2021" edition = "2021"
rust-version = "1.75"
name = "stardust-xr-server" name = "stardust-xr-server"
version = "0.42.1" version = "0.44.0"
authors = ["Nova King <technobaboo@proton.me>"] authors = ["Nova King <technobaboo@proton.me>"]
description = "Stardust XR reference display server" description = "Stardust XR reference display server"
license = "GPLv2" license = "GPLv2"
repository = "https://github.com/StardustXR/stardust-xr-server/" repository = "https://github.com/StardustXR/stardust-xr-server/"
homepage = "https://stardustxr.org" homepage = "https://stardustxr.org"
[workspace]
members = ["codegen"]
[[bin]] [[bin]]
name = "stardust-xr-server" name = "stardust-xr-server"
path = "src/main.rs" path = "src/main.rs"
@@ -15,8 +19,10 @@ path = "src/main.rs"
[features] [features]
default = ["wayland"] default = ["wayland"]
wayland = ["dep:smithay", "dep:xkbcommon"] wayland = ["dep:smithay", "dep:xkbcommon"]
xwayland_rootful = []
xwayland_rootless = ["smithay/xwayland"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-chrome"] profile_app = ["dep:tracing-tracy"]
[package.metadata.appimage] [package.metadata.appimage]
auto_link = true auto_link = true
@@ -38,7 +44,6 @@ lto = true
[dependencies] [dependencies]
color-eyre = { version = "0.6.2", default-features = false } color-eyre = { version = "0.6.2", default-features = false }
clap = { version = "4.2.4", features = ["derive"] } clap = { version = "4.2.4", features = ["derive"] }
dashmap = "5.4.0"
glam = { version = "0.23.0", features = ["mint"] } glam = { version = "0.23.0", features = ["mint"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
mint = "0.5.9" mint = "0.5.9"
@@ -47,46 +52,62 @@ 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"
xkbcommon = { version = "0.5.0", default-features = false, optional = true }
stardust-xr = "0.11.4"
directories = "5.0.0" directories = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] } serde = { version = "1.0.160", features = ["derive"] }
serde_repr = "0.1.16"
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
global_counter = "0.2.2" global_counter = "0.2.2"
rand = "0.8.5" rand = "0.8.5"
atty = "0.2.14" atty = "0.2.14"
xkbcommon = { version = "0.7.0", default-features = false, optional = true }
[dependencies.stereokit] ctrlc = "3.4.1"
default-features = false libc = "0.2.148"
features = ["linux-egl"] input-event-codes = "5.16.8"
version = "0.16.7" nix = "0.27.1"
wayland-scanner = "0.31.0"
wayland-backend = "0.3.2"
cluFlock = "1.2.7"
fxtypemap = "0.2.0"
[dependencies.smithay] [dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures # git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
# path = "../smithay" # path = "../smithay"
default-features = false default-features = false
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"] features = [
"desktop",
"backend_drm",
"backend_egl",
"renderer_gl",
"wayland_frontend",
]
version = "*" version = "*"
optional = true optional = true
[dependencies.stereokit]
default-features = false
features = ["linux-egl"]
version = "0.16.9"
[dependencies.console-subscriber] [dependencies.console-subscriber]
version = "0.1.8" version = "0.1.8"
optional = true optional = true
[dependencies.tracing-chrome] [dependencies.tracing-tracy]
version = "0.7.1" version = "0.10.4"
optional = true optional = true
[dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
[dependencies.stardust-xr-server-codegen]
path = "codegen"
# [patch.crates-io.stereokit] # [patch.crates-io.stereokit]
# path = "../stereokit-rs" # path = "../stereokit-rs"
# [patch.crates-io.stereokit-sys] # [patch.crates-io.stereokit-sys]
# path = "../stereokit-sys" # path = "../stereokit-sys"
# [patch.crates-io.stardust-xr]
# path = "../core/core"
# [patch.crates-io.stardust-xr-schemas]
# path = "../core/schemas"

17
codegen/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
edition = "2021"
name = "stardust-xr-server-codegen"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
convert_case = "0.6.0"
quote = "1.0.33"
mint = "0.5.9"
proc-macro2 = "1.0.71"
split-iter = "0.1.0"
[dependencies.stardust-xr-schemas]
git = "https://github.com/StardustXR/core.git"

563
codegen/src/lib.rs Normal file
View File

@@ -0,0 +1,563 @@
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr_schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
quote!(#a #b)
}
// #[proc_macro]
// pub fn codegen_root_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(ROOT_PROTOCOL)
// }
#[proc_macro]
pub fn codegen_node_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(NODE_PROTOCOL)
}
#[proc_macro]
pub fn codegen_spatial_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(SPATIAL_PROTOCOL)
}
#[proc_macro]
pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(FIELD_PROTOCOL)
}
#[proc_macro]
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DATA_PROTOCOL)
}
#[proc_macro]
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(AUDIO_PROTOCOL)
}
#[proc_macro]
pub fn codegen_drawable_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DRAWABLE_PROTOCOL)
}
// #[proc_macro]
// pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(INPUT_PROTOCOL)
// }
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let protocol = Protocol::parse(protocol).unwrap();
let interface = protocol
.interface
.map(|p| {
let virtual_aspect_name = p.path[1..]
.split('/')
.map(ToString::to_string)
.reduce(|a, b| format!("{a}_{b}"))
.unwrap_or_default()
+ "_interface";
generate_aspect(&Aspect {
name: virtual_aspect_name,
description: protocol.description.clone(),
members: p.members,
})
})
.unwrap_or_default();
let custom_enums = protocol
.custom_enums
.iter()
.map(generate_custom_enum)
.reduce(fold_tokens)
.unwrap_or_default();
let custom_unions = protocol
.custom_unions
.iter()
.map(generate_custom_union)
.reduce(fold_tokens)
.unwrap_or_default();
let custom_structs = protocol
.custom_structs
.iter()
.map(generate_custom_struct)
.reduce(fold_tokens)
.unwrap_or_default();
let aspects = protocol
.aspects
.iter()
.map(generate_aspect)
.reduce(fold_tokens)
.unwrap_or_default();
// let nodes = protocol
// .nodes
// .iter()
// .map(generate_node)
// .reduce(fold_tokens)
// .unwrap_or_default();
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into()
}
fn generate_custom_enum(custom_enum: &CustomEnum) -> TokenStream {
let name = Ident::new(&custom_enum.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_enum.description;
let argument_decls = custom_enum
.variants
.iter()
.map(|a| Ident::new(&a.to_case(Case::Pascal), Span::call_site()).to_token_stream())
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, Copy, serde_repr::Deserialize_repr, serde_repr::Serialize_repr)]
#[repr(u32)]
pub enum #name {#argument_decls}
}
}
fn generate_custom_union(custom_union: &CustomUnion) -> TokenStream {
let name = Ident::new(&custom_union.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_union.description;
let option_decls = custom_union
.options
.iter()
.map(generate_union_option)
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum #name {#option_decls}
}
}
fn generate_union_option(union_option: &UnionOption) -> TokenStream {
let name = union_option
.name
.as_ref()
.map(|n| n.to_case(Case::Pascal))
.unwrap_or_else(|| argument_type_option_name(&union_option._type));
let description = union_option
.description
.as_ref()
.map(|d| quote!(#[doc = #d]))
.unwrap_or_default();
let identifier = Ident::new(&name, Span::call_site());
let _type = generate_argument_type(&union_option._type, false, true);
quote! (#description #identifier(#_type))
}
fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
let name = Ident::new(&custom_struct.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_struct.description;
let argument_decls = custom_struct
.fields
.iter()
.map(|a| generate_argument_decl(a, true))
.map(|d| quote!(pub #d))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct #name {#argument_decls}
}
}
// fn generate_node(node: &Node) -> TokenStream {
// let node_name = Ident::new(&node.name, Span::call_site());
// let description = &node.description;
// let aspects = node
// .aspects
// .iter()
// .map(|a| {
// let aspect_name = Ident::new(&format!("{a}Aspect"), Span::call_site());
// quote!(impl #aspect_name for #node_name {})
// })
// .reduce(fold_tokens)
// .unwrap_or_default();
// quote! {
// #[doc = #description]
// #[derive(Debug)]
// pub struct #node_name (crate::node::Node);
// impl crate::node::NodeType for #node_name {
// fn node(&self) -> &crate::node::Node {
// &self.0
// }
// fn alias(&self) -> Self {
// #node_name(self.0.alias())
// }
// fn from_path(client: &std::sync::Arc<crate::client::Client>, path: String, destroyable: bool) -> Self {
// #node_name(crate::node::Node::from_path(client, path, destroyable))
// }
// }
// impl serde::Serialize for #node_name {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let node_path = self.0.get_path().map_err(|e| serde::ser::Error::custom(e))?;
// serializer.serialize_str(&node_path)
// }
// }
// #aspects
// }
// }
fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description;
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server);
let client_mod_name = Ident::new(
&format!("{}_client", &aspect.name.to_case(Case::Snake)),
Span::call_site(),
);
let client_side_members = client_members
.map(generate_member)
.reduce(fold_tokens)
.map(|t| {
quote! {
pub mod #client_mod_name {
#t
}
}
})
.unwrap_or_default();
let aspect_trait_name = Ident::new(
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let server_side_members = server_members
.map(generate_member)
.reduce(fold_tokens)
.unwrap_or_default();
let add_node_members = aspect
.members
.iter()
.filter(|m| m.side == Side::Server)
.map(generate_handler)
.reduce(fold_tokens)
.map(|members| {
quote! {
fn add_node_members(node: &crate::nodes::Node) {
#members
}
}
})
.unwrap_or_default();
let server_side_members = quote! {
#[doc = #description]
pub trait #aspect_trait_name {
#add_node_members
#server_side_members
}
};
quote!(#client_side_members #server_side_members)
}
fn generate_member(member: &Member) -> TokenStream {
let name_str = &member.name;
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
let description = &member.description;
let side = member.side;
let _type = member._type;
let first_args = match member.side {
Side::Server => {
quote!(_node: std::sync::Arc<crate::nodes::Node>, _calling_client: std::sync::Arc<crate::core::client::Client>)
}
Side::Client => quote!(_node: &crate::nodes::Node),
};
let argument_decls = member
.arguments
.iter()
.map(|a| generate_argument_decl(a, member.side == Side::Server))
.fold(first_args, |a, b| quote!(#a, #b));
let argument_uses = member
.arguments
.iter()
.map(|a| generate_argument_serialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
let return_type = member
.return_type
.as_ref()
.map(|r| generate_argument_type(&r, false, true))
.unwrap_or_else(|| quote!(()));
match (side, _type) {
(Side::Client, MemberType::Method) => {
quote! {
#[doc = #description]
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<#return_type> {
_node.execute_remote_method(#name_str, &(#argument_uses)).await
}
}
}
(Side::Client, MemberType::Signal) => {
quote! {
#[doc = #description]
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
_node.send_remote_signal(#name_str, serialized)
}
}
}
(Side::Server, MemberType::Method) => {
quote! {
#[doc = #description]
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static;
}
}
(Side::Server, MemberType::Signal) => {
let prefix =
if let Some(ArgumentType::Node { _type, return_info }) = &member.return_type {
if let Some(return_info) = return_info {
let parent_name = Ident::new(
&(name_str.to_case(Case::ScreamingSnake) + "_PARENT_PATH"),
Span::call_site(),
);
let parent_path = &return_info.parent;
quote!(const #parent_name: &'static str = #parent_path;)
} else {
TokenStream::default()
}
} else {
TokenStream::default()
};
quote! {
#prefix
#[doc = #description]
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
}
}
}
}
fn generate_handler(member: &Member) -> TokenStream {
let member_name = &member.name;
let member_name_ident = Ident::new(&member_name, Span::call_site());
let argument_names = member
.arguments
.iter()
.map(generate_argument_name)
.reduce(|a, b| quote!(#a, #b));
let argument_types = member
.arguments
.iter()
.map(|a| {
let _type = convert_deserializeable_argument_type(&a._type);
generate_argument_type(&_type, a.optional, true)
})
.reduce(|a, b| quote!(#a, #b));
// dbg!(&argument_types);
let deserialize = argument_names
.clone()
.zip(argument_types)
.map(|(argument_names, argument_types)| {
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
})
.unwrap_or_default();
let argument_uses = member
.arguments
.iter()
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
match member._type {
MemberType::Signal => quote! {
node.add_local_signal(#member_name, |_node, _calling_client, _message| {
#deserialize
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
});
},
MemberType::Method => quote! {
node.add_local_method(#member_name, |_node, _calling_client, _message, _method_response| {
_method_response.wrap_async(async move {
#deserialize
Ok((Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?, Vec::new()))
});
});
},
}
}
fn generate_argument_name(argument: &Argument) -> TokenStream {
Ident::new(&argument.name.to_case(Case::Snake), Span::call_site()).to_token_stream()
}
fn convert_deserializeable_argument_type(argument_type: &ArgumentType) -> ArgumentType {
match argument_type {
ArgumentType::Node { .. } => ArgumentType::String,
f => f.clone(),
}
}
fn generate_argument_deserialize(
argument_name: &str,
argument_type: &ArgumentType,
optional: bool,
) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
match argument_type {
ArgumentType::Node { .. } => match optional {
true => quote!(#name.map(|n| _calling_client.get_node(#argument_name, &n)?)),
false => quote!(_calling_client.get_node(#argument_name, &#name)?),
},
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
ArgumentType::Vec(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
}
ArgumentType::Map(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
}
_ => quote!(#name),
}
}
fn generate_argument_serialize(
argument_name: &str,
argument_type: &ArgumentType,
optional: bool,
) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
match argument_type {
ArgumentType::Node {
_type,
return_info: _,
} => match optional {
true => quote!(#name.map(|n| n.get_path())),
false => quote!(#name.get_path()),
},
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
ArgumentType::Vec(v) => {
let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
}
ArgumentType::Map(v) => {
let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
}
_ => quote!(#name),
}
}
fn generate_argument_decl(argument: &Argument, owned_values: bool) -> TokenStream {
let name = Ident::new(&argument.name.to_case(Case::Snake), Span::call_site());
let mut _type = generate_argument_type(&argument._type, argument.optional, owned_values);
quote!(#name: #_type)
}
fn argument_type_option_name(argument_type: &ArgumentType) -> String {
match argument_type {
ArgumentType::Bool => "Bool".to_string(),
ArgumentType::Int => "Int".to_string(),
ArgumentType::UInt => "UInt".to_string(),
ArgumentType::Float => "Float".to_string(),
ArgumentType::Vec2 => "Vec2".to_string(),
ArgumentType::Vec3 => "Vec3".to_string(),
ArgumentType::Quat => "Quat".to_string(),
ArgumentType::Color => "Color".to_string(),
ArgumentType::String => "String".to_string(),
ArgumentType::Bytes => "Bytes".to_string(),
ArgumentType::Vec(v) => format!("{}Vector", argument_type_option_name(&v)),
ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(&m)),
ArgumentType::Datamap => "Datamap".to_string(),
ArgumentType::ResourceID => "ResourceID".to_string(),
ArgumentType::Enum(e) => e.clone(),
ArgumentType::Union(u) => u.clone(),
ArgumentType::Struct(s) => s.clone(),
ArgumentType::Node { _type, .. } => _type.clone(),
}
}
fn generate_argument_type(
argument_type: &ArgumentType,
optional: bool,
owned: bool,
) -> TokenStream {
let _type = match argument_type {
ArgumentType::Bool => quote!(bool),
ArgumentType::Int => quote!(i32),
ArgumentType::UInt => quote!(u32),
ArgumentType::Float => quote!(f32),
ArgumentType::Vec2 => quote!(mint::Vector2<f32>),
ArgumentType::Vec3 => quote!(mint::Vector3<f32>),
ArgumentType::Quat => quote!(mint::Quaternion<f32>),
ArgumentType::Color => quote!(stardust_xr::values::Color),
ArgumentType::Bytes => {
if !owned {
quote!(&[u8])
} else {
quote!(Vec<u8>)
}
}
ArgumentType::String => {
if !owned {
quote!(&str)
} else {
quote!(String)
}
}
ArgumentType::Vec(t) => {
let t = generate_argument_type(&t, false, true);
if !owned {
quote!(&[#t])
} else {
quote!(Vec<#t>)
}
}
ArgumentType::Map(t) => {
let t = generate_argument_type(&t, false, true);
if !owned {
quote!(&rustc_hash::FxHashMap<String, #t>)
} else {
quote!(rustc_hash::FxHashMap<String, #t>)
}
}
ArgumentType::Datamap => {
if !owned {
quote!(&stardust_xr::values::Datamap)
} else {
quote!(stardust_xr::values::Datamap)
}
}
ArgumentType::ResourceID => {
if !owned {
quote!(&stardust_xr::values::ResourceID)
} else {
quote!(stardust_xr::values::ResourceID)
}
}
ArgumentType::Enum(e) => {
let enum_name = Ident::new(&e.to_case(Case::Pascal), Span::call_site());
quote!(#enum_name)
}
ArgumentType::Union(u) => {
let union_name = Ident::new(&u.to_case(Case::Pascal), Span::call_site());
quote!(#union_name)
}
ArgumentType::Struct(s) => {
let struct_name = Ident::new(&s.to_case(Case::Pascal), Span::call_site());
if !owned {
quote!(&#struct_name)
} else {
quote!(#struct_name)
}
}
ArgumentType::Node {
_type,
return_info: _,
} => {
if !owned {
quote!(&std::sync::Arc<crate::nodes::Node>)
} else {
quote!(std::sync::Arc<crate::nodes::Node>)
}
}
};
if optional {
quote!(Option<#_type>)
} else {
_type
}
}

256
flake.lock generated
View File

@@ -1,33 +1,12 @@
{ {
"nodes": { "nodes": {
"fenix": { "fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1683786056,
"narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=",
"owner": "nix-community",
"repo": "fenix",
"rev": "5816c7bbcc385d2e65877631497df3f7d66b354a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"fenix_2": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"flatland", "flatland",
"nixpkgs" "nixpkgs"
], ],
"rust-analyzer-src": "rust-analyzer-src_2" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1678775037, "lastModified": 1678775037,
@@ -43,32 +22,34 @@
"type": "github" "type": "github"
} }
}, },
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": { "flake-parts": {
"inputs": { "inputs": {
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1678379998, "lastModified": 1690933134,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=", "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447", "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1688466019,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -76,7 +57,7 @@
"type": "indirect" "type": "indirect"
} }
}, },
"flake-parts_2": { "flake-parts_3": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"hercules-ci-effects", "hercules-ci-effects",
@@ -85,11 +66,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1678379998, "lastModified": 1688466019,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=", "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447", "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -98,32 +79,17 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flatland": { "flatland": {
"inputs": { "inputs": {
"fenix": "fenix_2", "fenix": "fenix",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1683766358, "lastModified": 1694190588,
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=", "narHash": "sha256-tcvp09A54AbXkkXS/P6Ed5TUWzMEuQ9h/fnfLz7gnJc=",
"owner": "StardustXR", "owner": "StardustXR",
"repo": "flatland", "repo": "flatland",
"rev": "24613a496841bdf38e5f136608d5295860a75fce", "rev": "3867067452761959a95497d6589f9336b347270c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,59 +98,34 @@
"type": "github" "type": "github"
} }
}, },
"gitignore": {
"inputs": {
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"pre-commit-hooks-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"haskell-flake": { "haskell-flake": {
"locked": { "locked": {
"lastModified": 1678138103, "lastModified": 1684780604,
"narHash": "sha256-D0lao82bV3t2gEFjHiU6RN233t+1MnkQV+bq8MEu2ic=", "narHash": "sha256-2uMZsewmRn7rRtAnnQNw1lj0uZBMh4m6Cs/7dV5YF08=",
"owner": "hercules-ci", "owner": "srid",
"repo": "haskell-flake", "repo": "haskell-flake",
"rev": "1e1660e6dd00838ba73bc7952e6e73be67da18d1", "rev": "74210fa80a49f1b6f67223debdbf1494596ff9f2",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "hercules-ci", "owner": "srid",
"ref": "0.1-extraLibraries", "ref": "0.3.0",
"repo": "haskell-flake", "repo": "haskell-flake",
"type": "github" "type": "github"
} }
}, },
"hercules-ci-agent": { "hercules-ci-agent": {
"inputs": { "inputs": {
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_3",
"haskell-flake": "haskell-flake", "haskell-flake": "haskell-flake",
"nix-darwin": "nix-darwin", "nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
}, },
"locked": { "locked": {
"lastModified": 1678446614, "lastModified": 1688568579,
"narHash": "sha256-Z6Gsba5ahn/N0QlF0vJfIEfnZgCs4qr1IZtXAqjbE7s=", "narHash": "sha256-ON0M56wtY/TIIGPkXDlJboAmuYwc73Hi8X9iJGtxOhM=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "hercules-ci-agent", "repo": "hercules-ci-agent",
"rev": "0b90d1a87c117a5861785cb85833dd1c9df0b6ef", "rev": "367dd8cd649b57009a6502e878005a1e54ad78c5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -194,16 +135,16 @@
}, },
"hercules-ci-effects": { "hercules-ci-effects": {
"inputs": { "inputs": {
"flake-parts": "flake-parts", "flake-parts": "flake-parts_2",
"hercules-ci-agent": "hercules-ci-agent", "hercules-ci-agent": "hercules-ci-agent",
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_3"
}, },
"locked": { "locked": {
"lastModified": 1681898675, "lastModified": 1689397210,
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=", "narHash": "sha256-fVxZnqxMbsDkB4GzGAs/B41K0wt/e+B/fLxmTFF/S20=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "hercules-ci-effects", "repo": "hercules-ci-effects",
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0", "rev": "0a63bfa3f00a3775ea3a6722b247880f1ffe91ce",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -212,28 +153,6 @@
"type": "github" "type": "github"
} }
}, },
"nix-darwin": {
"inputs": {
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1673295039,
"narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
"type": "github"
},
"original": {
"owner": "LnL7",
"repo": "nix-darwin",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1678703398, "lastModified": 1678703398,
@@ -253,11 +172,11 @@
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1678375444, "lastModified": 1690881714,
"narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=", "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e", "rev": "9e1960bc196baf6881340d53dccb203a951745a2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -268,29 +187,31 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-stable": { "nixpkgs-lib_2": {
"locked": { "locked": {
"lastModified": 1673800717, "dir": "lib",
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", "lastModified": 1688049487,
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
"type": "github" "type": "github"
}, },
"original": { "original": {
"dir": "lib",
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-22.11", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1678293141, "lastModified": 1688322751,
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=", "narHash": "sha256-eW62dC5f33oKZL7VWlomttbUnOTHrAbte9yNUNW8rbk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d", "rev": "0fbe93c5a7cac99f90b60bdf5f149383daaa615f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -302,11 +223,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1678891326, "lastModified": 1689393711,
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=", "narHash": "sha256-l3gyTPy/qWLDk1CY1EgYwlsxcGxN2aVd7MlCzQa69rk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052", "rev": "27fcd46fa18df36d270174246e7bd8f1787129ff",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -317,72 +238,29 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1683408522, "lastModified": 1692734709,
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=", "narHash": "sha256-SCFnyHCyYjwEmgUsHDDuU0TsbVMKeU1vwkR+r7uS2Rg=",
"owner": "NixOS", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7", "rev": "b85ed9dcbf187b909ef7964774f8847d554fab3b",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "nixos",
"ref": "nixos-unstable", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"pre-commit-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1678376203,
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"fenix": "fenix", "flake-parts": "flake-parts",
"flatland": "flatland", "flatland": "flatland",
"hercules-ci-effects": "hercules-ci-effects", "hercules-ci-effects": "hercules-ci-effects",
"nixpkgs": "nixpkgs_4" "nixpkgs": "nixpkgs_4"
} }
}, },
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1683653808,
"narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-analyzer-src_2": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1678695923, "lastModified": 1678695923,

183
flake.nix
View File

@@ -3,130 +3,77 @@
extra-substituters = [ "https://stardustxr.cachix.org" ]; extra-substituters = [ "https://stardustxr.cachix.org" ];
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ]; extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ];
}; };
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
# 22.11 does not include PR #218472, hence we use the unstable version # Since we do not have a monorepo, we have to fetch Flatland in order to use
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; # it to create VM Tests
flatland.url = "github:StardustXR/flatland";
# Since we do not have a monorepo, we have to fetch Flatland in order to use };
# it to create VM Tests outputs = inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }:
inputs.flatland.url = "github:StardustXR/flatland";
inputs.fenix.url = github:nix-community/fenix;
inputs.fenix.inputs.nixpkgs.follows = "nixpkgs";
inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }:
let let
name = "server"; name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
pkgs = system: import nixpkgs { src = builtins.path {
inherit system; name = "${name}-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
}; };
shell = pkgs: pkgs.mkShell {
inputsFrom = [ self.packages.${pkgs.system}.default ];
# ---- START package specific dev settings ----
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific dev settings ----
};
package = pkgs:
let
toolchain = fenix.packages.${pkgs.system}.minimal.toolchain;
in
(pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
}).buildRustPackage rec {
pname = "stardust-xr-${name}";
src = builtins.path {
name = "stardust-xr-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
};
# ---- START package specific settings ----
version = "0.10.2";
cargoLock = {
lockFile = ./Cargo.lock;
allowBuiltinFetchGit = true;
};
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
mkdir -p $sk/build/cpm
cp ${pkgs.fetchurl {
url = "https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.32.2/CPM.cmake";
hash = "sha256-yDHlpqmpAE8CWiwJRoWyaqbuBAg0090G8WJIC2KLHp8=";
}} $sk/build/cpm/CPM_0.32.2.cmake
'';
CPM_SOURCE_CACHE = "./build";
nativeBuildInputs = with pkgs; [
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = with pkgs; [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific settings ----
};
in in
{ flake-parts.lib.mkFlake { inherit inputs; } {
overlays.default = final: prev: { imports = [
stardust-xr = (prev.stardust-xr or {}) // { flake-parts.flakeModules.easyOverlay
${name} = package final; ];
systems = [ "aarch64-linux" "x86_64-linux" "riscv64-linux" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
_module.args.pkgs = import inputs.nixpkgs { inherit system; overlays = [ inputs.self.overlays.default ]; };
overlayAttrs = config.packages;
packages = {
default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix { inherit name src; };
};
checks.gnome-graphical-test = pkgs.nixosTest (import ./nix/gnome-graphical-test.nix { inherit pkgs self; });
devShells.default = pkgs.mkShell {
inputsFrom = [ self'.packages.default ];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
}; };
}; };
flake = {
packages."x86_64-linux".default = package (pkgs "x86_64-linux"); herculesCI.ciSystems = [ "x86_64-linux" ];
packages."aarch64-linux".default = package (pkgs "aarch64-linux"); effects = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test; hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test; in { ref, rev, ... }: {
gnome-graphical-test = hci-effects.mkEffect {
checks."x86_64-linux".gnome-graphical-test = (pkgs "x86_64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "x86_64-linux"); inherit self; }); secretsMap."stardustxrDiscord" = "stardustxrDiscord";
checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; }); secretsMap."stardustxrIpfs" = "stardustxrIpfs";
effectScript = ''
devShells."x86_64-linux".default = shell (pkgs "x86_64-linux"); readSecretString stardustxrDiscord .webhook > .webhook
devShells."aarch64-linux".default = shell (pkgs "aarch64-linux"); readSecretString stardustxrIpfs .basicauth > .basicauth
set -x
herculesCI.ciSystems = [ "x86_64-linux" ]; export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
effects = let set +x
pkgs = nixpkgs.legacyPackages.x86_64-linux; export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
hci-effects = hercules-ci-effects.lib.withPkgs pkgs; ${pkgs.discord-sh}/bin/discord.sh \
in { branch, rev, ... }: { --description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
gnome-graphical-test = hci-effects.mkEffect { --field "Ref;${ref}" \
secretsMap."stardustxrDiscord" = "stardustxrDiscord"; --field "Commit ID;${rev}" \
secretsMap."stardustxrIpfs" = "stardustxrIpfs"; --field "Flatland Revision;${flatland.rev}" \
effectScript = '' --field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
readSecretString stardustxrDiscord .webhook > .webhook --image "$ADDRESS"
readSecretString stardustxrIpfs .basicauth > .basicauth '';
set -x };
export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
set +x
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
${pkgs.discord-sh}/bin/discord.sh \
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
--field "Branch;${branch}" \
--field "Commit ID;${rev}" \
--field "Flatland Revision;${flatland.rev}" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
--image "$ADDRESS"
'';
}; };
}; };
}; };

View File

@@ -0,0 +1,40 @@
{ rustPlatform
, src
, name
, openxr-loader
, libGL
, mesa
, xorg
, fontconfig
, libxkbcommon
, libclang
, cmake
, cpm-cmake
, pkg-config
, llvmPackages
}:
rustPlatform.buildRustPackage rec {
inherit src name;
cargoLock = {
lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true;
};
CPM_SOURCE_CACHE = "./build";
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
mkdir -p $sk/build/cpm
# This is not ideal, the original approach was to fetch the exact cmake
# file version that was wanted from GitHub directly, but at least this way it comes from Nixpkgs.. so meh
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.32.2.cmake
'';
nativeBuildInputs = [
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
];
LIBCLANG_PATH = "${libclang.lib}/lib";
}

View File

@@ -1,20 +1,21 @@
use super::scenegraph::Scenegraph; use super::{
client_state::{ClientState, CLIENT_STATES},
destroy_queue,
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 +35,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,13 +47,13 @@ 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 {
pid: Option<i32>, pub pid: Option<i32>,
// env: Option<FxHashMap<String, String>>, // env: Option<FxHashMap<String, String>>,
exe: Option<PathBuf>, exe: Option<PathBuf>,
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>, dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
@@ -63,7 +64,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 +81,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 +99,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 +111,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 +174,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(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
@@ -179,7 +210,9 @@ impl Client {
if let Some(flush_join_handle) = self.flush_join_handle.get() { if let Some(flush_join_handle) = self.flush_join_handle.get() {
flush_join_handle.abort(); flush_join_handle.abort();
} }
CLIENTS.remove(self); if let Some(client) = CLIENTS.remove(self) {
destroy_queue::add(client);
}
} }
} }
impl Drop for Client { impl Drop for Client {

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

@@ -0,0 +1,108 @@
use super::client::{get_env, Client};
use crate::nodes::{spatial::Spatial, Node};
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::{io::Write, 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 LaunchInfo {
pub cmdline: Vec<String>,
pub cwd: PathBuf,
pub env: FxHashMap<String, String>,
}
impl LaunchInfo {
fn from_client(client: &Client) -> Option<Self> {
Some(LaunchInfo {
cmdline: client.get_cmdline()?,
cwd: client.get_cwd()?,
env: get_env(client.pid?).ok()?,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientState {
pub launch_info: Option<LaunchInfo>,
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 {
launch_info: LaunchInfo::from_client(client),
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.get_aspect::<Spatial>().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 to_file(self) {
let project_dirs = directories::ProjectDirs::from("", "", "stardust").unwrap();
let state_dir = project_dirs.state_dir().unwrap();
std::fs::create_dir_all(state_dir).unwrap();
let mut file = std::fs::File::create(state_dir.join(nanoid::nanoid!())).unwrap();
file.write_all(&stardust_xr::schemas::flex::flexbuffers::to_vec(self).unwrap())
.unwrap();
}
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_parent_name(client, "/spatial/anchor", k, true)
.add_to_scenegraph()
.unwrap();
Spatial::add_to(&node, None, *v, false);
k.clone()
})
})
.collect(),
}
}
}
impl Default for ClientState {
fn default() -> Self {
Self {
launch_info: 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,12 +1,22 @@
use once_cell::sync::Lazy;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::any::Any; use std::any::Any;
use tokio::sync::mpsc::{self, unbounded_channel};
static MAIN_DESTROY_QUEUE: Mutex<Vec<Box<dyn Any + Send + Sync>>> = Mutex::new(Vec::new()); static MAIN_DESTROY_QUEUE: Lazy<(
mpsc::UnboundedSender<Box<dyn Any + Send + Sync>>,
Mutex<mpsc::UnboundedReceiver<Box<dyn Any + Send + Sync>>>,
)> = Lazy::new(|| {
let (tx, rx) = unbounded_channel();
(tx, Mutex::new(rx))
});
pub fn add<T: Any + Sync + Send>(thing: T) { pub fn add<T: Any + Sync + Send>(thing: T) {
MAIN_DESTROY_QUEUE.lock().push(Box::new(thing)); MAIN_DESTROY_QUEUE.0.send(Box::new(thing)).unwrap();
} }
pub fn clear() { pub fn clear() {
MAIN_DESTROY_QUEUE.lock().clear(); while let Ok(thing) = MAIN_DESTROY_QUEUE.1.lock().try_recv() {
drop(thing)
}
} }

11
src/core/idl_utils.rs Normal file
View File

@@ -0,0 +1,11 @@
#[macro_export]
macro_rules! create_interface {
($iface:ident, $aspect:ident, $path:expr) => {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create_path(client, $path, false);
<$iface as $aspect>::add_node_members(&node);
node.add_to_scenegraph()?;
Ok(())
}
};
}

View File

@@ -1,7 +1,9 @@
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;
pub mod idl_utils;
pub mod node_collections; pub mod node_collections;
pub mod registry; pub mod registry;
pub mod resource; pub mod resource;

View File

@@ -7,33 +7,32 @@ use std::{
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
#[derive(Default)] // #[derive(Default)]
pub struct LifeLinkedNodeList { // pub struct LifeLinkedNodeList {
nodes: Mutex<Vec<Weak<Node>>>, // nodes: Mutex<Vec<Weak<Node>>>,
} // }
impl LifeLinkedNodeList { // impl LifeLinkedNodeList {
pub fn add(&self, node: Weak<Node>) { // pub fn add(&self, node: Weak<Node>) {
self.nodes.lock().push(node); // self.nodes.lock().push(node);
} // }
// pub fn clear(&self) {
// self.nodes
// .lock()
// .iter()
// .filter_map(|node| node.upgrade())
// .for_each(|node| {
// node.destroy();
// });
// self.nodes.lock().clear();
// }
// }
// impl Drop for LifeLinkedNodeList {
// fn drop(&mut self) {
// self.clear();
// }
// }
pub fn clear(&self) { #[derive(Default, Debug)]
self.nodes
.lock()
.iter()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
self.nodes.lock().clear();
}
}
impl Drop for LifeLinkedNodeList {
fn drop(&mut self) {
self.clear();
}
}
#[derive(Default)]
pub struct LifeLinkedNodeMap<K: Hash + Eq> { pub struct LifeLinkedNodeMap<K: Hash + Eq> {
nodes: Mutex<FxHashMap<K, Weak<Node>>>, nodes: Mutex<FxHashMap<K, Weak<Node>>>,
} }

View File

@@ -5,6 +5,7 @@ use rustc_hash::FxHashMap;
use std::ptr; use std::ptr;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
#[derive(Debug)]
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>); pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
impl<T: Send + Sync + ?Sized> Registry<T> { impl<T: Send + Sync + ?Sized> Registry<T> {
@@ -54,12 +55,27 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
pub fn clear(&self) { pub fn clear(&self) {
self.lock().clear(); self.lock().clear();
} }
pub fn is_empty(&self) -> bool {
let registry = self.0.lock();
let Some(registry) = &*registry else {
return true;
};
if registry.is_empty() {
return true;
}
registry.values().all(|v| v.strong_count() == 0)
}
} }
impl<T: Send + Sync + ?Sized> Clone for Registry<T> { impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone())) Self(Mutex::new(self.0.lock().clone()))
} }
} }
impl<T: Send + Sync + ?Sized> Default for Registry<T> {
fn default() -> Self {
Self::new()
}
}
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>); pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
@@ -90,9 +106,12 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
self.lock() self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize)) .contains_key(&(ptr::addr_of!(*t) as *const () as usize))
} }
pub fn remove(&self, t: &T) { pub fn remove(&self, t: &T) -> Option<Arc<T>>
where
T: Sized,
{
self.lock() self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize)); .remove(&(ptr::addr_of!(*t) as *const () as usize))
} }
pub fn clear(&self) { pub fn clear(&self) {
self.lock().clear(); self.lock().clear();

View File

@@ -1,83 +1,46 @@
use color_eyre::eyre::eyre; use stardust_xr::values::ResourceID;
use serde::{de::Visitor, Deserialize};
use std::{ffi::OsStr, path::PathBuf}; use std::{ffi::OsStr, path::PathBuf};
#[derive(Debug)] use super::client::Client;
pub enum ResourceID {
File(PathBuf),
Namespaced { namespace: String, path: PathBuf },
}
impl ResourceID {
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
match self {
ResourceID::File(file) => (file.is_absolute()
&& file.exists() && Self::has_extension(file, extensions))
.then_some(file.clone()),
ResourceID::Namespaced { namespace, path } => {
let file_name = path.file_name()?;
prefixes
.iter()
.filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
let parent = prefixed_path.parent()?;
std::fs::read_dir(parent).ok()
})
.flatten()
.filter_map(|item| item.ok())
.map(|dir_entry| dir_entry.path())
.filter(|path| path.file_stem() == Some(file_name))
.find(|path| Self::has_extension(path, extensions))
}
}
}
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool { lazy_static::lazy_static! {
if let Some(path_extension) = path.extension() { static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default();
extensions.contains(&path_extension) }
} else {
false fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension)
} else {
false
}
}
pub fn get_resource_file(
resource: &ResourceID,
client: &Client,
extensions: &[&OsStr],
) -> Option<PathBuf> {
match resource {
ResourceID::Direct(file) => {
(file.is_absolute() && file.exists() && has_extension(file, extensions))
.then_some(file.clone())
}
ResourceID::Namespaced { namespace, path } => {
let file_name = path.file_name()?;
let base_prefixes = client.base_resource_prefixes.lock().clone();
THEMES
.iter()
.chain(base_prefixes.iter())
.filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
let parent = prefixed_path.parent()?;
std::fs::read_dir(parent).ok()
})
.flatten()
.filter_map(|item| item.ok())
.map(|dir_entry| dir_entry.path())
.filter(|path| path.file_stem() == Some(file_name))
.find(|path| has_extension(path, extensions))
} }
} }
} }
impl<'de> Deserialize<'de> for ResourceID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(ResourceVisitor)
}
}
struct ResourceVisitor;
impl<'de> Visitor<'de> for ResourceVisitor {
type Value = ResourceID;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("A string containing an absolute path to file or \"[namespace]:[path]\" for a namespaced resource")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(if v.starts_with('/') {
let path = PathBuf::from(v);
path.metadata().map_err(serde::de::Error::custom)?;
ResourceID::File(path)
} else if let Some((namespace, path)) = v.split_once(':') {
ResourceID::Namespaced {
namespace: namespace.to_string(),
path: PathBuf::from(path),
}
} else {
return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
})
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
}

View File

@@ -1,20 +1,25 @@
use crate::core::client::Client; use crate::nodes::alias::Alias;
use crate::nodes::Node; use crate::nodes::Node;
use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::Ordering;
use rustc_hash::FxHashMap;
use serde::Serialize;
use stardust_xr::scenegraph; use stardust_xr::scenegraph;
use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::serialize;
use std::future::Future;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tracing::{debug, debug_span, instrument}; use tokio::sync::oneshot;
use tracing::{debug, debug_span};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use rustc_hash::FxHasher;
#[derive(Default)] #[derive(Default)]
pub struct Scenegraph { pub struct Scenegraph {
pub(super) client: OnceCell<Weak<Client>>, pub(super) client: OnceCell<Weak<Client>>,
nodes: DashMap<String, Arc<Node>, BuildHasherDefault<FxHasher>>, nodes: Mutex<FxHashMap<String, Arc<Node>>>,
} }
impl Scenegraph { impl Scenegraph {
@@ -30,32 +35,84 @@ impl Scenegraph {
pub fn add_node_raw(&self, node: Arc<Node>) { pub fn add_node_raw(&self, node: Arc<Node>) {
debug!(node = ?&*node, "Add node"); debug!(node = ?&*node, "Add node");
let path = node.get_path().to_string(); let path = node.get_path().to_string();
self.nodes.insert(path, node); self.nodes.lock().insert(path, node);
} }
#[instrument(level = "debug", skip(self))]
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> { pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
let mut node = self.nodes.get(path)?.clone(); let mut node = self.nodes.lock().get(path)?.clone();
while let Some(alias) = node.alias.get() { while let Ok(alias) = node.get_aspect::<Alias>() {
node = alias.original.upgrade()?; if alias.enabled.load(Ordering::Acquire) {
node = alias.original.upgrade()?;
} else {
return None;
}
} }
Some(node) Some(node)
} }
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> { pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
debug!(path, "Remove node"); debug!(path, "Remove node");
let (_, node) = self.nodes.remove(path)?; self.nodes.lock().remove(path)
Some(node)
} }
} }
pub struct MethodResponseSender(oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>);
impl MethodResponseSender {
pub fn send(self, t: Result<Message, ScenegraphError>) {
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
}
// pub fn send_method_return<T: Serialize>(
// self,
// result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
// ) {
// let _ = self.0.send(map_method_return(result));
// }
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MethodError {
error: e.to_string(),
}))
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
}
}
fn map_method_return<T: Serialize>(
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
error: e.to_string(),
})?;
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError {
error: format!("Internal: Serialization failed: {e}"),
})?;
Ok((serialized_value, fds))
}
impl scenegraph::Scenegraph for Scenegraph { impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(&self, path: &str, method: &str, data: &[u8]) -> Result<(), ScenegraphError> { fn send_signal(
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)}; &self,
path: &str,
method: &str,
data: &[u8],
fds: Vec<OwnedFd>,
) -> Result<(), ScenegraphError> {
let Some(client) = self.get_client() else {
return Err(ScenegraphError::SignalNotFound);
};
debug_span!("Handle signal", path, method).in_scope(|| { debug_span!("Handle signal", path, method).in_scope(|| {
self.get_node(path) self.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)? .ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal(client, method, data) .send_local_signal(
client,
method,
Message {
data: data.to_vec(),
fds,
},
)
}) })
} }
fn execute_method( fn execute_method(
@@ -63,12 +120,26 @@ impl scenegraph::Scenegraph for Scenegraph {
path: &str, path: &str,
method: &str, method: &str,
data: &[u8], data: &[u8],
) -> Result<Vec<u8>, ScenegraphError> { fds: Vec<OwnedFd>,
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)}; response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
debug_span!("Handle method", path, method).in_scope(|| { ) {
self.get_node(path) let Some(client) = self.get_client() else {
.ok_or(ScenegraphError::NodeNotFound)? let _ = response.send(Err(ScenegraphError::MethodNotFound));
.execute_local_method(client, method, data) return;
}) };
debug!(path, method, "Handle method");
let Some(node) = self.get_node(path) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
node.execute_local_method(
client,
method,
Message {
data: data.to_vec(),
fds,
},
MethodResponseSender(response),
);
} }
} }

View File

@@ -1,10 +1,8 @@
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::future::Future; use std::future::Future;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tracing::instrument;
#[allow(unused_variables)] #[allow(unused_variables)]
#[instrument(level = "debug", skip_all)]
pub fn new< pub fn new<
F: FnOnce() -> S, F: FnOnce() -> S,
S: AsRef<str>, S: AsRef<str>,

View File

@@ -4,20 +4,24 @@ 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::{audio, drawable, hmd, input}; use crate::nodes::{audio, drawable, hmd, input};
use crate::objects::input::eye_pointer::EyePointer; use crate::objects::input::eye_pointer::EyePointer;
use crate::objects::input::mouse_pointer::MousePointer; use crate::objects::input::mouse_pointer::MousePointer;
use crate::objects::input::sk_controller::SkController; use crate::objects::input::sk_controller::SkController;
use crate::objects::input::sk_hand::SkHand; use crate::objects::input::sk_hand::SkHand;
use crate::objects::play_space::PlaySpace; use crate::objects::play_space::PlaySpace;
use crate::wayland::X_DISPLAY;
use self::core::eventloop::EventLoop; use self::core::eventloop::EventLoop;
use clap::Parser; use clap::Parser;
use directories::ProjectDirs; use directories::ProjectDirs;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use stardust_xr::server; use stardust_xr::server;
use std::mem::ManuallyDrop; use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::Arc; use std::sync::Arc;
@@ -27,6 +31,8 @@ use stereokit::{
TextureFormat, TextureType, TextureFormat, TextureType,
}; };
use stereokit::{DisplayBlend, Sk}; use stereokit::{DisplayBlend, Sk};
use tokio::sync::Notify;
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};
@@ -54,6 +60,7 @@ struct CliArgs {
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new(); static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new(); static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
static STOP_NOTIFIER: Notify = Notify::const_new();
struct EventLoopInfo { struct EventLoopInfo {
tokio_handle: Handle, tokio_handle: Handle,
@@ -61,13 +68,17 @@ struct EventLoopInfo {
} }
fn main() { fn main() {
ctrlc::set_handler(|| {
if atty::isnt(atty::Stream::Stdout) {
STOP_NOTIFIER.notify_waiters()
}
})
.unwrap();
let registry = tracing_subscriber::registry(); let registry = tracing_subscriber::registry();
#[cfg(feature = "profile_app")] #[cfg(feature = "profile_app")]
let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new() let registry = registry.with(tracing_tracy::TracyLayer::new().with_filter(LevelFilter::DEBUG));
.include_args(true)
.build();
#[cfg(feature = "profile_app")]
let registry = registry.with(chrome_layer);
#[cfg(feature = "profile_tokio")] #[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build(); let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
@@ -108,6 +119,7 @@ fn main() {
overlay_app: cli_args.overlay_priority.is_some(), overlay_app: cli_args.overlay_priority.is_some(),
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX), overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
disable_desktop_input_window: true, disable_desktop_input_window: true,
render_scaling: 2.0,
..Default::default() ..Default::default()
} }
.init() .init()
@@ -115,6 +127,8 @@ fn main() {
let _ = SK_MULTITHREAD.set(sk.multithreaded()); let _ = SK_MULTITHREAD.set(sk.multithreaded());
info!("Init StereoKit"); info!("Init StereoKit");
sk.render_set_multisample(0);
sk.material_set_shader( sk.material_set_shader(
sk.material_find("default/material_pbr").unwrap(), sk.material_find("default/material_pbr").unwrap(),
sk.shader_find("default/shader_pbr_clip").unwrap(), sk.shader_find("default/shader_pbr_clip").unwrap(),
@@ -145,7 +159,7 @@ fn main() {
} }
} }
let mouse_pointer = cli_args let mut mouse_pointer = cli_args
.flatscreen .flatscreen
.then(MousePointer::new) .then(MousePointer::new)
.transpose() .transpose()
@@ -159,15 +173,16 @@ fn main() {
.flatten(); .flatten();
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller) let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
.then(|| { .then(|| {
let left = SkController::new(Handed::Left).ok(); let left = SkController::new(&sk, Handed::Left).ok();
let right = SkController::new(Handed::Right).ok(); let right = SkController::new(&sk, Handed::Right).ok();
left.zip(right) left.zip(right)
}) })
.flatten(); .flatten();
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze()) let eye_pointer = (sk.active_display_mode() == DisplayMode::MixedReality
.then(EyePointer::new) && sk.device_has_eye_gaze())
.transpose() .then(EyePointer::new)
.unwrap(); .transpose()
.unwrap();
if hands.is_none() { if hands.is_none() {
sk.input_hand_visible(Handed::Left, false); sk.input_hand_visible(Handed::Left, false);
@@ -179,46 +194,68 @@ fn main() {
.then(|| PlaySpace::new().ok()) .then(|| PlaySpace::new().ok())
.flatten(); .flatten();
let (event_stop_tx, event_stop_rx) = oneshot::channel::<()>();
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>(); let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
let event_thread = std::thread::Builder::new() let event_thread = std::thread::Builder::new()
.name("event_loop".to_owned()) .name("event_loop".to_owned())
.spawn(move || event_loop(info_sender, event_stop_rx)) .spawn(move || event_loop(info_sender))
.unwrap(); .unwrap();
let event_loop_info = info_receiver.blocking_recv().unwrap(); let event_loop_info = info_receiver.blocking_recv().unwrap();
let _tokio_handle = event_loop_info.tokio_handle.enter(); let _tokio_handle = event_loop_info.tokio_handle.enter();
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
let mut wayland = wayland::Wayland::new().unwrap(); let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
info!("Stardust ready!"); info!("Stardust ready!");
if let Some(project_dirs) = project_dirs.as_ref() { let mut startup_child = (|| {
let project_dirs = project_dirs.as_ref()?;
let startup_script_path = cli_args let startup_script_path = cli_args
.startup_script .startup_script
.clone() .clone()
.and_then(|p| p.canonicalize().ok()) .and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| project_dirs.config_dir().join("startup")); .unwrap_or_else(|| project_dirs.config_dir().join("startup"));
let _startup = Command::new(startup_script_path) let mut startup_command = Command::new("bash");
.stdin(Stdio::null()) startup_command.arg(startup_script_path);
.env( startup_command.arg("&");
"FLAT_WAYLAND_DISPLAY",
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(), startup_command.stdin(Stdio::null());
) startup_command.stdout(Stdio::null());
.env("WAYLAND_DISPLAY", &wayland.socket_name) startup_command.stderr(Stdio::null());
.env( startup_command.env(
"STARDUST_INSTANCE", "FLAT_WAYLAND_DISPLAY",
event_loop_info std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
.socket_path );
.file_name() startup_command.env(
.expect("Stardust socket path not found"), "STARDUST_INSTANCE",
) event_loop_info
.env("GDK_BACKEND", "wayland") .socket_path
.env("QT_QPA_PLATFORM", "wayland") .file_name()
.env("MOZ_ENABLE_WAYLAND", "1") .expect("Stardust socket path not found"),
.env("CLUTTER_BACKEND", "wayland") );
.env("SDL_VIDEODRIVER", "wayland") #[cfg(feature = "wayland")]
.spawn(); {
} if let Some(wayland_socket) = wayland.socket_name.as_ref() {
startup_command.env("WAYLAND_DISPLAY", &wayland_socket);
}
startup_command.env(
"DISPLAY",
format!(":{}", X_DISPLAY.get().cloned().unwrap_or_default()),
);
startup_command.env("GDK_BACKEND", "wayland");
startup_command.env("QT_QPA_PLATFORM", "wayland");
startup_command.env("MOZ_ENABLE_WAYLAND", "1");
startup_command.env("CLUTTER_BACKEND", "wayland");
startup_command.env("SDL_VIDEODRIVER", "wayland");
}
unsafe {
startup_command.pre_exec(|| {
nix::unistd::setsid()
.map(|_| ())
.map_err(|_| std::io::ErrorKind::Other.into())
})
};
let child = startup_command.spawn().ok()?;
Some(child)
})();
let mut last_frame_delta = Duration::ZERO; let mut last_frame_delta = Duration::ZERO;
let mut sleep_duration = Duration::ZERO; let mut sleep_duration = Duration::ZERO;
@@ -229,16 +266,17 @@ fn main() {
let _span = _span.enter(); let _span = _span.enter();
hmd::frame(sk); hmd::frame(sk);
camera::update(sk);
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
wayland.frame_event(sk); wayland.frame_event(sk);
destroy_queue::clear(); destroy_queue::clear();
if let Some(mouse_pointer) = &mouse_pointer { if let Some(mouse_pointer) = &mut mouse_pointer {
mouse_pointer.update(sk); mouse_pointer.update(sk);
} }
if let Some((left_hand, right_hand)) = &mut hands { if let Some((left_hand, right_hand)) = &mut hands {
left_hand.update(sk); left_hand.update(!cli_args.disable_controller, sk);
right_hand.update(sk); right_hand.update(!cli_args.disable_controller, sk);
} }
if let Some((left_controller, right_controller)) = &mut controllers { if let Some((left_controller, right_controller)) = &mut controllers {
left_controller.update(sk); left_controller.update(sk);
@@ -266,20 +304,25 @@ fn main() {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
wayland.make_context_current(); wayland.make_context_current();
}, },
|_| { |_sk| {
info!("Cleanly shut down StereoKit"); info!("Cleanly shut down StereoKit");
if let Some(mut startup_child) = startup_child.take() {
let _ = startup_child.kill();
}
}, },
) )
}); });
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
let _wayland = ManuallyDrop::new(wayland); drop(wayland);
let _ = event_stop_tx.send(()); STOP_NOTIFIER.notify_waiters();
event_thread event_thread
.join() .join()
.expect("Failed to cleanly shut down event loop") .expect("Failed to cleanly shut down event loop")
.unwrap(); .unwrap();
info!("Cleanly shut down Stardust"); info!("Cleanly shut down Stardust");
} }
@@ -308,10 +351,7 @@ fn adaptive_sleep(
#[tokio::main] #[tokio::main]
// #[tokio::main(flavor = "current_thread")] // #[tokio::main(flavor = "current_thread")]
async fn event_loop( async fn event_loop(info_sender: oneshot::Sender<EventLoopInfo>) -> color_eyre::eyre::Result<()> {
info_sender: oneshot::Sender<EventLoopInfo>,
stop_rx: oneshot::Receiver<()>,
) -> color_eyre::eyre::Result<()> {
let socket_path = let socket_path =
server::get_free_socket_path().expect("Unable to find a free stardust socket path"); server::get_free_socket_path().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell"); STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
@@ -326,15 +366,9 @@ async fn event_loop(
socket_path, socket_path,
}); });
if atty::is(atty::Stream::Stdin) { STOP_NOTIFIER.notified().await;
stop_rx.await?; println!("Stopping...");
} else { save_clients().await;
tokio::select! {
biased;
_ = tokio::signal::ctrl_c() => (),
_ = stop_rx => (),
};
}
info!("Cleanly shut down event loop"); info!("Cleanly shut down event loop");
@@ -344,3 +378,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

@@ -1,6 +1,7 @@
use super::Node; use super::{Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use color_eyre::eyre::{ensure, Result}; use color_eyre::eyre::{ensure, Result};
use portable_atomic::AtomicBool;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@@ -12,6 +13,7 @@ pub struct AliasInfo {
#[allow(dead_code)] #[allow(dead_code)]
pub struct Alias { pub struct Alias {
pub enabled: Arc<AtomicBool>,
pub(super) node: Weak<Node>, pub(super) node: Weak<Node>,
pub original: Weak<Node>, pub original: Weak<Node>,
@@ -33,14 +35,18 @@ impl Alias {
"Node already exists" "Node already exists"
); );
let node = Node::create(client, parent, name, true).add_to_scenegraph()?; let node = Node::create_parent_name(client, parent, name, true).add_to_scenegraph()?;
let alias = Alias { let alias = Alias {
enabled: Arc::new(AtomicBool::new(true)),
node: Arc::downgrade(&node), node: Arc::downgrade(&node),
original: Arc::downgrade(original), original: Arc::downgrade(original),
info, info,
}; };
let alias = original.aliases.add(alias); let alias = original.aliases.add(alias);
let _ = node.alias.set(alias); node.add_aspect_raw(alias);
Ok(node) Ok(node)
} }
} }
impl Aspect for Alias {
const NAME: &'static str = "Alias";
}

View File

@@ -1,23 +1,25 @@
use super::Node; use super::{Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::destroy_queue; use crate::core::destroy_queue;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::resource::ResourceID; use crate::core::resource::get_resource_file;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::create_interface;
use color_eyre::eyre::{ensure, eyre, Result}; use crate::nodes::spatial::{Spatial, Transform};
use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Vec4Swizzles}; use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use serde::Deserialize; use stardust_xr::values::ResourceID;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use std::sync::Arc;
use std::{ffi::OsStr, path::PathBuf};
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw}; use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
static SOUND_REGISTRY: Registry<Sound> = Registry::new(); static SOUND_REGISTRY: Registry<Sound> = Registry::new();
stardust_xr_server_codegen::codegen_audio_protocol!();
pub struct Sound { pub struct Sound {
space: Arc<Spatial>, space: Arc<Spatial>,
@@ -28,26 +30,16 @@ pub struct Sound {
stop: Mutex<Option<()>>, stop: Mutex<Option<()>>,
play: Mutex<Option<()>>, play: Mutex<Option<()>>,
} }
impl Sound { impl Sound {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> { pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
ensure!( let pending_audio_path = get_resource_file(
node.spatial.get().is_some(), &resource_id,
"Internal: Node does not have a spatial attached!" &*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
); &[OsStr::new("wav"), OsStr::new("mp3")],
let pending_audio_path = resource_id )
.get_file( .ok_or_else(|| eyre!("Resource not found"))?;
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("wav"), OsStr::new("mp3")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let sound = Sound { let sound = Sound {
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
volume: 1.0, volume: 1.0,
pending_audio_path, pending_audio_path,
sk_sound: OnceCell::new(), sk_sound: OnceCell::new(),
@@ -56,9 +48,8 @@ impl Sound {
play: Mutex::new(None), play: Mutex::new(None),
}; };
let sound_arc = SOUND_REGISTRY.add(sound); let sound_arc = SOUND_REGISTRY.add(sound);
node.add_local_signal("play", Sound::play_flex); node.add_aspect_raw(sound_arc.clone());
node.add_local_signal("stop", Sound::stop_flex); <Sound as SoundAspect>::add_node_members(node);
let _ = node.sound.set(sound_arc.clone());
Ok(sound_arc) Ok(sound_arc)
} }
@@ -71,7 +62,7 @@ impl Sound {
sk.sound_inst_stop(instance); sk.sound_inst_stop(instance);
} }
} }
if self.play.lock().is_some() && self.instance.lock().is_none() { if self.instance.lock().is_none() && self.play.lock().take().is_some() {
self.instance.lock().replace(sk.sound_play( self.instance.lock().replace(sk.sound_play(
sound.as_ref(), sound.as_ref(),
vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0),
@@ -82,19 +73,30 @@ impl Sound {
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz()); sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
} }
} }
}
fn play_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { impl Aspect for Sound {
let sound = node.sound.get().unwrap(); const NAME: &'static str = "Sound";
}
impl SoundAspect for Sound {
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let sound = node.get_aspect::<Sound>().unwrap();
sound.play.lock().replace(()); sound.play.lock().replace(());
Ok(()) Ok(())
} }
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { let sound = node.get_aspect::<Sound>().unwrap();
let sound = node.sound.get().unwrap();
sound.stop.lock().replace(()); sound.stop.lock().replace(());
Ok(()) Ok(())
} }
} }
impl Drop for Sound {
fn drop(&mut self) {
if let Some(sk_sound) = self.sk_sound.take() {
destroy_queue::add(sk_sound);
}
SOUND_REGISTRY.remove(self);
}
}
pub fn update(sk: &impl StereoKitDraw) { pub fn update(sk: &impl StereoKitDraw) {
for sound in SOUND_REGISTRY.get_valid_contents() { for sound in SOUND_REGISTRY.get_valid_contents() {
@@ -102,35 +104,25 @@ pub fn update(sk: &impl StereoKitDraw) {
} }
} }
pub fn create_interface(client: &Arc<Client>) -> Result<()> { create_interface!(AudioInterface, AudioInterfaceAspect, "/audio");
let node = Node::create(client, "", "audio", false); struct AudioInterface;
node.add_local_signal("create_sound", create_flex); impl AudioInterfaceAspect for AudioInterface {
node.add_to_scenegraph().map(|_| ()) #[doc = "Create a sound node. WAV and MP3 are supported."]
} fn create_sound(
_node: Arc<Node>,
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { calling_client: Arc<Client>,
#[derive(Deserialize)] name: String,
struct CreateSoundInfo<'a> { parent: Arc<Node>,
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
resource: ResourceID, resource: ResourceID,
} ) -> Result<()> {
let info: CreateSoundInfo = deserialize(data)?; let node =
let node = Node::create(&calling_client, "/audio/sound", info.name, true); Node::create_parent_name(&calling_client, Self::CREATE_SOUND_PARENT_PATH, &name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, true); let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?; Spatial::add_to(&node, Some(parent.clone()), transform, false);
Sound::add_to(&node, info.resource)?; Sound::add_to(&node, resource)?;
Ok(()) Ok(())
}
impl Drop for Sound {
fn drop(&mut self) {
if let Some(instance) = self.instance.lock().take() {
destroy_queue::add(instance);
}
SOUND_REGISTRY.remove(self);
} }
} }

View File

@@ -1,30 +1,50 @@
use super::alias::AliasInfo; use super::alias::AliasInfo;
use super::fields::Field; use super::fields::Field;
use super::spatial::{parse_transform, Spatial}; use super::spatial::{parse_transform, Spatial};
use super::{Alias, Node}; use super::{Alias, Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO}; use crate::create_interface;
use crate::nodes::spatial::find_spatial_parent; use crate::nodes::fields::FIELD_ALIAS_INFO;
use color_eyre::eyre::{ensure, eyre, Result}; use crate::nodes::spatial::Transform;
use glam::vec3a; use color_eyre::eyre::{bail, ensure, eyre, Result};
use mint::{Quaternion, Vector3}; use lazy_static::lazy_static;
use nanoid::nanoid; use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use parking_lot::Mutex;
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize}; use rustc_hash::FxHashMap;
use stardust_xr::values::Transform; use stardust_xr::schemas::flex::flexbuffers;
use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
lazy_static! {
pub static ref KEYMAPS: Mutex<FxHashMap<String, String>> = Mutex::new(FxHashMap::default());
}
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new(); static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new(); pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool { pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(datamap.raw().as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
(|| -> Result<_> { (|| -> Result<_> {
for key in mask_map_lesser.get_mask()?.iter_keys() { for key in get_mask(mask_map_lesser)?.iter_keys() {
let lesser_key = mask_map_lesser.get_mask()?.index(key)?; let lesser_key = get_mask(mask_map_lesser)?.index(key)?;
let greater_key = mask_map_greater.get_mask()?.index(key)?; let greater_key = get_mask(mask_map_greater)?.index(key)?;
if lesser_key.to_string() != greater_key.to_string() { // otherwise zero-length vectors don't count the same as a single type vector
if lesser_key.flexbuffer_type().is_heterogenous_vector()
&& lesser_key.as_vector().len() == 0
&& greater_key.flexbuffer_type().is_vector()
{
continue;
}
if !lesser_key.flexbuffer_type().is_null()
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
{
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into()); return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
} }
} }
@@ -33,49 +53,24 @@ pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
.is_ok() .is_ok()
} }
pub struct Mask(pub Vec<u8>); stardust_xr_server_codegen::codegen_data_protocol!();
impl Mask {
pub fn from_struct<T: Default + Serialize>() -> Self {
let mut serializer = flexbuffers::FlexbufferSerializer::new();
T::default().serialize(&mut serializer).unwrap();
Mask(serializer.take_buffer())
}
pub fn get_mask(&self) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(self.0.as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
}
#[derive(Serialize, Deserialize)]
struct SendDataInfo<'a> {
uid: &'a str,
data: Vec<u8>,
}
pub struct PulseSender { pub struct PulseSender {
uid: String,
node: Weak<Node>, node: Weak<Node>,
pub mask: Mask, pub mask: Datamap,
aliases: LifeLinkedNodeMap<String>, aliases: LifeLinkedNodeMap<String>,
} }
impl PulseSender { impl PulseSender {
pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> { pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let sender = PulseSender { let sender = PulseSender {
uid: nanoid!(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
mask, mask,
aliases: LifeLinkedNodeMap::default(), aliases: LifeLinkedNodeMap::default(),
}; };
// <PulseSender as PulseSenderAspect>::add_node_members(node);
let sender = PULSE_SENDER_REGISTRY.add(sender); let sender = PULSE_SENDER_REGISTRY.add(sender);
let _ = node.pulse_sender.set(sender.clone()); node.add_aspect_raw(sender.clone());
node.add_local_signal("send_data", PulseSender::send_data_flex);
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() { for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver); sender.handle_new_receiver(&receiver);
} }
@@ -85,95 +80,61 @@ impl PulseSender {
if !mask_matches(&self.mask, &receiver.mask) { if !mask_matches(&self.mask, &receiver.mask) {
return; return;
} }
let Some(tx_node) = self.node.upgrade() else { return }; let Some(tx_node) = self.node.upgrade() else {
let Some(tx_client) = tx_node.get_client() else { return }; return;
let Some(rx_node) = receiver.node.upgrade() else { return }; };
let Some(tx_client) = tx_node.get_client() else {
return;
};
let Some(rx_node) = receiver.node.upgrade() else {
return;
};
// Receiver itself // Receiver itself
let rx_alias = Alias::create( let Ok(rx_alias) = Alias::create(
&tx_client, &tx_client,
tx_node.get_path(), tx_node.get_path(),
receiver.uid.as_str(), receiver.uid.as_str(),
&rx_node, &rx_node,
AliasInfo { AliasInfo {
server_methods: vec!["sendData", "getTransform"], server_methods: vec!["send_data"],
..Default::default() ..Default::default()
}, },
); ) else {
if let Ok(rx_alias) = rx_alias { return;
self.aliases.add(receiver.uid.clone(), &rx_alias);
if let Some(rx_field_node) = receiver.field.spatial_ref().node.upgrade() {
// Receiver's field
let rx_field_alias = Alias::create(
&tx_client,
rx_alias.get_path(),
"field",
&rx_field_node,
FIELD_ALIAS_INFO.clone(),
);
if let Ok(rx_field_alias) = rx_field_alias {
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
}
}
}
#[derive(Serialize)]
struct NewReceiverInfo<'a> {
uid: &'a str,
distance: f32,
position: Vector3<f32>,
rotation: Quaternion<f32>,
}
let (_, rotation, position) = Spatial::space_to_space_matrix(
rx_node.spatial.get().map(|s| s.as_ref()),
tx_node.spatial.get().map(|s| s.as_ref()),
)
.to_scale_rotation_translation();
let info = NewReceiverInfo {
uid: &receiver.uid,
distance: receiver
.field
.distance(tx_node.spatial.get().unwrap(), vec3a(0.0, 0.0, 0.0)),
position: position.into(),
rotation: rotation.into(),
}; };
self.aliases.add(receiver.uid.clone(), &rx_alias);
let Ok(data) = serialize(info) else {return}; // Receiver's field
let _ = tx_node.send_remote_signal("new_receiver", &data); let Ok(rx_field_alias) = Alias::create(
&tx_client,
rx_alias.get_path(),
"field",
&rx_node.get_aspect::<PulseReceiver>().unwrap().field_node,
FIELD_ALIAS_INFO.clone(),
) else {
return;
};
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
let _ =
pulse_sender_client::new_receiver(&tx_node, &receiver.uid, &rx_alias, &rx_field_alias);
} }
fn handle_drop_receiver(&self, receiver: &PulseReceiver) { fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str(); let uid = receiver.uid.as_str();
self.aliases.remove(uid); self.aliases.remove(uid);
self.aliases.remove(&(uid.to_string() + "-field")); self.aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {return}; let Some(tx_node) = self.node.upgrade() else {
let Ok(data) = serialize(&uid) else {return}; return;
let _ = tx_node.send_remote_signal("drop_receiver", &data); };
} let _ = pulse_sender_client::drop_receiver(&tx_node, uid);
fn send_data_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let info: SendDataInfo = deserialize(data)?;
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
let receiver =
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
let receiver_mask = &receiver_node
.get_aspect("Pulse receiver", "pulse receiver", |node| {
&node.pulse_receiver
})?
.mask;
let data_mask = Mask(info.data);
data_mask.get_mask()?;
ensure!(
mask_matches(receiver_mask, &data_mask),
"Message does not contain the same keys as the receiver's mask"
);
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
} }
} }
impl Aspect for PulseSender {
const NAME: &'static str = "PulseSender";
}
impl PulseSenderAspect for PulseSender {}
impl Drop for PulseSender { impl Drop for PulseSender {
fn drop(&mut self) { fn drop(&mut self) {
PULSE_SENDER_REGISTRY.remove(self); PULSE_SENDER_REGISTRY.remove(self);
@@ -183,39 +144,52 @@ impl Drop for PulseSender {
pub struct PulseReceiver { pub struct PulseReceiver {
uid: String, uid: String,
pub node: Weak<Node>, pub node: Weak<Node>,
pub field: Arc<Field>, pub field_node: Arc<Node>,
pub mask: Mask, pub mask: Datamap,
} }
impl PulseReceiver { impl PulseReceiver {
pub fn add_to(node: &Arc<Node>, field: Arc<Field>, mask: Mask) -> Result<Arc<PulseReceiver>> { pub fn add_to(
ensure!( node: &Arc<Node>,
node.spatial.get().is_some(), field_node: Arc<Node>,
"Internal: Node does not have a spatial attached!" mask: Datamap,
); ) -> Result<Arc<PulseReceiver>> {
let receiver = PulseReceiver { let receiver = PulseReceiver {
uid: nanoid!(), uid: nanoid!(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
field, field_node,
mask, mask,
}; };
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver); let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
node.add_aspect_raw(receiver.clone());
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() { for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver); sender.handle_new_receiver(&receiver);
} }
let _ = node.pulse_receiver.set(receiver.clone());
Ok(receiver) Ok(receiver)
} }
}
impl Aspect for PulseReceiver {
const NAME: &'static str = "PulseReceiver";
}
impl PulseReceiverAspect for PulseReceiver {
fn send_data(
node: Arc<Node>,
_calling_client: Arc<Client>,
sender: Arc<Node>,
data: Datamap,
) -> Result<()> {
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> { ensure!(
if let Some(node) = self.node.upgrade() { mask_matches(&this_receiver.mask, &data),
node.send_remote_signal("data", &serialize(SendDataInfo { uid, data })?)?; "Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
} this_receiver.mask
);
pulse_receiver_client::data(&node, &sender.uid, &data)?;
Ok(()) Ok(())
} }
} }
impl Drop for PulseReceiver { impl Drop for PulseReceiver {
fn drop(&mut self) { fn drop(&mut self) {
PULSE_RECEIVER_REGISTRY.remove(self); PULSE_RECEIVER_REGISTRY.remove(self);
@@ -225,62 +199,90 @@ impl Drop for PulseReceiver {
} }
} }
pub fn create_interface(client: &Arc<Client>) -> Result<()> { create_interface!(DataInterface, DataInterfaceAspect, "/data");
let node = Node::create(client, "", "data", false); struct DataInterface;
node.add_local_signal("create_pulse_sender", create_pulse_sender_flex); impl DataInterfaceAspect for DataInterface {
node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex); fn create_pulse_sender(
node.add_to_scenegraph().map(|_| ()) _node: Arc<Node>,
} calling_client: Arc<Client>,
name: String,
pub fn create_pulse_sender_flex( parent: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseSenderInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
mask: Vec<u8>, mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_PULSE_SENDER_PARENT_PATH,
&name,
true,
);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseSender::add_to(&node, mask)?;
Ok(())
} }
let info: CreatePulseSenderInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/data/sender", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let mask = Mask(info.mask); fn create_pulse_receiver(
mask.get_mask()?; _node: Arc<Node>,
calling_client: Arc<Client>,
let node = node.add_to_scenegraph()?; name: String,
Spatial::add_to(&node, Some(parent), transform, false)?; parent: Arc<Node>,
PulseSender::add_to(&node, mask)?;
Ok(())
}
pub fn create_pulse_receiver_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseReceiverInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
field_path: &'a str, field: Arc<Node>,
mask: Vec<u8>, mask: Datamap,
} ) -> Result<()> {
let info: CreatePulseReceiverInfo = deserialize(data)?; get_mask(&mask)?;
let node = Node::create(&calling_client, "/data/receiver", info.name, true); let node = Node::create_parent_name(
let parent = find_spatial_parent(&calling_client, info.parent_path)?; &calling_client,
let transform = parse_transform(info.transform, true, true, false); Self::CREATE_PULSE_RECEIVER_PARENT_PATH,
let field = find_field(&calling_client, info.field_path)?; &name,
let mask = Mask(info.mask); true,
mask.get_mask()?; );
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let _ = field.get_aspect::<Field>()?;
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?; Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseReceiver::add_to(&node, field, mask)?; PulseReceiver::add_to(&node, field, mask)?;
Ok(()) Ok(())
}
async fn register_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap: String,
) -> Result<String> {
let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps
.iter()
.filter(|(_k, v)| *v == &keymap)
.map(|(k, _v)| k)
.last()
{
return Ok(found_keymap_id.clone());
}
let generated_id = nanoid!();
keymaps.insert(generated_id.clone(), keymap);
Ok(generated_id)
}
async fn get_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap_id: String,
) -> Result<String> {
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(&keymap_id) else {
bail!("Could not find keymap. Try registering it")
};
Ok(keymap.clone())
}
} }

View File

@@ -1,68 +1,48 @@
use super::{Line, LinesAspect};
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, registry::Registry},
nodes::{ nodes::{spatial::Spatial, Aspect, Node},
spatial::{find_spatial_parent, parse_transform, Spatial},
Node,
},
}; };
use color_eyre::eyre::{bail, ensure, Result}; use color_eyre::eyre::Result;
use glam::Vec3A; use glam::Vec3A;
use mint::Vector3;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Lerp, Rgba}; use prisma::Lerp;
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw}; use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
use super::Drawable;
static LINES_REGISTRY: Registry<Lines> = Registry::new(); static LINES_REGISTRY: Registry<Lines> = Registry::new();
#[derive(Debug, Clone, Deserialize)]
struct LinePointRaw {
point: Vector3<f32>,
thickness: f32,
color: [f32; 4],
}
#[derive(Debug, Clone)]
struct LineData {
points: Vec<LinePointRaw>,
cyclic: bool,
}
pub struct Lines { pub struct Lines {
enabled: Arc<AtomicBool>, enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
data: Mutex<LineData>, data: Mutex<Vec<Line>>,
} }
impl Lines { impl Lines {
fn add_to(node: &Arc<Node>, points: Vec<LinePointRaw>, cyclic: bool) -> Result<Arc<Lines>> { pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
ensure!( let _ = node
node.drawable.get().is_none(), .get_aspect::<Spatial>()
"Internal: Node already has a drawable attached!" .unwrap()
); .bounding_box_calc
.set(|node| {
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| { let mut bounds = Bounds::default();
let mut bounds = Bounds::default(); if let Ok(lines) = node.get_aspect::<Lines>() {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {return bounds}; for line in &*lines.data.lock() {
for point in &lines.data.lock().points { for point in &line.points {
bounds = bounds_grow_to_fit_pt(bounds, point.point); bounds = bounds_grow_to_fit_pt(bounds, point.point);
} }
}
bounds }
}); bounds
});
let lines = LINES_REGISTRY.add(Lines { let lines = LINES_REGISTRY.add(Lines {
enabled: node.enabled.clone(), enabled: node.enabled.clone(),
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(), space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(LineData { points, cyclic }), data: Mutex::new(lines),
}); });
node.add_local_signal("set_points", Lines::set_points_flex); <Lines as LinesAspect>::add_node_members(node);
node.add_local_signal("set_cyclic", Lines::set_cyclic_flex); node.add_aspect_raw(lines.clone());
let _ = node.drawable.set(Drawable::Lines(lines.clone()));
Ok(lines) Ok(lines)
} }
@@ -70,49 +50,55 @@ impl Lines {
fn draw(&self, draw_ctx: &impl StereoKitDraw) { fn draw(&self, draw_ctx: &impl StereoKitDraw) {
let transform_mat = self.space.global_transform(); let transform_mat = self.space.global_transform();
let data = self.data.lock().clone(); let data = self.data.lock().clone();
let mut points: VecDeque<SkLinePoint> = data for line in &data {
.points let mut points: VecDeque<SkLinePoint> = line
.iter() .points
.map(|p| SkLinePoint { .iter()
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(), .map(|p| SkLinePoint {
thickness: p.thickness, pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
color: p.color.map(|c| (c * 255.0) as u8).into(), thickness: p.thickness,
}) color: stereokit::sys::color128::from([
.collect(); p.color.c.r,
if data.cyclic && !points.is_empty() { p.color.c.g,
let first = data.points.first().unwrap(); p.color.c.b,
let last = data.points.last().unwrap(); p.color.a,
let color = Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5); ])
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5))
.into(), .into(),
thickness: (first.thickness + last.thickness) * 0.5, })
color: Color128::from([color.red(), color.green(), color.blue(), color.alpha()]) .collect();
.into(), if line.cyclic && !points.is_empty() {
}; let first = line.points.first().unwrap();
points.push_front(connect_point.clone()); let last = line.points.last().unwrap();
points.push_back(connect_point);
let color = Color128 {
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
a: first.color.a.lerp(&last.color.a, 0.5),
};
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(
Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5),
)
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: color.into(),
};
points.push_front(connect_point.clone());
points.push_back(connect_point);
}
draw_ctx.line_add_listv(points.make_contiguous());
} }
draw_ctx.line_add_listv(points.make_contiguous());
} }
}
pub fn set_points_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { impl Aspect for Lines {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")}; const NAME: &'static str = "Lines";
}
let mut points: Vec<LinePointRaw> = deserialize(data)?; impl LinesAspect for Lines {
for p in &mut points { fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
p.color[0] = p.color[0].powf(2.2); let lines_aspect = node.get_aspect::<Lines>()?;
p.color[1] = p.color[1].powf(2.2); *lines_aspect.data.lock() = lines;
p.color[2] = p.color[2].powf(2.2);
}
lines.data.lock().points = points;
Ok(())
}
pub fn set_cyclic_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
lines.data.lock().cyclic = deserialize(data)?;
Ok(()) Ok(())
} }
} }
@@ -129,29 +115,3 @@ pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
points: Vec<LinePointRaw>,
cyclic: bool,
}
let mut info: CreateTextInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/drawable/lines", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
for p in &mut info.points {
p.color[0] = p.color[0].powf(2.2);
p.color[1] = p.color[1].powf(2.2);
p.color[2] = p.color[2].powf(2.2);
}
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Lines::add_to(&node, info.points, info.cyclic)?;
Ok(())
}

View File

@@ -3,39 +3,22 @@ pub mod model;
pub mod shaders; pub mod shaders;
pub mod text; pub mod text;
use self::{ use self::{lines::Lines, model::Model, text::Text};
lines::Lines, use super::{
model::{Model, ModelPart}, spatial::{Spatial, Transform},
text::Text, Node,
}; };
use crate::{
use super::Node; core::{client::Client, resource::get_resource_file},
use crate::core::client::Client; create_interface,
use color_eyre::eyre::Result; };
use color_eyre::eyre::{self, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; use stardust_xr::values::ResourceID;
use stardust_xr::schemas::flex::deserialize; use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use std::{path::PathBuf, sync::Arc};
use stereokit::StereoKitDraw; use stereokit::StereoKitDraw;
use tracing::instrument;
pub fn create_interface(client: &Arc<Client>) -> Result<()> { // #[instrument(level = "debug", skip(sk))]
let node = Node::create(client, "", "drawable", false);
node.add_local_signal("create_lines", lines::create_flex);
node.add_local_signal("create_model", model::create_flex);
node.add_local_signal("create_text", text::create_flex);
node.add_local_signal("set_sky_file", set_sky_file_flex);
node.add_to_scenegraph().map(|_| ())
}
pub enum Drawable {
Lines(Arc<Lines>),
Model(Arc<Model>),
ModelPart(Arc<ModelPart>),
Text(Arc<Text>),
}
#[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) { pub fn draw(sk: &impl StereoKitDraw) {
lines::draw_all(sk); lines::draw_all(sk);
model::draw_all(sk); model::draw_all(sk);
@@ -56,21 +39,83 @@ pub fn draw(sk: &impl StereoKitDraw) {
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
fn set_sky_file_flex(_node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { stardust_xr_server_codegen::codegen_drawable_protocol!();
#[derive(Deserialize)] create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable");
struct SkyFileInfo {
path: PathBuf, pub struct DrawableInterface;
skytex: Option<bool>, impl DrawableInterfaceAspect for DrawableInterface {
skylight: Option<bool>, fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
} let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
let info: SkyFileInfo = deserialize(data)?; .ok_or(eyre::eyre!("Could not find resource"))?;
info.path.metadata()?; QUEUED_SKYTEX.lock().replace(resource_path);
if info.skytex.unwrap_or_default() { Ok(())
QUEUED_SKYTEX.lock().replace(info.path.clone());
}
if info.skylight.unwrap_or_default() {
QUEUED_SKYLIGHT.lock().replace(info.path);
} }
Ok(()) fn set_sky_light(
_node: Arc<Node>,
calling_client: Arc<Client>,
light: ResourceID,
) -> Result<()> {
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?;
QUEUED_SKYLIGHT.lock().replace(resource_path);
Ok(())
}
fn create_lines(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
lines: Vec<Line>,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_LINES_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Lines::add_to(&node, lines)?;
Ok(())
}
fn load_model(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
model: ResourceID,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::LOAD_MODEL_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Model::add_to(&node, model)?;
Ok(())
}
fn create_text(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
text: String,
style: TextStyle,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_TEXT_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Text::add_to(&node, text, style)?;
Ok(())
}
} }

View File

@@ -1,53 +1,31 @@
use super::Node; use super::{MaterialParameter, ModelAspect, ModelPartAspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::node_collections::LifeLinkedNodeMap; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::resource::ResourceID; use crate::core::resource::get_resource_file;
use crate::nodes::drawable::Drawable; use crate::nodes::spatial::Spatial;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::Aspect;
use crate::SK_MULTITHREAD; use crate::SK_MULTITHREAD;
use color_eyre::eyre::{bail, ensure, eyre, Result}; use color_eyre::eyre::{eyre, Result};
use glam::Mat4;
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use send_wrapper::SendWrapper; use stardust_xr::values::ResourceID;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use stereokit::named_colors::WHITE; use stereokit::named_colors::WHITE;
use stereokit::{ use stereokit::{
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw, Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
StereoKitMultiThread, StereoKitMultiThread, Transparency,
}; };
static MODEL_REGISTRY: Registry<Model> = Registry::new(); static MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new();
#[derive(Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
pub enum MaterialParameter {
Float(f32),
Vector2(Vector2<f32>),
Vector3(Vector3<f32>),
Vector4(Vector4<f32>),
Color([f32; 4]),
Int(i32),
Int2(Vector2<i32>),
Int3(Vector3<i32>),
Int4(Vector4<i32>),
Bool(bool),
UInt(u32),
UInt2(Vector2<u32>),
UInt3(Vector3<u32>),
UInt4(Vector4<u32>),
Matrix(ColumnMatrix4<f32>),
Texture(ResourceID),
}
impl MaterialParameter { impl MaterialParameter {
fn apply_to_material( fn apply_to_material(
&self, &self,
@@ -57,56 +35,37 @@ impl MaterialParameter {
parameter_name: &str, parameter_name: &str,
) { ) {
match self { match self {
MaterialParameter::Float(val) => { MaterialParameter::Bool(val) => {
sk.material_set_float(material, parameter_name, *val); sk.material_set_bool(material, parameter_name, *val);
}
MaterialParameter::Vector2(val) => {
sk.material_set_vector2(material, parameter_name, *val);
}
MaterialParameter::Vector3(val) => {
sk.material_set_vector3(material, parameter_name, *val);
}
MaterialParameter::Vector4(val) => {
sk.material_set_vector4(material, parameter_name, *val);
}
MaterialParameter::Color(val) => {
sk.material_set_color(material, parameter_name, Color128::from(val.clone()));
} }
MaterialParameter::Int(val) => { MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *val); sk.material_set_int(material, parameter_name, *val);
} }
MaterialParameter::Int2(val) => {
sk.material_set_int2(material, parameter_name, val.x, val.y);
}
MaterialParameter::Int3(val) => {
sk.material_set_int3(material, parameter_name, val.x, val.y, val.z);
}
MaterialParameter::Int4(val) => {
sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z);
}
MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val);
}
MaterialParameter::UInt(val) => { MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val); sk.material_set_uint(material, parameter_name, *val);
} }
MaterialParameter::UInt2(val) => { MaterialParameter::Float(val) => {
sk.material_set_uint2(material, parameter_name, val.x, val.y); sk.material_set_float(material, parameter_name, *val);
} }
MaterialParameter::UInt3(val) => { MaterialParameter::Vec2(val) => {
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z); sk.material_set_vector2(material, parameter_name, *val);
} }
MaterialParameter::UInt4(val) => { MaterialParameter::Vec3(val) => {
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z); sk.material_set_vector3(material, parameter_name, *val);
} }
MaterialParameter::Matrix(val) => { MaterialParameter::Color(val) => {
sk.material_set_matrix(material, parameter_name, Mat4::from(*val)); sk.material_set_color(
material,
parameter_name,
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
);
} }
MaterialParameter::Texture(resource) => { MaterialParameter::Texture(resource) => {
let Some(texture_path) = resource.get_file( let Some(texture_path) =
&client.base_resource_prefixes.lock().clone(), get_resource_file(&resource, &client, &[OsStr::new("png"), OsStr::new("jpg")])
&[OsStr::new("png"), OsStr::new("jpg")], else {
) else {return}; return;
};
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) { if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
sk.material_set_texture(material, parameter_name, &tex); sk.material_set_texture(material, parameter_name, &tex);
} }
@@ -121,10 +80,26 @@ pub struct ModelPart {
space: Arc<Spatial>, space: Arc<Spatial>,
model: Weak<Model>, model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<SendWrapper<Material>>>>, pending_material_replacement: Mutex<Option<Arc<Material>>>,
} }
impl ModelPart { impl ModelPart {
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) { fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
HOLDOUT_MATERIAL.get_or_init(|| {
let mat = sk.material_copy(Material::UNLIT);
sk.material_set_transparency(&mat, Transparency::None);
sk.material_set_color(
&mat,
"color",
stereokit::sys::color128 {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
);
Arc::new(mat)
});
let first_root_part = sk.model_node_get_root(sk_model); let first_root_part = sk.model_node_get_root(sk_model);
let mut current_option_part = Some(first_root_part); let mut current_option_part = Some(first_root_part);
@@ -158,40 +133,50 @@ impl ModelPart {
.and_then(|id| model.parts.get(&id)); .and_then(|id| model.parts.get(&id));
let parent_part = parent_node let parent_part = parent_node
.as_ref() .as_ref()
.and_then(|node| match node.drawable.get() { .and_then(|node| node.get_aspect::<ModelPart>().ok());
Some(Drawable::ModelPart(model_part)) => Some(model_part),
_ => None,
});
let stardust_model_part = model.space.node()?; let stardust_model_part = model.space.node()?;
let client = stardust_model_part.get_client()?; let client = stardust_model_part.get_client()?;
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default(); let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default();
part_path.push(sk.model_node_get_name(sk_model, id)?); part_path.push(sk.model_node_get_name(sk_model, id)?);
let node = client.scenegraph.add_node(Node::create( let node = client.scenegraph.add_node(Node::create_parent_name(
&client, &client,
stardust_model_part.get_path(), stardust_model_part.get_path(),
part_path.to_str()?, part_path.to_str()?,
false, false,
)); ));
let spatial_parent = parent_node let spatial_parent = parent_node
.and_then(|n| n.spatial.get().cloned()) .and_then(|n| n.get_aspect::<Spatial>().ok())
.unwrap_or_else(|| model.space.clone()); .unwrap_or_else(|| model.space.clone());
let space = Spatial::add_to( let space = Spatial::add_to(
&node, &node,
Some(spatial_parent), Some(spatial_parent),
sk.model_node_get_transform_local(sk_model, id), sk.model_node_get_transform_local(sk_model, id),
false, false,
) );
.ok()?;
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| { let _ = node
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {return Bounds::default()}; .get_aspect::<Spatial>()
let Some(sk) = SK_MULTITHREAD.get() else {return Bounds::default()}; .unwrap()
let Some(model) = model_part.model.upgrade() else {return Bounds::default()}; .bounding_box_calc
let Some(sk_model) = model.sk_model.get() else {return Bounds::default()}; .set(|node| {
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {return Bounds::default()}; let Ok(model_part) = node.get_aspect::<ModelPart>() else {
sk.mesh_get_bounds(sk_mesh) return Bounds::default();
}); };
let Some(sk) = SK_MULTITHREAD.get() else {
return Bounds::default();
};
let Some(model) = model_part.model.upgrade() else {
return Bounds::default();
};
let Some(sk_model) = model.sk_model.get() else {
return Bounds::default();
};
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {
return Bounds::default();
};
sk.mesh_get_bounds(sk_mesh)
});
let model_part = Arc::new(ModelPart { let model_part = Arc::new(ModelPart {
id, id,
@@ -201,50 +186,40 @@ impl ModelPart {
pending_material_parameters: Mutex::new(FxHashMap::default()), pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None), pending_material_replacement: Mutex::new(None),
}); });
node.add_local_signal( <ModelPart as ModelPartAspect>::add_node_members(&node);
"set_material_parameter", node.add_aspect_raw(model_part.clone());
ModelPart::set_material_parameter_flex,
);
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
model.parts.add(id, &node); model.parts.add(id, &node);
Some(model_part) Some(model_part)
} }
fn set_material_parameter_flex( pub fn replace_material(&self, replacement: Arc<Material>) {
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
let (name, value): (String, MaterialParameter) = deserialize(data)?;
model_part
.pending_material_parameters
.lock()
.insert(name, value);
Ok(())
}
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
self.pending_material_replacement self.pending_material_replacement
.lock() .lock()
.replace(replacement); .replace(replacement);
} }
fn update(&self, sk: &impl StereoKitDraw) { fn update(&self, sk: &impl StereoKitDraw) {
let Some(model) = self.model.upgrade() else {return}; let Some(model) = self.model.upgrade() else {
let Some(sk_model) = model.sk_model.get() else {return}; return;
let Some(node) = model.space.node() else {return}; };
let Some(client) = node.get_client() else {return}; let Some(sk_model) = model.sk_model.get() else {
return;
};
let Some(node) = model.space.node() else {
return;
};
let Some(client) = node.get_client() else {
return;
};
if let Some(material_replacement) = self.pending_material_replacement.lock().take() { if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref()); sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref());
} }
let mut material_parameters = self.pending_material_parameters.lock(); let mut material_parameters = self.pending_material_parameters.lock();
for (parameter_name, parameter_value) in material_parameters.drain() { for (parameter_name, parameter_value) in material_parameters.drain() {
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {continue}; let Some(material) = sk.model_node_get_material(sk_model, self.id) else {
continue;
};
let new_material = sk.material_copy(material); let new_material = sk.material_copy(material);
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str()); parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
sk.model_node_set_material(sk_model, self.id, &new_material); sk.model_node_set_material(sk_model, self.id, &new_material);
@@ -257,6 +232,33 @@ impl ModelPart {
); );
} }
} }
impl Aspect for ModelPart {
const NAME: &'static str = "ModelPart";
}
impl ModelPartAspect for ModelPart {
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let model_part = node.get_aspect::<ModelPart>()?;
model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone());
Ok(())
}
#[doc = "Set the material parameter with `parameter_name` to `value`"]
fn set_material_parameter(
node: Arc<Node>,
_calling_client: Arc<Client>,
parameter_name: String,
value: MaterialParameter,
) -> Result<()> {
let model_part = node.get_aspect::<ModelPart>()?;
model_part
.pending_material_parameters
.lock()
.insert(parameter_name, value);
Ok(())
}
}
pub struct Model { pub struct Model {
self_ref: Weak<Model>, self_ref: Weak<Model>,
@@ -271,31 +273,17 @@ unsafe impl Sync for Model {}
impl Model { impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> { pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
ensure!( let pending_model_path = get_resource_file(
node.spatial.get().is_some(), &resource_id,
"Internal: Node does not have a spatial attached!" &*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
); &[OsStr::new("glb"), OsStr::new("gltf")],
ensure!( )
node.drawable.get().is_none(), .ok_or_else(|| eyre!("Resource not found"))?;
"Internal: Node already has a drawable attached!"
);
let pending_model_path = resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("glb"), OsStr::new("gltf")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model { let model = Arc::new_cyclic(|self_ref| Model {
self_ref: self_ref.clone(), self_ref: self_ref.clone(),
enabled: node.enabled.clone(), enabled: node.enabled.clone(),
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
_resource_id: resource_id, _resource_id: resource_id,
sk_model: OnceCell::new(), sk_model: OnceCell::new(),
parts: LifeLinkedNodeMap::default(), parts: LifeLinkedNodeMap::default(),
@@ -308,15 +296,18 @@ impl Model {
); );
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model); ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
let _ = model.sk_model.set(sk_model); let _ = model.sk_model.set(sk_model);
let _ = node.drawable.set(Drawable::Model(model.clone())); node.add_aspect_raw(model.clone());
Ok(model) Ok(model)
} }
fn draw(&self, sk: &impl StereoKitDraw) { fn draw(&self, sk: &impl StereoKitDraw) {
let Some(sk_model) = self.sk_model.get() else {return}; let Some(sk_model) = self.sk_model.get() else {
return;
};
for model_node_node in self.parts.nodes() { for model_node_node in self.parts.nodes() {
let Some(Drawable::ModelPart(model_node)) = model_node_node.drawable.get() else {continue}; if let Ok(model_node) = model_node_node.get_aspect::<ModelPart>() {
model_node.update(sk); model_node.update(sk);
};
} }
sk.model_draw( sk.model_draw(
@@ -327,9 +318,15 @@ impl Model {
); );
} }
} }
impl Aspect for Model {
const NAME: &'static str = "Model";
}
impl ModelAspect for Model {}
impl Drop for Model { impl Drop for Model {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(sk_model) = self.sk_model.take() {
destroy_queue::add(sk_model);
}
MODEL_REGISTRY.remove(self); MODEL_REGISTRY.remove(self);
} }
} }
@@ -341,21 +338,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateModelInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
resource: ResourceID,
}
let info: CreateModelInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/drawable/model", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Model::add_to(&node, info.resource)?;
Ok(())
}

View File

@@ -1,5 +1,4 @@
#![allow(dead_code)] #![allow(dead_code)]
use smithay::backend::renderer::gles::{ use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER}, ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError, GlesError,
@@ -8,6 +7,12 @@ use std::mem::transmute;
use stereokit::Shader; use stereokit::Shader;
use tracing::error; use tracing::error;
// Simula shader with fancy lanzcos sampling
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
struct FfiAssetHeader { struct FfiAssetHeader {
asset_type: i32, asset_type: i32,
asset_state: i32, asset_state: i32,

View File

@@ -1,110 +1,79 @@
use crate::{ use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID}, core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
nodes::{ nodes::{spatial::Spatial, Aspect, Node},
drawable::Drawable,
spatial::{find_spatial_parent, parse_transform, Spatial},
Node,
},
}; };
use color_eyre::eyre::{bail, ensure, eyre, Result}; use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Mat4, Vec2}; use glam::{vec3, Mat4, Vec2};
use mint::Vector2;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Rgba};
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle}; use stereokit::{
named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle as SkTextStyle,
};
use super::{TextAspect, TextStyle};
static TEXT_REGISTRY: Registry<Text> = Registry::new(); static TEXT_REGISTRY: Registry<Text> = Registry::new();
struct TextData { fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
text: String, let x_align = match x_align {
character_height: f32, super::XAlign::Left => TextAlign::XLeft,
text_align: TextAlign, super::XAlign::Center => TextAlign::XCenter,
bounds: Option<Vec2>, super::XAlign::Right => TextAlign::XRight,
fit: TextFit, } as u32;
bounds_align: TextAlign, let y_align = match y_align {
color: Rgba<f32>, super::YAlign::Top => TextAlign::YTop,
super::YAlign::Center => TextAlign::YCenter,
super::YAlign::Bottom => TextAlign::YBottom,
} as u32;
unsafe { std::mem::transmute(x_align | y_align) }
} }
pub struct Text { pub struct Text {
enabled: Arc<AtomicBool>, enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
font_path: Option<PathBuf>, font_path: Option<PathBuf>,
style: OnceCell<SendWrapper<TextStyle>>, style: OnceCell<SkTextStyle>,
data: Mutex<TextData>, text: Mutex<String>,
data: Mutex<TextStyle>,
} }
impl Text { impl Text {
#[allow(clippy::too_many_arguments)] pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
pub fn add_to(
node: &Arc<Node>,
font_resource_id: Option<ResourceID>,
text: String,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: Rgba<f32>,
) -> Result<Arc<Text>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?; let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text { let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(), enabled: node.enabled.clone(),
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
font_path: font_resource_id.and_then(|res| { font_path: style.font.as_ref().and_then(|res| {
res.get_file( get_resource_file(&res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("ttf"), OsStr::new("otf")],
)
}), }),
style: OnceCell::new(), style: OnceCell::new(),
data: Mutex::new(TextData { text: Mutex::new(text),
text, data: Mutex::new(style),
character_height,
text_align,
bounds: bounds.map(|b| b.into()),
fit,
bounds_align,
color,
}),
}); });
node.add_local_signal("set_character_height", Text::set_character_height_flex); <Text as TextAspect>::add_node_members(node);
node.add_local_signal("set_text", Text::set_text_flex); node.add_aspect_raw(text.clone());
let _ = node.drawable.set(Drawable::Text(text.clone()));
Ok(text) Ok(text)
} }
fn draw(&self, sk: &impl StereoKitDraw) { fn draw(&self, sk: &impl StereoKitDraw) {
let style = self.style.get_or_try_init( let style =
|| -> Result<SendWrapper<TextStyle>, color_eyre::eyre::Error> { self.style
let font = self .get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
.font_path let font = self
.as_deref() .font_path
.and_then(|path| sk.font_create(path).ok()) .as_deref()
.unwrap_or_else(|| sk.font_find("default/font").unwrap()); .and_then(|path| sk.font_create(path).ok())
Ok(SendWrapper::new(unsafe { .unwrap_or_else(|| sk.font_find("default/font").unwrap());
sk.text_make_style(font, 1.0, WHITE) Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) })
})) });
},
);
if let Ok(style) = style { if let Ok(style) = style {
let text = self.text.lock();
let data = self.data.lock(); let data = self.data.lock();
let transform = self.space.global_transform() let transform = self.space.global_transform()
* Mat4::from_scale(vec3( * Mat4::from_scale(vec3(
@@ -112,57 +81,55 @@ impl Text {
data.character_height, data.character_height,
data.character_height, data.character_height,
)); ));
if let Some(bounds) = data.bounds { if let Some(bounds) = &data.bounds {
sk.text_add_in( sk.text_add_in(
&data.text, &*text,
transform, transform,
bounds / data.character_height, Vec2::from(bounds.bounds) / data.character_height,
data.fit, match bounds.fit {
**style, super::TextFit::Wrap => TextFit::Wrap,
data.bounds_align, super::TextFit::Clip => TextFit::Clip,
data.text_align, super::TextFit::Squeeze => TextFit::Squeeze,
super::TextFit::Exact => TextFit::Exact,
super::TextFit::Overflow => TextFit::Overflow,
},
*style,
convert_align(bounds.anchor_align_x.clone(), bounds.anchor_align_y.clone()),
convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0),
Color128::from([ Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
); );
} else { } else {
sk.text_add_at( sk.text_add_at(
&data.text, &*text,
transform, transform,
**style, *style,
data.bounds_align, TextAlign::Center,
data.text_align, convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0),
Color128::from([ Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
); );
} }
} }
} }
}
pub fn set_character_height_flex( impl Aspect for Text {
node: &Node, const NAME: &'static str = "Text";
}
impl TextAspect for Text {
fn set_character_height(
node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8], height: f32,
) -> Result<()> { ) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")}; let this_text = node.get_aspect::<Text>()?;
this_text.data.lock().character_height = height;
text.data.lock().character_height = deserialize(data)?;
Ok(()) Ok(())
} }
pub fn set_text_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")}; let this_text = node.get_aspect::<Text>()?;
*this_text.text.lock() = text;
text.data.lock().text = deserialize(data)?;
Ok(()) Ok(())
} }
} }
@@ -182,40 +149,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
text: String,
font_resource: Option<ResourceID>,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: [f32; 4],
}
let info: CreateTextInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let color = Rgba::from_slice(&info.color);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Text::add_to(
&node,
info.font_resource,
info.text,
info.character_height,
info.text_align,
info.bounds,
info.fit,
info.bounds_align,
color,
)?;
Ok(())
}

View File

@@ -1,13 +1,11 @@
use super::{Field, FieldTrait, Node}; use super::{BoxFieldAspect, FieldTrait, Node};
use crate::core::client::Client; use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::Spatial;
use color_eyre::eyre::{ensure, Result}; use crate::{core::client::Client, nodes::fields::Field};
use color_eyre::eyre::Result;
use glam::{vec3, vec3a, Vec3, Vec3A}; use glam::{vec3, vec3a, Vec3, Vec3A};
use mint::Vector3; use mint::Vector3;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::Arc; use std::sync::Arc;
pub struct BoxField { pub struct BoxField {
@@ -16,36 +14,19 @@ pub struct BoxField {
} }
impl BoxField { impl BoxField {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<Arc<Field>> { pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let box_field = BoxField { let box_field = BoxField {
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
size: Mutex::new(size.into()), size: Mutex::new(size.into()),
}; };
box_field.add_field_methods(node); <BoxField as FieldAspect>::add_node_members(node);
node.add_local_signal("set_size", BoxField::set_size_flex); <BoxField as BoxFieldAspect>::add_node_members(node);
let field = Arc::new(Field::Box(box_field)); node.add_aspect(Field::Box(box_field));
let _ = node.field.set(field.clone());
Ok(field)
} }
pub fn set_size(&self, size: Vector3<f32>) { pub fn set_size(&self, size: Vector3<f32>) {
*self.size.lock() = size.into(); *self.size.lock() = size.into();
} }
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
box_field.set_size(deserialize(data)?);
Ok(())
}
} }
impl FieldTrait for BoxField { impl FieldTrait for BoxField {
@@ -63,21 +44,17 @@ impl FieldTrait for BoxField {
self.space.as_ref() self.space.as_ref()
} }
} }
impl BoxFieldAspect for BoxField {
pub fn create_box_field_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_size(
#[derive(Deserialize)] node: Arc<Node>,
struct CreateFieldInfo<'a> { _calling_client: Arc<Client>,
name: &'a str, size: mint::Vector3<f32>,
parent_path: &'a str, ) -> Result<()> {
transform: Transform, let this_field = node.get_aspect::<Field>()?;
size: Vector3<f32>, let Field::Box(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(size.into());
Ok(())
} }
let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
BoxField::add_to(&node, info.size)?;
Ok(())
} }

View File

@@ -1,12 +1,10 @@
use super::{Field, FieldTrait, Node}; use super::{CylinderFieldAspect, Field, FieldTrait, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::fields::FieldAspect;
use color_eyre::eyre::{ensure, Result}; use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::{swizzles::*, vec2, Vec3A}; use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32; use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@@ -18,39 +16,22 @@ pub struct CylinderField {
} }
impl CylinderField { impl CylinderField {
pub fn add_to(node: &Arc<Node>, length: f32, radius: f32) -> Result<()> { pub fn add_to(node: &Arc<Node>, length: f32, radius: f32) {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let cylinder_field = CylinderField { let cylinder_field = CylinderField {
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
length: AtomicF32::new(length.abs()), length: AtomicF32::new(length.abs()),
radius: AtomicF32::new(radius.abs()), radius: AtomicF32::new(radius.abs()),
}; };
cylinder_field.add_field_methods(node); <CylinderField as FieldAspect>::add_node_members(node);
node.add_local_signal("set_size", CylinderField::set_size_flex); <CylinderField as CylinderFieldAspect>::add_node_members(node);
let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field))); node.add_aspect(Field::Cylinder(cylinder_field));
Ok(())
} }
pub fn set_size(&self, length: f32, radius: f32) { pub fn set_size(&self, length: f32, radius: f32) {
self.length.store(length.abs(), Ordering::Relaxed); self.length.store(length.abs(), Ordering::Relaxed);
self.radius.store(radius.abs(), Ordering::Relaxed); self.radius.store(radius.abs(), Ordering::Relaxed);
} }
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (length, radius) = deserialize(data)?;
cylinder_field.set_size(length, radius);
Ok(())
}
} }
impl FieldTrait for CylinderField { impl FieldTrait for CylinderField {
fn local_distance(&self, p: Vec3A) -> f32 { fn local_distance(&self, p: Vec3A) -> f32 {
let radius = self.radius.load(Ordering::Relaxed); let radius = self.radius.load(Ordering::Relaxed);
@@ -63,26 +44,18 @@ impl FieldTrait for CylinderField {
self.space.as_ref() self.space.as_ref()
} }
} }
impl CylinderFieldAspect for CylinderField {
pub fn create_cylinder_field_flex( fn set_size(
_node: &Node, node: Arc<Node>,
calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
length: f32, length: f32,
radius: f32, radius: f32,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Cylinder(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(length, radius);
Ok(())
} }
let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
CylinderField::add_to(&node, info.length, info.radius)?;
Ok(())
} }

View File

@@ -3,38 +3,34 @@ mod cylinder;
mod sphere; mod sphere;
mod torus; mod torus;
use self::cylinder::{create_cylinder_field_flex, CylinderField}; use self::cylinder::CylinderField;
use self::r#box::{create_box_field_flex, BoxField}; use self::r#box::BoxField;
use self::sphere::{create_sphere_field_flex, SphereField}; use self::sphere::SphereField;
use self::torus::{create_torus_field_flex, TorusField}; use self::torus::TorusField;
use super::alias::AliasInfo; use super::alias::AliasInfo;
use super::spatial::Spatial; use super::spatial::Spatial;
use super::Node; use super::{Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::find_reference_space; use crate::create_interface;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec2, vec3a, Vec3, Vec3A}; use glam::{vec2, vec3a, Mat4, Vec3, Vec3A};
use mint::Vector3; use mint::Vector3;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
// TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo { pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
server_methods: vec!["distance", "normal", "closest_point", "ray_march"], server_methods: vec!["distance", "normal", "closest_point", "ray_march"],
..Default::default() ..Default::default()
}); });
pub trait FieldTrait { stardust_xr_server_codegen::codegen_field_protocol!();
fn add_field_methods(&self, node: &Arc<Node>) {
node.add_local_method("distance", field_distance_flex); pub trait FieldTrait: Send + Sync + 'static {
node.add_local_method("normal", field_normal_flex);
node.add_local_method("closest_point", field_closest_point_flex);
node.add_local_method("ray_march", field_ray_march_flex);
}
fn spatial_ref(&self) -> &Spatial; fn spatial_ref(&self) -> &Spatial;
fn local_distance(&self, p: Vec3A) -> f32; fn local_distance(&self, p: Vec3A) -> f32;
@@ -80,6 +76,8 @@ pub trait FieldTrait {
fn ray_march(&self, ray: Ray) -> RayMarchResult { fn ray_march(&self, ray: Ray) -> RayMarchResult {
let mut result = RayMarchResult { let mut result = RayMarchResult {
ray_origin: ray.origin.into(),
ray_direction: ray.direction.into(),
min_distance: f32::MAX, min_distance: f32::MAX,
deepest_point_distance: 0_f32, deepest_point_distance: 0_f32,
ray_length: 0_f32, ray_length: 0_f32,
@@ -89,7 +87,9 @@ pub trait FieldTrait {
let ray_to_field_matrix = let ray_to_field_matrix =
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref())); Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into()); let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
let ray_direction = ray_to_field_matrix.transform_vector3a(ray.direction.into()); let ray_direction = ray_to_field_matrix
.transform_vector3a(ray.direction.into())
.normalize();
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH { while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
let distance = self.local_distance(ray_point); let distance = self.local_distance(ray_point);
@@ -109,6 +109,60 @@ pub trait FieldTrait {
result result
} }
} }
impl<Fi: FieldTrait + 'static> FieldAspect for Fi {
async fn distance(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<f32> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok((*this_field).distance(reference_space.as_ref(), point.into()))
}
async fn normal(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field
.normal(reference_space.as_ref(), point.into(), 0.001)
.into())
}
async fn closest_point(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field
.closest_point(reference_space.as_ref(), point.into(), 0.001)
.into())
}
async fn ray_march(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
ray_origin: mint::Vector3<f32>,
ray_direction: mint::Vector3<f32>,
) -> Result<RayMarchResult> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field.ray_march(Ray {
origin: ray_origin.into(),
direction: ray_direction.into(),
space: reference_space.clone(),
}))
}
}
pub struct Ray { pub struct Ray {
pub origin: Vec3, pub origin: Vec3,
@@ -116,14 +170,6 @@ pub struct Ray {
pub space: Arc<Spatial>, pub space: Arc<Spatial>,
} }
#[derive(Debug, Serialize)]
pub struct RayMarchResult {
pub min_distance: f32,
pub deepest_point_distance: f32,
pub ray_length: f32,
pub ray_steps: u32,
}
// const MIN_RAY_STEPS: u32 = 0; // const MIN_RAY_STEPS: u32 = 0;
const MAX_RAY_STEPS: u32 = 1000; const MAX_RAY_STEPS: u32 = 1000;
@@ -133,85 +179,15 @@ const MAX_RAY_MARCH: f32 = f32::MAX;
// const MIN_RAY_LENGTH: f32 = 0_f32; // const MIN_RAY_LENGTH: f32 = 0_f32;
const MAX_RAY_LENGTH: f32 = 1000_f32; const MAX_RAY_LENGTH: f32 = 1000_f32;
fn field_distance_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(data)?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let distance = node
.field
.get()
.unwrap()
.distance(reference_space.as_ref(), args.point.into());
Ok(serialize(distance)?)
}
fn field_normal_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(data)?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let normal = node.field.get().as_ref().unwrap().normal(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(normal))?)
}
fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(data)?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let closest_point = node.field.get().as_ref().unwrap().closest_point(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(closest_point))?)
}
fn field_ray_march_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
ray_origin: Vector3<f32>,
ray_direction: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(data)?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let ray_march_result = node.field.get().unwrap().ray_march(Ray {
origin: args.ray_origin.into(),
direction: args.ray_direction.into(),
space: reference_space,
});
Ok(serialize(ray_march_result)?)
}
pub enum Field { pub enum Field {
Box(BoxField), Box(BoxField),
Cylinder(CylinderField), Cylinder(CylinderField),
Sphere(SphereField), Sphere(SphereField),
Torus(TorusField), Torus(TorusField),
} }
impl Aspect for Field {
const NAME: &'static str = "Field";
}
impl Deref for Field { impl Deref for Field {
type Target = dyn FieldTrait; type Target = dyn FieldTrait;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -223,19 +199,171 @@ impl Deref for Field {
} }
} }
} }
// impl FieldTrait for Field {
// fn spatial_ref(&self) -> &Spatial {
// match self {
// Field::Box(field) => field.spatial_ref(),
// Field::Cylinder(field) => field.spatial_ref(),
// Field::Sphere(field) => field.spatial_ref(),
// Field::Torus(field) => field.spatial_ref(),
// }
// }
// fn local_distance(&self, p: Vec3A) -> f32 {
// match self {
// Field::Box(field) => field.local_distance(p),
// Field::Cylinder(field) => field.local_distance(p),
// Field::Sphere(field) => field.local_distance(p),
// Field::Torus(field) => field.local_distance(p),
// }
// }
// fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.local_normal(p, r),
// Field::Cylinder(field) => field.local_normal(p, r),
// Field::Sphere(field) => field.local_normal(p, r),
// Field::Torus(field) => field.local_normal(p, r),
// }
// }
// fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.local_closest_point(p, r),
// Field::Cylinder(field) => field.local_closest_point(p, r),
// Field::Sphere(field) => field.local_closest_point(p, r),
// Field::Torus(field) => field.local_closest_point(p, r),
// }
// }
// fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
// match self {
// Field::Box(field) => field.distance(reference_space, p),
// Field::Cylinder(field) => field.distance(reference_space, p),
// Field::Sphere(field) => field.distance(reference_space, p),
// Field::Torus(field) => field.distance(reference_space, p),
// }
// }
// fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.normal(reference_space, p, r),
// Field::Cylinder(field) => field.normal(reference_space, p, r),
// Field::Sphere(field) => field.normal(reference_space, p, r),
// Field::Torus(field) => field.normal(reference_space, p, r),
// }
// }
// fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.closest_point(reference_space, p, r),
// Field::Cylinder(field) => field.closest_point(reference_space, p, r),
// Field::Sphere(field) => field.closest_point(reference_space, p, r),
// Field::Torus(field) => field.closest_point(reference_space, p, r),
// }
// }
// fn ray_march(&self, ray: Ray) -> RayMarchResult {
// match self {
// Field::Box(field) => field.ray_march(ray),
// Field::Cylinder(field) => field.ray_march(ray),
// Field::Sphere(field) => field.ray_march(ray),
// Field::Torus(field) => field.ray_march(ray),
// }
// }
// }
pub fn create_interface(client: &Arc<Client>) -> Result<()> { create_interface!(FieldInterface, FieldInterfaceAspect, "/field");
let node = Node::create(client, "", "field", false); pub struct FieldInterface;
node.add_local_signal("create_box_field", create_box_field_flex); impl FieldInterfaceAspect for FieldInterface {
node.add_local_signal("create_cylinder_field", create_cylinder_field_flex); fn create_box_field(
node.add_local_signal("create_sphere_field", create_sphere_field_flex); _node: Arc<Node>,
node.add_local_signal("create_torus_field", create_torus_field_flex); calling_client: Arc<Client>,
node.add_to_scenegraph().map(|_| ()) name: String,
parent: Arc<Node>,
transform: Transform,
size: mint::Vector3<f32>,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_BOX_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
BoxField::add_to(&node, size);
Ok(())
}
fn create_cylinder_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
length: f32,
radius: f32,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_CYLINDER_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
CylinderField::add_to(&node, length, radius);
Ok(())
}
fn create_sphere_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
position: mint::Vector3<f32>,
radius: f32,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_SPHERE_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(
&node,
Some(parent.clone()),
Mat4::from_translation(position.into()),
false,
);
SphereField::add_to(&node, radius);
Ok(())
}
fn create_torus_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
radius_a: f32,
radius_b: f32,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_TORUS_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
TorusField::add_to(&node, radius_a, radius_b);
Ok(())
}
} }
pub fn find_field(client: &Client, path: &str) -> Result<Arc<Field>> { pub fn find_field(client: &Client, path: &str) -> Result<Arc<Field>> {
client client.get_node("Field", path)?.get_aspect::<Field>()
.get_node("Field", path)?
.get_aspect("Field", "info", |n| &n.field)
.cloned()
} }

View File

@@ -1,12 +1,10 @@
use super::{Field, FieldTrait, Node}; use super::{Field, FieldTrait, Node, SphereFieldAspect};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, Spatial}; use crate::nodes::fields::FieldAspect;
use color_eyre::eyre::{ensure, Result}; use crate::nodes::spatial::Spatial;
use glam::{Mat4, Vec3A}; use color_eyre::eyre::Result;
use mint::Vector3; use glam::Vec3A;
use portable_atomic::AtomicF32; use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@@ -16,34 +14,19 @@ pub struct SphereField {
} }
impl SphereField { impl SphereField {
pub fn add_to(node: &Arc<Node>, radius: f32) -> Result<()> { pub fn add_to(node: &Arc<Node>, radius: f32) {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let sphere_field = SphereField { let sphere_field = SphereField {
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
radius: AtomicF32::new(radius), radius: AtomicF32::new(radius),
}; };
sphere_field.add_field_methods(node); <SphereField as FieldAspect>::add_node_members(node);
node.add_local_signal("set_radius", SphereField::set_radius_flex); <SphereField as SphereFieldAspect>::add_node_members(node);
let _ = node.field.set(Arc::new(Field::Sphere(sphere_field))); node.add_aspect(Field::Sphere(sphere_field));
Ok(())
} }
pub fn set_radius(&self, radius: f32) { pub fn set_radius(&self, radius: f32) {
self.radius.store(radius, Ordering::Relaxed); self.radius.store(radius, Ordering::Relaxed);
} }
pub fn set_radius_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
sphere_field.set_radius(deserialize(data)?);
Ok(())
}
} }
impl FieldTrait for SphereField { impl FieldTrait for SphereField {
@@ -60,29 +43,13 @@ impl FieldTrait for SphereField {
self.space.as_ref() self.space.as_ref()
} }
} }
impl SphereFieldAspect for SphereField {
pub fn create_sphere_field_flex( fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, radius: f32) -> Result<()> {
_node: &Node, let this_field = node.get_aspect::<Field>()?;
calling_client: Arc<Client>, let Field::Sphere(this_field) = &*this_field else {
data: &[u8], return Ok(());
) -> Result<()> { };
#[derive(Deserialize)] this_field.set_radius(radius);
struct CreateFieldInfo<'a> { Ok(())
name: &'a str,
parent_path: &'a str,
origin: Option<Vector3<f32>>,
radius: f32,
} }
let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = Mat4::from_translation(
info.origin
.unwrap_or_else(|| Vector3::from([0.0; 3]))
.into(),
);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
SphereField::add_to(&node, info.radius)?;
Ok(())
} }

View File

@@ -1,12 +1,10 @@
use super::{Field, FieldTrait, Node}; use super::{Field, FieldTrait, Node, TorusFieldAspect};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::fields::FieldAspect;
use color_eyre::eyre::{ensure, Result}; use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::{swizzles::*, vec2, Vec3A}; use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32; use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@@ -18,40 +16,22 @@ pub struct TorusField {
} }
impl TorusField { impl TorusField {
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) -> Result<()> { pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let torus_field = TorusField { let torus_field = TorusField {
space: node.spatial.get().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
radius_a: AtomicF32::new(radius_a.abs()), radius_a: AtomicF32::new(radius_a.abs()),
radius_b: AtomicF32::new(radius_b.abs()), radius_b: AtomicF32::new(radius_b.abs()),
}; };
torus_field.add_field_methods(node); <TorusField as FieldAspect>::add_node_members(node);
node.add_local_signal("set_size", TorusField::set_size_flex); <TorusField as TorusFieldAspect>::add_node_members(node);
let _ = node.field.set(Arc::new(Field::Torus(torus_field))); node.add_aspect(Field::Torus(torus_field));
Ok(())
} }
pub fn set_size(&self, radius_a: f32, radius_b: f32) { pub fn set_size(&self, radius_a: f32, radius_b: f32) {
self.radius_a.store(radius_a.abs(), Ordering::Relaxed); self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
self.radius_b.store(radius_b.abs(), Ordering::Relaxed); self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
} }
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (radius_a, radius_b) = deserialize(data)?;
torus_field.set_size(radius_a, radius_b);
Ok(())
}
} }
impl FieldTrait for TorusField { impl FieldTrait for TorusField {
fn local_distance(&self, p: Vec3A) -> f32 { fn local_distance(&self, p: Vec3A) -> f32 {
let radius_a = self.radius_a.load(Ordering::Relaxed); let radius_a = self.radius_a.load(Ordering::Relaxed);
@@ -63,26 +43,18 @@ impl FieldTrait for TorusField {
self.space.as_ref() self.space.as_ref()
} }
} }
impl TorusFieldAspect for TorusField {
pub fn create_torus_field_flex( fn set_size(
_node: &Node, node: Arc<Node>,
calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
radius_a: f32, radius_a: f32,
radius_b: f32, radius_b: f32,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Torus(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(radius_a, radius_b);
Ok(())
} }
let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
TorusField::add_to(&node, info.radius_a, info.radius_b)?;
Ok(())
} }

View File

@@ -7,25 +7,20 @@ use color_eyre::eyre::Result;
use glam::{vec3, Mat4}; use glam::{vec3, Mat4};
use std::sync::Arc; use std::sync::Arc;
use stereokit::StereoKitMultiThread; use stereokit::StereoKitMultiThread;
use tracing::instrument;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref HMD: Arc<Node> = create(); static ref HMD: Arc<Node> = create();
} }
fn create() -> Arc<Node> { fn create() -> Arc<Node> {
let node = Arc::new(Node::create(&INTERNAL_CLIENT, "", "hmd", false)); let node = Arc::new(Node::create_parent_name(&INTERNAL_CLIENT, "", "hmd", false));
Spatial::add_to(&node, None, Mat4::IDENTITY, false).expect("Unable to make spatial for HMD"); Spatial::add_to(&node, None, Mat4::IDENTITY, false);
node node
} }
#[instrument(level = "debug", name = "Update HMD Pose", skip(sk))]
pub fn frame(sk: &impl StereoKitMultiThread) { pub fn frame(sk: &impl StereoKitMultiThread) {
let spatial = HMD let spatial = HMD.get_aspect::<Spatial>().unwrap();
.spatial
.get()
.expect("Unable to get spatial to update HMD");
let hmd_pose = sk.input_head(); let hmd_pose = sk.input_head();
*spatial.transform.lock() = Mat4::from_scale_rotation_translation( *spatial.transform.lock() = Mat4::from_scale_rotation_translation(
vec3(1.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0),

View File

@@ -31,7 +31,7 @@ impl InputSpecialization for Hand {
} }
fn serialize( fn serialize(
&self, &self,
_distance_link: &DistanceLink, distance_link: &DistanceLink,
local_to_handler_matrix: Mat4, local_to_handler_matrix: Mat4,
) -> InputDataType { ) -> InputDataType {
let mut hand = self.base; let mut hand = self.base;
@@ -68,6 +68,10 @@ impl InputSpecialization for Hand {
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation(); let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into(); joint.position = position.into();
joint.rotation = rotation.into(); joint.rotation = rotation.into();
joint.distance = distance_link
.handler
.field
.distance(&distance_link.handler.spatial, position.into());
} }
InputDataType::Hand(Box::new(hand)) InputDataType::Hand(Box::new(hand))

View File

@@ -9,23 +9,22 @@ use self::tip::Tip;
use super::{ use super::{
alias::{Alias, AliasInfo}, alias::{Alias, AliasInfo},
fields::{find_field, Field, FIELD_ALIAS_INFO}, fields::{find_field, Field, FIELD_ALIAS_INFO},
spatial::{find_spatial_parent, parse_transform, Spatial}, spatial::{parse_transform, Spatial},
Node, Aspect, Message, Node,
}; };
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap}; use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry}; use crate::{core::registry::Registry, nodes::spatial::Transform};
use color_eyre::eyre::{ensure, Result}; use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::AtomicBool; use portable_atomic::AtomicBool;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::{flat::InputData, flex::deserialize}; use stardust_xr::schemas::{flat::InputDataType, flex::serialize};
use stardust_xr::schemas::{ use stardust_xr::{
flat::{Datamap, InputDataType}, schemas::{flat::InputData, flex::deserialize},
flex::serialize, values::Datamap,
}; };
use stardust_xr::values::Transform;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -77,11 +76,6 @@ impl InputMethod {
specialization: InputType, specialization: InputType,
datamap: Option<Datamap>, datamap: Option<Datamap>,
) -> Result<Arc<InputMethod>> { ) -> Result<Arc<InputMethod>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
node.add_local_signal("capture", InputMethod::capture_flex); node.add_local_signal("capture", InputMethod::capture_flex);
node.add_local_signal("set_datamap", InputMethod::set_datamap_flex); node.add_local_signal("set_datamap", InputMethod::set_datamap_flex);
node.add_local_signal("set_handlers", InputMethod::set_handlers_flex); node.add_local_signal("set_handlers", InputMethod::set_handlers_flex);
@@ -90,7 +84,7 @@ impl InputMethod {
node: Arc::downgrade(node), node: Arc::downgrade(node),
uid: node.uid.clone(), uid: node.uid.clone(),
enabled: Mutex::new(true), enabled: Mutex::new(true),
spatial: node.spatial.get().unwrap().clone(), spatial: node.get_aspect::<Spatial>().unwrap().clone(),
specialization: Mutex::new(specialization), specialization: Mutex::new(specialization),
captures: Registry::new(), captures: Registry::new(),
datamap: Mutex::new(datamap), datamap: Mutex::new(datamap),
@@ -99,31 +93,42 @@ impl InputMethod {
}; };
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() { for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler); method.handle_new_handler(&handler);
method.make_alias(&handler);
} }
let method = INPUT_METHOD_REGISTRY.add(method); let method = INPUT_METHOD_REGISTRY.add(method);
let _ = node.input_method.set(method.clone()); node.add_aspect_raw(method.clone());
Ok(method) Ok(method)
} }
fn get(node: &Node) -> Result<Arc<Self>> { fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Method", "input method", |n| &n.input_method) node.get_aspect::<Self>()
.cloned()
} }
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?; let method = InputMethod::get(&node)?;
let handler = InputHandler::find(&calling_client, deserialize(data)?)?; let handler = InputHandler::find(&calling_client, deserialize(message.as_ref())?)?;
method.captures.add_raw(&handler); method.captures.add_raw(&handler);
node.send_remote_signal("capture", data) node.send_remote_signal("capture", message)
} }
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_datamap_flex(
let method = InputMethod::get(node)?; node: Arc<Node>,
method.datamap.lock().replace(Datamap::new(data.to_vec())?); _calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let method = InputMethod::get(&node)?;
method
.datamap
.lock()
.replace(Datamap::from_raw(message.data)?);
Ok(()) Ok(())
} }
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_handlers_flex(
let method = InputMethod::get(node)?; node: Arc<Node>,
let handler_paths: Vec<&str> = deserialize(data)?; calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let method = InputMethod::get(&node)?;
let handler_paths: Vec<&str> = deserialize(message.as_ref())?;
let handlers: Vec<Weak<InputHandler>> = handler_paths let handlers: Vec<Weak<InputHandler>> = handler_paths
.into_iter() .into_iter()
.filter_map(|p| InputHandler::find(&calling_client, p).ok()) .filter_map(|p| InputHandler::find(&calling_client, p).ok())
@@ -137,6 +142,34 @@ impl InputMethod {
Ok(()) Ok(())
} }
fn make_alias(&self, handler: &InputHandler) {
let Some(method_node) = self.node.upgrade() else {
return;
};
let Some(handler_node) = handler.node.upgrade() else {
return;
};
let Some(client) = handler_node.get_client() else {
return;
};
let Ok(method_alias) = Alias::create(
&client,
handler_node.get_path(),
&self.uid,
&method_node,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
) else {
return;
};
method_alias.enabled.store(false, Ordering::Relaxed);
handler
.method_aliases
.add(self as *const InputMethod as usize, &method_alias);
}
fn compare_distance(&self, to: &Field) -> f32 { fn compare_distance(&self, to: &Field) -> f32 {
self.specialization self.specialization
.lock() .lock()
@@ -147,9 +180,15 @@ impl InputMethod {
} }
fn handle_new_handler(&self, handler: &InputHandler) { fn handle_new_handler(&self, handler: &InputHandler) {
let Some(method_node) = self.node.upgrade() else { return }; let Some(method_node) = self.node.upgrade() else {
let Some(method_client) = method_node.get_client() else { return }; return;
let Some(handler_node) = handler.node.upgrade() else { return }; };
let Some(method_client) = method_node.get_client() else {
return;
};
let Some(handler_node) = handler.node.upgrade() else {
return;
};
// Receiver itself // Receiver itself
let Ok(handler_alias) = Alias::create( let Ok(handler_alias) = Alias::create(
&method_client, &method_client,
@@ -157,38 +196,49 @@ impl InputMethod {
handler.uid.as_str(), handler.uid.as_str(),
&handler_node, &handler_node,
AliasInfo { AliasInfo {
server_methods: vec!["getTransform"], server_methods: vec!["get_transform"],
..Default::default() ..Default::default()
}, },
) else {return}; ) else {
return;
};
self.handler_aliases self.handler_aliases
.add(handler.uid.clone(), &handler_alias); .add(handler.uid.clone(), &handler_alias);
if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() { if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() {
// Handler's field // Handler's field
let Ok(rx_field_alias) = Alias::create( let Ok(rx_field_alias) = Alias::create(
&method_client, &method_client,
handler_alias.get_path(), handler_alias.get_path(),
"field", "field",
&handler_field_node, &handler_field_node,
FIELD_ALIAS_INFO.clone(), FIELD_ALIAS_INFO.clone(),
) else {return}; ) else {
return;
};
self.handler_aliases self.handler_aliases
.add(handler.uid.clone() + "-field", &rx_field_alias); .add(handler.uid.clone() + "-field", &rx_field_alias);
} }
let Ok(data) = serialize(&handler.uid) else {return}; let Ok(data) = serialize(&handler.uid) else {
let _ = method_node.send_remote_signal("handler_created", &data); return;
};
let _ = method_node.send_remote_signal("handler_created", data);
} }
fn handle_drop_handler(&self, handler: &InputHandler) { fn handle_drop_handler(&self, handler: &InputHandler) {
let uid = handler.uid.as_str(); let uid = handler.uid.as_str();
self.handler_aliases.remove(uid); self.handler_aliases.remove(uid);
self.handler_aliases.remove(&(uid.to_string() + "-field")); self.handler_aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {return}; let Some(tx_node) = self.node.upgrade() else {
let Ok(data) = serialize(&uid) else {return}; return;
let _ = tx_node.send_remote_signal("handler_destroyed", &data); };
let Ok(data) = serialize(&uid) else { return };
let _ = tx_node.send_remote_signal("handler_destroyed", data);
} }
} }
impl Aspect for InputMethod {
const NAME: &'static str = "InputMethod";
}
impl Drop for InputMethod { impl Drop for InputMethod {
fn drop(&mut self) { fn drop(&mut self) {
INPUT_METHOD_REGISTRY.remove(self); INPUT_METHOD_REGISTRY.remove(self);
@@ -201,32 +251,19 @@ pub struct DistanceLink {
handler: Arc<InputHandler>, handler: Arc<InputHandler>,
} }
impl DistanceLink { impl DistanceLink {
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> { fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Self {
let handler_node = handler.node.upgrade()?; DistanceLink {
let method_alias = Alias::create(
&handler_node.get_client()?,
handler_node.get_path(),
&method.uid,
&method.node.upgrade()?,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
)
.ok()?;
handler.method_aliases.add(Arc::downgrade(&method_alias));
Some(DistanceLink {
distance: method.compare_distance(&handler.field), distance: method.compare_distance(&handler.field),
method, method,
handler, handler,
}) }
} }
fn send_input(&self, order: u32, datamap: Datamap) { fn send_input(&self, order: u32, captured: bool, datamap: Datamap) {
self.handler.send_input(order, self, datamap); self.handler.send_input(order, captured, self, datamap);
} }
#[instrument(level = "debug", skip(self))] #[instrument(level = "debug", skip(self))]
fn serialize(&self, order: u32, datamap: Datamap) -> Vec<u8> { fn serialize(&self, order: u32, captured: bool, datamap: Datamap) -> Vec<u8> {
let input = self.method.specialization.lock().serialize( let input = self.method.specialization.lock().serialize(
self, self,
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)), Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
@@ -238,6 +275,7 @@ impl DistanceLink {
distance: self.method.true_distance(&self.handler.field), distance: self.method.true_distance(&self.handler.field),
datamap, datamap,
order, order,
captured,
}; };
root.serialize() root.serialize()
} }
@@ -249,44 +287,47 @@ pub struct InputHandler {
node: Weak<Node>, node: Weak<Node>,
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
field: Arc<Field>, field: Arc<Field>,
method_aliases: LifeLinkedNodeList, method_aliases: LifeLinkedNodeMap<usize>,
} }
impl InputHandler { impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> { pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let handler = InputHandler { let handler = InputHandler {
enabled: node.enabled.clone(), enabled: node.enabled.clone(),
uid: node.uid.clone(), uid: node.uid.clone(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
spatial: node.spatial.get().unwrap().clone(), spatial: node.get_aspect::<Spatial>().unwrap().clone(),
field: field.clone(), field: field.clone(),
method_aliases: LifeLinkedNodeList::default(), method_aliases: LifeLinkedNodeMap::default(),
}; };
for method in INPUT_METHOD_REGISTRY.get_valid_contents() { for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.make_alias(&handler);
method.handle_new_handler(&handler); method.handle_new_handler(&handler);
} }
let handler = INPUT_HANDLER_REGISTRY.add(handler); let handler = INPUT_HANDLER_REGISTRY.add(handler);
let _ = node.input_handler.set(handler); node.add_aspect_raw(handler);
Ok(()) Ok(())
} }
fn find(client: &Client, path: &str) -> Result<Arc<Self>> { fn find(client: &Client, path: &str) -> Result<Arc<Self>> {
InputHandler::get(&*client.get_node("Input Handler", path)?) client.get_node("Input Handler", path)?.get_aspect::<Self>()
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Handler", "input handler", |n| &n.input_handler)
.cloned()
} }
#[instrument(level = "debug", skip(self, distance_link))] #[instrument(level = "debug", skip(self, distance_link))]
fn send_input(&self, order: u32, distance_link: &DistanceLink, datamap: Datamap) { fn send_input(
let Some(node) = self.node.upgrade() else {return}; &self,
let _ = node.send_remote_signal("input", &distance_link.serialize(order, datamap)); order: u32,
captured: bool,
distance_link: &DistanceLink,
datamap: Datamap,
) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("input", distance_link.serialize(order, captured, datamap));
} }
} }
impl Aspect for InputHandler {
const NAME: &'static str = "InputHandler";
}
impl PartialEq for InputHandler { impl PartialEq for InputHandler {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.spatial == other.spatial self.spatial == other.spatial
@@ -302,7 +343,7 @@ impl Drop for InputHandler {
} }
pub fn create_interface(client: &Arc<Client>) -> Result<()> { pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "input", false); let node = Node::create_path(client, "/input", false);
node.add_local_signal("create_input_handler", create_input_handler_flex); node.add_local_signal("create_input_handler", create_input_handler_flex);
node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex); node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
node.add_local_signal("create_input_method_tip", tip::create_tip_flex); node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
@@ -310,9 +351,9 @@ pub fn create_interface(client: &Arc<Client>) -> Result<()> {
} }
pub fn create_input_handler_flex( pub fn create_input_handler_flex(
_node: &Node, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], message: Message,
) -> Result<()> { ) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreateInputHandlerInfo<'a> { struct CreateInputHandlerInfo<'a> {
@@ -321,14 +362,16 @@ pub fn create_input_handler_flex(
transform: Transform, transform: Transform,
field_path: &'a str, field_path: &'a str,
} }
let info: CreateInputHandlerInfo = deserialize(data)?; let info: CreateInputHandlerInfo = deserialize(message.as_ref())?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?; let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, true); let transform = parse_transform(info.transform, true, true, true);
let field = find_field(&calling_client, info.field_path)?; let field = find_field(&calling_client, info.field_path)?;
let node = let node = Node::create_parent_name(&calling_client, "/input/handler", info.name, true)
Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph()?; .add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?; Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputHandler::add_to(&node, &field)?; InputHandler::add_to(&node, &field)?;
Ok(()) Ok(())
} }
@@ -343,10 +386,12 @@ pub fn process_input() {
.filter(|method| method.datamap.lock().is_some()) .filter(|method| method.datamap.lock().is_some())
}); });
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents(); let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
for handler in &handlers { const LIMIT: usize = 50;
handler.method_aliases.clear();
}
for method in methods { for method in methods {
for alias in method.node.upgrade().unwrap().aliases.get_valid_contents() {
alias.enabled.store(false, Ordering::Release);
}
debug_span!("Process input method").in_scope(|| { debug_span!("Process input method").in_scope(|| {
// Get all valid input handlers and convert them to DistanceLink objects // Get all valid input handlers and convert them to DistanceLink objects
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links") let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links")
@@ -357,14 +402,16 @@ pub fn process_input() {
.iter() .iter()
.filter_map(|h| h.upgrade()) .filter_map(|h| h.upgrade())
.filter(|handler| handler.enabled.load(Ordering::Relaxed)) .filter(|handler| handler.enabled.load(Ordering::Relaxed))
.filter_map(|handler| DistanceLink::from(method.clone(), handler)) .map(|handler| DistanceLink::from(method.clone(), handler))
.collect() .collect()
} else { } else {
let mut distance_links: Vec<_> = handlers let mut distance_links: Vec<_> = handlers
.iter() .iter()
.filter(|handler| handler.enabled.load(Ordering::Relaxed)) .filter(|handler| handler.enabled.load(Ordering::Relaxed))
.filter_map(|handler| { .map(|handler| {
DistanceLink::from(method.clone(), handler.clone()) debug_span!("Create distance link").in_scope(|| {
DistanceLink::from(method.clone(), handler.clone())
})
}) })
.collect(); .collect();
@@ -375,6 +422,7 @@ pub fn process_input() {
}); });
}); });
distance_links.truncate(LIMIT);
distance_links distance_links
} }
}); });
@@ -382,11 +430,24 @@ pub fn process_input() {
let captures = method.captures.take_valid_contents(); let captures = method.captures.take_valid_contents();
// Iterate over the distance links and send input to them // Iterate over the distance links and send input to them
for (i, distance_link) in distance_links.into_iter().enumerate() { for (i, distance_link) in distance_links.into_iter().enumerate() {
distance_link.send_input(i as u32, method.datamap.lock().clone().unwrap()); if let Some(method_alias) = distance_link
.handler
.method_aliases
.get(&(Arc::as_ptr(&distance_link.method) as usize))
.and_then(|a| a.get_aspect::<Alias>().ok())
{
method_alias.enabled.store(true, Ordering::Release);
}
let captured = captures.contains(&distance_link.handler);
distance_link.send_input(
i as u32,
captured,
method.datamap.lock().clone().unwrap(),
);
// If the current distance link is in the list of captured input handlers, // If the current distance link is in the list of captured input handlers,
// break out of the loop to avoid sending input to the remaining distance links // break out of the loop to avoid sending input to the remaining distance links
if captures.contains(&distance_link.handler) { if captured {
break; break;
} }
} }

View File

@@ -2,13 +2,14 @@ use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::fields::{Field, Ray, RayMarchResult}; use crate::nodes::fields::{Field, Ray, RayMarchResult};
use crate::nodes::input::{InputMethod, InputType}; use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::Node; use crate::nodes::{Message, Node};
use glam::{vec3, Mat4}; use glam::{vec3, Mat4};
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType, Pointer as FlatPointer}; use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform; use stardust_xr::values::Datamap;
use std::sync::Arc; use std::sync::Arc;
#[derive(Default)] #[derive(Default)]
@@ -34,9 +35,13 @@ impl Pointer {
impl InputSpecialization for Pointer { impl InputSpecialization for Pointer {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 { fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field); let ray_info = self.ray_march(space, field);
ray_info if ray_info.min_distance > 0.0 {
.deepest_point_distance ray_info.deepest_point_distance + 1000.0
.hypot(ray_info.min_distance.recip()) } else {
ray_info
.deepest_point_distance
.hypot(0.001 / ray_info.min_distance)
}
} }
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 { fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field); let ray_info = self.ray_march(space, field);
@@ -61,9 +66,9 @@ impl InputSpecialization for Pointer {
} }
pub fn create_pointer_flex( pub fn create_pointer_flex(
_node: &Node, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], message: Message,
) -> color_eyre::eyre::Result<()> { ) -> color_eyre::eyre::Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreatePointerInfo<'a> { struct CreatePointerInfo<'a> {
@@ -72,17 +77,20 @@ pub fn create_pointer_flex(
transform: Transform, transform: Transform,
datamap: Option<Vec<u8>>, datamap: Option<Vec<u8>>,
} }
let info: CreatePointerInfo = deserialize(data)?; let info: CreatePointerInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true); let node = Node::create_parent_name(&calling_client, "/input/method/pointer", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false); let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?; Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputMethod::add_to( InputMethod::add_to(
&node, &node,
InputType::Pointer(Pointer), InputType::Pointer(Pointer),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()), info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
)?; )?;
Ok(()) Ok(())
} }

View File

@@ -2,14 +2,15 @@ use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::fields::Field; use crate::nodes::fields::Field;
use crate::nodes::input::{InputMethod, InputType}; use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::Node; use crate::nodes::{Message, Node};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec3a, Mat4}; use glam::{vec3a, Mat4};
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip}; use stardust_xr::schemas::flat::{InputDataType, Tip as FlatTip};
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform; use stardust_xr::values::Datamap;
use std::sync::Arc; use std::sync::Arc;
#[derive(Default)] #[derive(Default)]
@@ -17,9 +18,10 @@ pub struct Tip {
pub radius: f32, pub radius: f32,
} }
impl Tip { impl Tip {
fn set_radius(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
if let InputType::Tip(tip) = &mut *node.input_method.get().unwrap().specialization.lock() { let input_method = node.get_aspect::<InputMethod>()?;
tip.radius = deserialize(data)?; if let InputType::Tip(tip) = &mut *input_method.specialization.lock() {
tip.radius = deserialize(message.as_ref())?;
} }
Ok(()) Ok(())
} }
@@ -45,7 +47,11 @@ impl InputSpecialization for Tip {
} }
} }
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { pub fn create_tip_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreateTipInfo<'a> { struct CreateTipInfo<'a> {
name: &'a str, name: &'a str,
@@ -54,19 +60,22 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
radius: f32, radius: f32,
datamap: Option<Vec<u8>>, datamap: Option<Vec<u8>>,
} }
let info: CreateTipInfo = deserialize(data)?; let info: CreateTipInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/input/method/tip", info.name, true); let node = Node::create_parent_name(&calling_client, "/input/method/tip", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false); let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?; Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputMethod::add_to( InputMethod::add_to(
&node, &node,
InputType::Tip(Tip { InputType::Tip(Tip {
radius: info.radius, radius: info.radius,
}), }),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()), info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
)?; )?;
node.add_local_signal("set_radius", Tip::set_radius); node.add_local_signal("set_radius", Tip::set_radius);
Ok(()) Ok(())

193
src/nodes/items/camera.rs Normal file
View File

@@ -0,0 +1,193 @@
use super::{Item, ItemType};
use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
scenegraph::MethodResponseSender,
},
nodes::{
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
items::TypeInfo,
spatial::{parse_transform, Spatial, Transform},
Message, Node,
},
};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::{RowMatrix4, Vector2};
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
use stereokit::{
Color128, Material, Rect, RenderLayer, StereoKitDraw, Tex, TextureType, Transparency,
};
lazy_static! {
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
type_name: "camera",
aliased_local_signals: vec!["apply_preview_material", "frame"],
aliased_local_methods: vec![],
aliased_remote_signals: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
struct FrameInfo {
proj_matrix: Mat4,
px_size: Vector2<u32>,
}
pub struct CameraItem {
space: Arc<Spatial>,
frame_info: Mutex<FrameInfo>,
sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<Material>>,
applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>,
}
impl CameraItem {
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
Item::add_to(
node,
nanoid!(),
&ITEM_TYPE_INFO_CAMERA,
ItemType::Camera(CameraItem {
space: node.get_aspect::<Spatial>().unwrap().clone(),
frame_info: Mutex::new(FrameInfo {
proj_matrix,
px_size,
}),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
applied_to: Registry::new(),
apply_to: Registry::new(),
}),
);
node.add_local_method("frame", CameraItem::frame_flex);
node.add_local_signal(
"apply_preview_material",
CameraItem::apply_preview_material_flex,
);
}
fn frame_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else {
return Err(eyre!("Wrong item type?"));
};
Ok(serialize(())?.into())
});
}
fn apply_preview_material_flex(
node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
bail!("Wrong item type?")
};
let model_part_node =
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
let model_part = model_part_node.get_aspect::<ModelPart>()?;
camera.applied_to.add_raw(&model_part);
camera.apply_to.add_raw(&model_part);
Ok(())
}
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize(id)?.into())
}
pub fn update(&self, sk: &impl StereoKitDraw) {
let frame_info = self.frame_info.lock();
let sk_tex = self.sk_tex.get_or_init(|| {
sk.tex_gen_color(
Color128::default(),
frame_info.px_size.x as i32,
frame_info.px_size.y as i32,
TextureType::RENDER_TARGET,
stereokit::TextureFormat::RGBA32Linear,
)
});
let sk_mat = self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&UNLIT_SHADER_BYTES).unwrap();
let mat = sk.material_create(&shader);
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
sk.material_set_transparency(&mat, Transparency::Blend);
Arc::new(mat)
});
for model_part in self.apply_to.take_valid_contents() {
model_part.replace_material(sk_mat.clone())
}
if !self.applied_to.is_empty() {
sk.render_to(
sk_tex,
frame_info.proj_matrix,
self.space.global_transform(),
RenderLayer::all(),
stereokit::RenderClear::All,
Rect {
x: 0.0,
y: 0.0,
w: 0.0,
h: 0.0,
},
);
}
}
}
pub fn update(sk: &impl StereoKitDraw) {
for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() {
let ItemType::Camera(camera) = &camera.specialization else {
continue;
};
camera.update(sk);
}
}
pub(super) fn create_camera_item_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateCameraItemInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
proj_matrix: RowMatrix4<f32>,
px_size: Vector2<u32>,
}
let info: CreateCameraItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_CAMERA.type_name);
let space = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false);
let node = Node::create_parent_name(&INTERNAL_CLIENT, &parent_name, info.name, false)
.add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false);
CameraItem::add_to(&node, info.proj_matrix.into(), info.px_size);
node.get_aspect::<Item>().unwrap().make_alias_named(
&calling_client,
&parent_name,
info.name,
)?;
Ok(())
}

View File

@@ -1,23 +1,21 @@
use super::{Item, ItemSpecialization, ItemType}; use super::{Item, ItemType};
use crate::{ use crate::{
core::{ core::{
client::{Client, INTERNAL_CLIENT}, client::{Client, INTERNAL_CLIENT},
registry::Registry, registry::Registry,
scenegraph::MethodResponseSender,
}, },
nodes::{ nodes::{
items::TypeInfo, items::TypeInfo,
spatial::{find_spatial_parent, parse_transform, Spatial}, spatial::{parse_transform, Spatial, Transform},
Node, Message, Node,
}, },
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use nanoid::nanoid; use nanoid::nanoid;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::{ use stardust_xr::schemas::flex::{deserialize, serialize};
schemas::flex::{deserialize, flexbuffers, serialize},
values::Transform,
};
use std::sync::Arc; use std::sync::Arc;
lazy_static! { lazy_static! {
@@ -46,23 +44,31 @@ impl EnvironmentItem {
node.add_local_method("get_path", EnvironmentItem::get_path_flex); node.add_local_method("get_path", EnvironmentItem::get_path_flex);
} }
fn get_path_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<Vec<u8>> { fn get_path_flex(
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else { node: Arc<Node>,
return Err(eyre!("Wrong item type?")) _calling_client: Arc<Client>,
}; _message: Message,
Ok(flexbuffers::singleton(environment_item.path.as_str())) response: MethodResponseSender,
) {
response.wrap_sync(move || {
let ItemType::Environment(environment_item) =
&node.get_aspect::<Item>().unwrap().specialization
else {
return Err(eyre!("Wrong item type?"));
};
Ok(serialize(environment_item.path.as_str())?.into())
});
} }
}
impl ItemSpecialization for EnvironmentItem { pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
fn serialize_start_data(&self, id: &str) -> Vec<u8> { Ok(serialize((id, self.path.as_str()))?.into())
serialize((id, self.path.as_str())).unwrap()
} }
} }
pub(super) fn create_environment_item_flex( pub(super) fn create_environment_item_flex(
_node: &Node, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], message: Message,
) -> Result<()> { ) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreateEnvironmentItemInfo<'a> { struct CreateEnvironmentItemInfo<'a> {
@@ -71,18 +77,21 @@ pub(super) fn create_environment_item_flex(
transform: Transform, transform: Transform,
item_data: String, item_data: String,
} }
let info: CreateEnvironmentItemInfo = deserialize(data)?; let info: CreateEnvironmentItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name); let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let space = find_spatial_parent(&calling_client, info.parent_path)?; let space = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false); let transform = parse_transform(info.transform, true, true, false);
let node = let node = Node::create_parent_name(&INTERNAL_CLIENT, &parent_name, info.name, false)
Node::create(&INTERNAL_CLIENT, &parent_name, info.name, false).add_to_scenegraph()?; .add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false)?; Spatial::add_to(&node, None, transform * space.global_transform(), false);
EnvironmentItem::add_to(&node, info.item_data); EnvironmentItem::add_to(&node, info.item_data);
node.item node.get_aspect::<Item>().unwrap().make_alias_named(
.get() &calling_client,
.unwrap() &parent_name,
.make_alias_named(&calling_client, &parent_name, info.name)?; info.name,
)?;
Ok(()) Ok(())
} }

View File

@@ -1,26 +1,28 @@
pub mod camera;
mod environment; mod environment;
pub mod panel;
use self::camera::CameraItem;
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT}; use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL};
use super::fields::Field; use super::fields::Field;
use super::spatial::{find_spatial_parent, parse_transform, Spatial}; use super::spatial::{parse_transform, Spatial};
use super::{Alias, Node}; use super::{Alias, Aspect, Message, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo; use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field; use crate::nodes::fields::find_field;
#[cfg(feature = "wayland")] use crate::nodes::spatial::Transform;
use crate::wayland::panel_item::{PanelItem, ITEM_TYPE_INFO_PANEL};
use color_eyre::eyre::{ensure, eyre, Result}; use color_eyre::eyre::{ensure, eyre, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use nanoid::nanoid; use nanoid::nanoid;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::Ordering; use portable_atomic::Ordering;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
lazy_static! { lazy_static! {
@@ -38,8 +40,8 @@ lazy_static! {
} }
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) { pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() { if item.captured_acceptor.lock().strong_count() > 0 {
release(item, Some(&acceptor)); release(item);
} }
*item.captured_acceptor.lock() = Arc::downgrade(acceptor); *item.captured_acceptor.lock() = Arc::downgrade(acceptor);
acceptor.handle_capture(item); acceptor.handle_capture(item);
@@ -47,9 +49,9 @@ pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
ui.handle_capture_item(item, acceptor); ui.handle_capture_item(item, acceptor);
} }
} }
fn release(item: &Item, acceptor: Option<&ItemAcceptor>) { fn release(item: &Item) {
let mut captured_acceptor = item.captured_acceptor.lock(); let mut captured_acceptor = item.captured_acceptor.lock();
if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) { if let Some(acceptor) = captured_acceptor.upgrade().as_ref() {
*captured_acceptor = Weak::default(); *captured_acceptor = Weak::default();
acceptor.handle_release(item); acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() { if let Some(ui) = item.type_info.ui.lock().upgrade() {
@@ -106,17 +108,17 @@ impl Item {
if let Some(ui) = type_info.ui.lock().upgrade() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item); ui.handle_create_item(&item);
} }
let _ = node.item.set(item.clone()); node.add_aspect_raw(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
} }
@@ -154,43 +156,54 @@ impl Item {
self.make_alias_named(client, parent, &self.uid) self.make_alias_named(client, parent, &self.uid)
} }
fn release_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { fn release_flex(
let item = node.get_aspect("Item", "item", |n| &n.item)?; node: Arc<Node>,
release(item, None); _calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let item = node.get_aspect::<Item>()?;
release(&item);
Ok(()) Ok(())
} }
} }
impl Aspect for Item {
const NAME: &'static str = "Item";
}
impl Drop for Item { impl Drop for Item {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.items.remove(self); self.type_info.items.remove(self);
release(self, None); release(self);
if let Some(ui) = self.type_info.ui.lock().upgrade() { if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_item(self); ui.handle_destroy_item(self);
} }
} }
} }
pub trait ItemSpecialization {
fn serialize_start_data(&self, id: &str) -> Vec<u8>;
}
pub enum ItemType { pub enum ItemType {
Camera(CameraItem),
Environment(EnvironmentItem), Environment(EnvironmentItem),
#[cfg(feature = "wayland")] Panel(Arc<dyn PanelItemTrait>),
Panel(Arc<PanelItem>),
} }
impl Deref for ItemType { impl ItemType {
type Target = dyn ItemSpecialization; fn serialize_start_data(&self, id: &str) -> Result<Message> {
fn deref(&self) -> &Self::Target {
match self { match self {
ItemType::Environment(item) => item, ItemType::Camera(c) => c.serialize_start_data(id),
#[cfg(feature = "wayland")] ItemType::Environment(e) => e.serialize_start_data(id),
ItemType::Panel(item) => item.as_ref(), ItemType::Panel(p) => p.serialize_start_data(id),
} }
} }
} }
// impl Deref for ItemType {
// type Target = dyn ItemSpecialization;
// fn deref(&self) -> &Self::Target {
// match self {
// ItemType::Environment(item) => item,
// ItemType::Panel(item) => item.as_ref(),
// }
// }
// }
pub struct ItemUI { pub struct ItemUI {
node: Weak<Node>, node: Weak<Node>,
@@ -214,7 +227,7 @@ impl ItemUI {
acceptor_field_aliases: Default::default(), acceptor_field_aliases: Default::default(),
}); });
*type_info.ui.lock() = Arc::downgrade(&ui); *type_info.ui.lock() = Arc::downgrade(&ui);
let _ = node.item_ui.set(ui.clone()); node.add_aspect_raw(ui.clone());
for item in type_info.items.get_valid_contents() { for item in type_info.items.get_valid_contents() {
ui.handle_create_item(&item); ui.handle_create_item(&item);
@@ -225,58 +238,78 @@ impl ItemUI {
Ok(()) Ok(())
} }
fn send_state(&self, state: &str, name: &str) { fn send_state(&self, state: &str, name: &str) {
let Ok(serialized_data) = serialize(name) else {
return;
};
let _ = self let _ = self
.node .node
.upgrade() .upgrade()
.unwrap() .unwrap()
.send_remote_signal(state, flexbuffers::singleton(name).as_slice()); .send_remote_signal(state, serialized_data);
} }
fn handle_create_item(&self, item: &Item) { fn handle_create_item(&self, item: &Item) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
let Some(client) = node.get_client() else { return }; return;
};
let Some(client) = node.get_client() else {
return;
};
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) { if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
self.item_aliases.add(item.uid.clone(), &alias_node); self.item_aliases.add(item.uid.clone(), &alias_node);
} }
let _ = node.send_remote_signal( let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
"create_item", return;
&item.specialization.serialize_start_data(&item.uid), };
); let _ = node.send_remote_signal("create_item", serialized_data);
} }
fn handle_destroy_item(&self, item: &Item) { fn handle_destroy_item(&self, item: &Item) {
self.item_aliases.remove(&item.uid); self.item_aliases.remove(&item.uid);
self.send_state("destroy_item", item.uid.as_str()); self.send_state("destroy_item", item.uid.as_str());
} }
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) { fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal( let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
"capture_item", return;
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(), };
); let _ = node.send_remote_signal("capture_item", message);
} }
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) { fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal( let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
"release_item", return;
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(), };
); let _ = node.send_remote_signal("release_item", message);
} }
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) { fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
let Some(client) = node.get_client() else { return }; return;
};
let Some(client) = node.get_client() else {
return;
};
let Ok((alias, field_alias)) = acceptor.make_aliases( let Ok((alias, field_alias)) = acceptor.make_aliases(
&client, &client,
&format!("/item/{}/acceptor", self.type_info.type_name), &format!("/item/{}/acceptor", self.type_info.type_name),
) else {return}; ) else {
return;
};
self.acceptor_aliases.add(acceptor.uid.clone(), &alias); self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
self.acceptor_field_aliases self.acceptor_field_aliases
.add(acceptor.uid.clone(), &field_alias); .add(acceptor.uid.clone(), &field_alias);
let _ = node.send_remote_signal("create_acceptor", &serialize(&acceptor.uid).unwrap()); let Ok(message) = serialize(&acceptor.uid) else {
return;
};
let _ = node.send_remote_signal("create_acceptor", message);
} }
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) { fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
self.send_state("destroy_acceptor", acceptor.uid.as_str()); self.send_state("destroy_acceptor", acceptor.uid.as_str());
@@ -284,6 +317,9 @@ impl ItemUI {
self.acceptor_field_aliases.remove(&acceptor.uid); self.acceptor_field_aliases.remove(&acceptor.uid);
} }
} }
impl Aspect for ItemUI {
const NAME: &'static str = "Item";
}
impl Drop for ItemUI { impl Drop for ItemUI {
fn drop(&mut self) { fn drop(&mut self) {
*self.type_info.ui.lock() = Weak::new(); *self.type_info.ui.lock() = Weak::new();
@@ -312,19 +348,19 @@ impl ItemAcceptor {
if let Some(ui) = type_info.ui.lock().upgrade() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor); ui.handle_create_acceptor(&acceptor);
} }
let _ = node.item_acceptor.set(acceptor); node.add_aspect_raw(acceptor);
} }
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
if !node.enabled.load(Ordering::Relaxed) { if !node.enabled.load(Ordering::Relaxed) {
return Ok(()); return Ok(());
} }
let acceptor = node.item_acceptor.get().unwrap(); let acceptor = node.get_aspect::<ItemAcceptor>().unwrap();
let item_path: &str = deserialize(data)?; let item_path: &str = deserialize(message.as_ref())?;
let item_node = calling_client.get_node("Item", item_path)?; let item_node = calling_client.get_node("Item", item_path)?;
let item = item_node.get_aspect("Item", "item", |n| &n.item)?; let item = item_node.get_aspect::<Item>()?;
capture(item, acceptor); capture(&item, &acceptor);
Ok(()) Ok(())
} }
@@ -353,31 +389,44 @@ impl ItemAcceptor {
Ok((acceptor_alias, acceptor_field_alias)) Ok((acceptor_alias, acceptor_field_alias))
} }
fn handle_capture(&self, item: &Arc<Item>) { fn handle_capture(&self, item: &Arc<Item>) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
let Some(client) = node.get_client() else { return }; return;
};
let Some(client) = node.get_client() else {
return;
};
self.accepted_registry.add_raw(item); self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) { if let Ok(alias_node) = item.make_alias(&client, &node.path) {
self.accepted_aliases.add(item.uid.clone(), &alias_node); self.accepted_aliases.add(item.uid.clone(), &alias_node);
} }
let _ = node.send_remote_signal(
"capture", let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
&item.specialization.serialize_start_data(&item.uid), return;
); };
let _ = node.send_remote_signal("capture", serialized_data);
} }
fn handle_release(&self, item: &Item) { fn handle_release(&self, item: &Item) {
let Some(node) = self.node.upgrade() else { return }; let Some(node) = self.node.upgrade() else {
return;
};
self.accepted_registry.remove(item); self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid); self.accepted_aliases.remove(&item.uid);
let _ = node.send_remote_signal("release", &serialize(&item.uid).unwrap()); let Ok(message) = serialize(&item.uid) else {
return;
};
let _ = node.send_remote_signal("release", message);
} }
} }
impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor";
}
impl Drop for ItemAcceptor { impl Drop for ItemAcceptor {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.acceptors.remove(self); self.type_info.acceptors.remove(self);
for item in self.accepted_registry.get_valid_contents() { for item in self.accepted_registry.get_valid_contents() {
release(&item, Some(self)); release(&item);
} }
if let Some(ui) = self.type_info.ui.lock().upgrade() { if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_acceptor(self); ui.handle_destroy_acceptor(self);
@@ -386,7 +435,8 @@ impl Drop for ItemAcceptor {
} }
pub fn create_interface(client: &Arc<Client>) -> Result<()> { pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "item", false); let node = Node::create_parent_name(client, "", "item", false);
node.add_local_signal("create_camera_item", camera::create_camera_item_flex);
node.add_local_signal( node.add_local_signal(
"create_environment_item", "create_environment_item",
environment::create_environment_item_flex, environment::create_environment_item_flex,
@@ -405,20 +455,28 @@ fn type_info(name: &str) -> Result<&'static TypeInfo> {
} }
} }
pub fn register_item_ui_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { pub fn register_item_ui_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct RegisterItemUIInfo<'a> { struct RegisterItemUIInfo<'a> {
item_type: &'a str, item_type: &'a str,
} }
let info: RegisterItemUIInfo = deserialize(data)?; let info: RegisterItemUIInfo = deserialize(message.as_ref())?;
let type_info = type_info(info.item_type)?; let type_info = type_info(info.item_type)?;
let ui = let ui = Node::create_parent_name(&calling_client, "/item", type_info.type_name, true)
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?; .add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?; ItemUI::add_to(&ui, type_info)?;
Ok(()) Ok(())
} }
fn create_item_acceptor_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn create_item_acceptor_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreateItemAcceptorInfo<'a> { struct CreateItemAcceptorInfo<'a> {
name: &'a str, name: &'a str,
@@ -427,20 +485,22 @@ fn create_item_acceptor_flex(_node: &Node, calling_client: Arc<Client>, data: &[
field_path: &'a str, field_path: &'a str,
item_type: &'a str, item_type: &'a str,
} }
let info: CreateItemAcceptorInfo = deserialize(data)?; let info: CreateItemAcceptorInfo = deserialize(message.as_ref())?;
let space = find_spatial_parent(&calling_client, info.parent_path)?; let space = calling_client
.get_node("Reference space", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false); let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?; let field = find_field(&calling_client, info.field_path)?;
let type_info = type_info(info.item_type)?; let type_info = type_info(info.item_type)?;
let node = Node::create( let node = Node::create_parent_name(
&calling_client, &calling_client,
&format!("/item/{}/acceptor", type_info.type_name), &format!("/item/{}/acceptor", type_info.type_name),
info.name, info.name,
true, true,
) )
.add_to_scenegraph()?; .add_to_scenegraph()?;
Spatial::add_to(&node, Some(space), transform, false)?; Spatial::add_to(&node, Some(space.clone()), transform, false);
ItemAcceptor::add_to(&node, type_info, field); ItemAcceptor::add_to(&node, type_info, field);
Ok(()) Ok(())
} }

597
src/nodes/items/panel.rs Normal file
View File

@@ -0,0 +1,597 @@
use crate::{
core::{
client::{get_env, state, Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo},
spatial::Spatial,
Message, Node,
},
};
use color_eyre::eyre::{eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use nanoid::nanoid;
use rustc_hash::FxHashMap;
use serde::{
de::{Deserializer, Error, SeqAccess, Visitor},
ser::Serializer,
Deserialize, Serialize,
};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak};
use tracing::debug;
lazy_static! {
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel",
aliased_local_signals: vec![
"apply_surface_material",
"close_toplevel",
"auto_size_toplevel",
"set_toplevel_size",
"set_toplevel_focused_visuals",
"pointer_motion",
"pointer_button",
"pointer_scroll",
"keyboard_keymap",
"keyboard_key",
"touch_down",
"touch_move",
"touch_up",
"reset_touches",
],
aliased_local_methods: vec![],
aliased_remote_signals: vec![
"toplevel_parent_changed",
"toplevel_title_changed",
"toplevel_app_id_changed",
"toplevel_fullscreen_active",
"toplevel_move_request",
"toplevel_resize_request",
"toplevel_size_changed",
"set_cursor",
"new_child",
"reposition_child",
"drop_child",
],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
/// An ID for a surface inside this panel item
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum SurfaceID {
Cursor,
Toplevel,
Child(String),
}
impl Default for SurfaceID {
fn default() -> Self {
Self::Toplevel
}
}
impl<'de> serde::Deserialize<'de> for SurfaceID {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_seq(SurfaceIDVisitor)
}
}
struct SurfaceIDVisitor;
impl<'de> Visitor<'de> for SurfaceIDVisitor {
type Value = SurfaceID;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("idk")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let Some(discrim) = seq.next_element()? else {
return Err(A::Error::missing_field("discrim"));
};
// idk if you wanna check for extraneous elements
// I didn't bother
match discrim {
"Cursor" => Ok(SurfaceID::Cursor),
"Toplevel" => Ok(SurfaceID::Toplevel),
"Child" => {
let Some(text) = seq.next_element()? else {
return Err(A::Error::missing_field("child_text"));
};
Ok(SurfaceID::Child(text))
}
_ => Err(A::Error::unknown_variant(
discrim,
&["Cursor", "Toplevel", "Child"],
)),
}
}
}
impl serde::Serialize for SurfaceID {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Cursor => ["Cursor"].serialize(serializer),
Self::Toplevel => ["Toplevel"].serialize(serializer),
Self::Child(text) => ["Child", text].serialize(serializer),
}
}
}
/// The origin and size of the surface's "solid" part.
#[derive(Debug, Serialize, Clone, Copy)]
pub struct Geometry {
pub origin: Vector2<i32>,
pub size: Vector2<u32>,
}
/// The state of the panel item's toplevel.
#[derive(Debug, Clone, Serialize)]
pub struct ToplevelInfo {
/// The UID of the panel item of the parent of this toplevel, if it exists
pub parent: Option<String>,
/// Equivalent to the window title
pub title: Option<String>,
/// Application identifier, see <https://standards.freedesktop.org/desktop-entry-spec/>
pub app_id: Option<String>,
/// Current size in pixels
pub size: Vector2<u32>,
/// Recommended minimum size in pixels
pub min_size: Option<Vector2<u32>>,
/// Recommended maximum size in pixels
pub max_size: Option<Vector2<u32>>,
/// Surface geometry
pub logical_rectangle: Geometry,
}
/// Data on positioning a child
#[derive(Debug, Clone, Serialize)]
pub struct ChildInfo {
pub parent: SurfaceID,
pub geometry: Geometry,
}
/// The init data for the panel item.
#[derive(Debug, Clone, Serialize)]
pub struct PanelItemInitData {
/// The cursor, if applicable.
pub cursor: Option<Geometry>,
/// Size of the toplevel surface in pixels.
pub toplevel: ToplevelInfo,
/// Vector of childs that already exist
pub children: FxHashMap<String, ChildInfo>,
/// The surface, if any, that has exclusive input to the pointer.
pub pointer_grab: Option<SurfaceID>,
/// The surface, if any, that has exclusive input to the keyboard.
pub keyboard_grab: Option<SurfaceID>,
}
pub trait Backend: Send + Sync + 'static {
fn start_data(&self) -> Result<PanelItemInitData>;
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>);
fn close_toplevel(&self);
fn auto_size_toplevel(&self);
fn set_toplevel_size(&self, size: Vector2<u32>);
fn set_toplevel_focused_visuals(&self, focused: bool);
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>);
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool);
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
);
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>);
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>);
fn touch_move(&self, id: u32, position: Vector2<f32>);
fn touch_up(&self, id: u32);
fn reset_touches(&self);
}
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
let ItemType::Panel(panel_item) = &node.get_aspect::<Item>().ok()?.specialization else {
return None;
};
Some(panel_item.clone())
}
pub trait PanelItemTrait: Backend + Send + Sync + 'static {
fn uid(&self) -> &str;
fn serialize_start_data(&self, id: &str) -> Result<Message>;
}
pub struct PanelItem<B: Backend + ?Sized> {
pub uid: String,
node: Weak<Node>,
pub backend: Box<B>,
}
impl<B: Backend + ?Sized> PanelItem<B> {
pub fn create(backend: Box<B>, pid: Option<i32>) -> Arc<PanelItem<B>> {
debug!(?pid, "Create panel item");
let startup_settings = pid
.and_then(|pid| get_env(pid).ok())
.and_then(|env| state(&env));
let uid = nanoid!();
let node = Node::create_parent_name(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
.add_to_scenegraph()
.unwrap();
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(startup_settings.root);
}
let panel_item = Arc::new(PanelItem {
uid: uid.clone(),
node: Arc::downgrade(&node),
backend,
});
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item),
);
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("auto_size_toplevel", Self::auto_size_toplevel_flex);
node.add_local_signal("set_toplevel_size", Self::set_toplevel_size_flex);
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
node.add_local_signal("pointer_button", Self::pointer_button_flex);
node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex);
node.add_local_signal("keyboard_key", Self::keyboard_keys_flex);
node.add_local_signal("touch_down", Self::touch_down_flex);
node.add_local_signal("touch_move", Self::touch_move_flex);
node.add_local_signal("touch_up", Self::touch_up_flex);
node.add_local_signal("reset_touches", Self::reset_touches_flex);
panel_item
}
pub fn drop_toplevel(&self) {
let Some(node) = self.node.upgrade() else {
return;
};
node.destroy();
}
}
// Remote signals
impl<B: Backend + ?Sized> PanelItem<B> {
pub fn toplevel_parent_changed(&self, parent: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_parent_changed", serialize(parent).unwrap());
}
pub fn toplevel_title_changed(&self, title: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_title_changed", serialize(title).unwrap());
}
pub fn toplevel_app_id_changed(&self, app_id: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_app_id_changed", serialize(app_id).unwrap());
}
pub fn toplevel_fullscreen_active(&self, active: bool) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_fullscreen_active", serialize(active).unwrap());
}
pub fn toplevel_move_request(&self) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_move_request", Vec::<u8>::new());
}
pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal(
"toplevel_resize_request",
serialize((up, down, left, right)).unwrap(),
);
}
pub fn toplevel_size_changed(&self, size: Vector2<u32>) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_size_changed", serialize(size).unwrap());
}
pub fn set_cursor(&self, geometry: Option<Geometry>) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("set_cursor", serialize(geometry).unwrap());
}
pub fn new_child(&self, uid: &str, info: ChildInfo) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("new_child", serialize((uid, info)).unwrap());
}
pub fn reposition_child(&self, uid: &str, geometry: Geometry) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("reposition_child", serialize((uid, geometry)).unwrap());
}
pub fn drop_child(&self, uid: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("drop_child", serialize(uid).unwrap());
}
}
// Local signals
macro_rules! flex_no_args {
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.$trait_fn();
Ok(())
}
};
}
macro_rules! flex_deserialize {
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.$trait_fn(deserialize(message.as_ref())?);
Ok(())
}
};
}
impl<B: Backend + ?Sized> PanelItem<B> {
fn apply_surface_material_flex(
node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
#[derive(Debug, Deserialize)]
struct SurfaceMaterialInfo<'a> {
surface: SurfaceID,
model_node_path: &'a str,
}
let info: SurfaceMaterialInfo = deserialize(message.as_ref())?;
let model_node = calling_client
.scenegraph
.get_node(info.model_node_path)
.ok_or_else(|| eyre!("Model node not found"))?;
let model_part = model_node.get_aspect::<ModelPart>()?;
debug!(?info, "Apply surface material");
panel_item.apply_surface_material(info.surface, &model_part);
Ok(())
}
flex_no_args!(close_toplevel_flex, close_toplevel);
flex_no_args!(auto_size_toplevel_flex, auto_size_toplevel);
flex_deserialize!(set_toplevel_size_flex, set_toplevel_size);
fn pointer_motion_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (surface_id, position): (SurfaceID, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?surface_id, ?position, "Pointer deactivate");
panel_item.pointer_motion(&surface_id, position);
Ok(())
}
fn pointer_button_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(?surface_id, button, state, "Pointer button");
panel_item.pointer_button(&surface_id, button, state != 0);
Ok(())
}
fn pointer_scroll_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
#[derive(Debug, Deserialize)]
struct PointerScrollInfo {
surface_id: SurfaceID,
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
}
let info: PointerScrollInfo = deserialize(message.as_ref())?;
debug!(?info, "Pointer scroll");
panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete);
Ok(())
}
fn keyboard_keys_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (surface_id, keymap_id, keys): (SurfaceID, &str, Vec<i32>) =
deserialize(message.as_ref())?;
debug!(?keys, "Set keyboard key state");
panel_item.keyboard_keys(&surface_id, keymap_id, keys);
Ok(())
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else {
return;
};
let Ok(message) = serialize(sid) else { return };
let _ = node.send_remote_signal("grab_keyboard", message);
}
fn touch_down_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (surface_id, id, position): (SurfaceID, u32, Vector2<f32>) =
deserialize(message.as_ref())?;
debug!(?surface_id, id, ?position, "Touch down");
panel_item.touch_down(&surface_id, id, position);
Ok(())
}
fn touch_move_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (id, position): (u32, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?position, "Touch move");
panel_item.touch_move(id, position);
Ok(())
}
flex_deserialize!(touch_up_flex, touch_up);
flex_no_args!(reset_touches_flex, reset_touches);
}
impl<B: Backend + ?Sized> PanelItemTrait for PanelItem<B> {
fn uid(&self) -> &str {
&self.uid
}
fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize((id, self.start_data()?))?.into())
}
}
impl<B: Backend + ?Sized> Backend for PanelItem<B> {
fn start_data(&self) -> Result<PanelItemInitData> {
self.backend.start_data()
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
self.backend.apply_surface_material(surface, model_part)
}
fn close_toplevel(&self) {
self.backend.close_toplevel()
}
fn auto_size_toplevel(&self) {
self.backend.auto_size_toplevel()
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
self.backend.set_toplevel_size(size)
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
self.backend.set_toplevel_focused_visuals(focused)
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
self.backend.pointer_motion(surface, position)
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
self.backend.pointer_button(surface, button, pressed)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
self.backend
.pointer_scroll(surface, scroll_distance, scroll_steps)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
self.backend.keyboard_keys(surface, keymap_id, keys)
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
self.backend.touch_down(surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.backend.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.backend.touch_up(id)
}
fn reset_touches(&self) {
self.backend.reset_touches()
}
}
impl<B: Backend + ?Sized> Drop for PanelItem<B> {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
}
}

View File

@@ -8,40 +8,51 @@ 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 core::hash::BuildHasherDefault;
use dashmap::DashMap;
use nanoid::nanoid; use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHasher; use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use stardust_xr::messenger::MessageSenderHandle; 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, serialize};
use std::any::{Any, TypeId};
use std::fmt::Debug; use std::fmt::Debug;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::vec::Vec; use std::vec::Vec;
use tracing::{debug_span, instrument};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
use self::alias::Alias; use self::alias::Alias;
use self::audio::Sound;
use self::data::{PulseReceiver, PulseSender};
use self::drawable::Drawable;
use self::fields::Field;
use self::input::{InputHandler, InputMethod};
use self::items::{Item, ItemAcceptor, ItemUI};
use self::spatial::zone::Zone;
use self::spatial::Spatial;
use self::startup::StartupSettings;
pub type Signal = fn(&Node, Arc<Client>, &[u8]) -> Result<()>; #[derive(Default)]
pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>; pub struct Message {
pub data: Vec<u8>,
pub fds: Vec<OwnedFd>,
}
impl From<Vec<u8>> for Message {
fn from(data: Vec<u8>) -> Self {
Message {
data,
fds: Vec::new(),
}
}
}
impl AsRef<[u8]> for Message {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
stardust_xr_server_codegen::codegen_node_protocol!();
pub struct Node { pub struct Node {
pub enabled: Arc<AtomicBool>, pub enabled: Arc<AtomicBool>,
@@ -50,40 +61,12 @@ pub struct Node {
client: Weak<Client>, client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>, message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize, // trailing_slash_pos: usize,
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>, local_signals: Mutex<FxHashMap<String, Signal>>,
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>, local_methods: Mutex<FxHashMap<String, Method>>,
destroyable: bool,
pub alias: OnceCell<Arc<Alias>>,
aliases: Registry<Alias>, aliases: Registry<Alias>,
aspects: Aspects,
pub spatial: OnceCell<Arc<Spatial>>, destroyable: bool,
pub field: OnceCell<Arc<Field>>,
pub zone: OnceCell<Arc<Zone>>,
// Data
pub pulse_sender: OnceCell<Arc<PulseSender>>,
pub pulse_receiver: OnceCell<Arc<PulseReceiver>>,
// Drawable
pub drawable: OnceCell<Drawable>,
// Input
pub input_method: OnceCell<Arc<InputMethod>>,
pub input_handler: OnceCell<Arc<InputHandler>>,
// Item
pub item: OnceCell<Arc<Item>>,
pub item_acceptor: OnceCell<Arc<ItemAcceptor>>,
pub item_ui: OnceCell<Arc<ItemUI>>,
// Sound
pub sound: OnceCell<Arc<Sound>>,
// Startup
pub startup_settings: OnceCell<Mutex<StartupSettings>>,
} }
impl Node { impl Node {
pub fn get_client(&self) -> Option<Arc<Client>> { pub fn get_client(&self) -> Option<Arc<Client>> {
self.client.upgrade() self.client.upgrade()
@@ -95,40 +78,32 @@ impl Node {
self.path.as_str() self.path.as_str()
} }
pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self { pub fn create_parent_name(
client: &Arc<Client>,
parent: &str,
name: &str,
destroyable: bool,
) -> Self {
let mut path = parent.to_string(); let mut path = parent.to_string();
path.push('/'); path.push('/');
path.push_str(name); path.push_str(name);
Self::create_path(client, path, destroyable)
}
pub fn create_path(client: &Arc<Client>, path: impl ToString, destroyable: bool) -> Self {
let node = Node { let node = Node {
enabled: Arc::new(AtomicBool::new(true)), enabled: Arc::new(AtomicBool::new(true)),
uid: nanoid!(), uid: nanoid!(),
client: Arc::downgrade(client), client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(), message_sender_handle: client.message_sender_handle.clone(),
path, path: path.to_string(),
// trailing_slash_pos: parent.len(), // trailing_slash_pos: parent.len(),
local_signals: Default::default(), local_signals: Default::default(),
local_methods: Default::default(), local_methods: Default::default(),
aliases: Default::default(),
aspects: Default::default(),
destroyable, destroyable,
alias: OnceCell::new(),
aliases: Registry::new(),
spatial: OnceCell::new(),
field: OnceCell::new(),
zone: OnceCell::new(),
pulse_sender: OnceCell::new(),
pulse_receiver: OnceCell::new(),
drawable: OnceCell::new(),
input_method: OnceCell::new(),
input_handler: OnceCell::new(),
item: OnceCell::new(),
item_acceptor: OnceCell::new(),
item_ui: OnceCell::new(),
sound: OnceCell::new(),
startup_settings: OnceCell::new(),
}; };
node.add_local_signal("set_enabled", Node::set_enabled_flex); <Node as NodeAspect>::add_node_members(&node);
node.add_local_signal("destroy", Node::destroy_flex);
node node
} }
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> { pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
@@ -144,45 +119,44 @@ impl Node {
} }
} }
pub fn set_enabled_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { // very much up for debate if we should allow this, as you can match objects using this
node.enabled.store(deserialize(data)?, Ordering::Relaxed); // pub fn get_client_pid_flex(
Ok(()) // node: Arc<Node>,
} // _calling_client: Arc<Client>,
pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { // _message: Message,
if node.destroyable { // ) -> Result<Message> {
node.destroy(); // let client = node
} // .client
Ok(()) // .upgrade()
} // .ok_or_else(|| eyre!("Could not get client for node?"))?;
// let pid = client.pid.ok_or_else(|| eyre!("Client PID is unknown"))?;
// Ok(serialize(pid)?.into())
// }
pub fn add_local_signal(&self, name: &str, signal: Signal) { pub fn add_local_signal(&self, name: &str, signal: Signal) {
self.local_signals.insert(name.to_string(), signal); self.local_signals.lock().insert(name.to_string(), signal);
} }
pub fn add_local_method(&self, name: &str, method: Method) { pub fn add_local_method(&self, name: &str, method: Method) {
self.local_methods.insert(name.to_string(), method); self.local_methods.lock().insert(name.to_string(), method);
} }
pub fn get_aspect<F, T>( pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
&self, self.aspects.add(aspect)
node_name: &'static str, }
aspect_type: &'static str, pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) {
aspect_fn: F, self.aspects.add_raw(aspect)
) -> Result<&T> }
where pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
F: FnOnce(&Node) -> &OnceCell<T>, self.aspects.get()
{
aspect_fn(self)
.get()
.ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
} }
pub fn send_local_signal( pub fn send_local_signal(
&self, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
method: &str, method: &str,
data: &[u8], message: Message,
) -> Result<(), ScenegraphError> { ) -> Result<(), ScenegraphError> {
if let Some(alias) = self.alias.get() { if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_signals.iter().any(|e| e == &method) { if !alias.info.server_signals.iter().any(|e| e == &method) {
return Err(ScenegraphError::SignalNotFound); return Err(ScenegraphError::SignalNotFound);
} }
@@ -190,78 +164,97 @@ impl Node {
.original .original
.upgrade() .upgrade()
.ok_or(ScenegraphError::BrokenAlias)? .ok_or(ScenegraphError::BrokenAlias)?
.send_local_signal(calling_client, method, data) .send_local_signal(calling_client, method, message)
} else { } else {
let signal = self let signal = self
.local_signals .local_signals
.lock()
.get(method) .get(method)
.cloned()
.ok_or(ScenegraphError::SignalNotFound)?; .ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, data).map_err(|error| ScenegraphError::SignalError { signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
error: error.to_string(), error: error.to_string(),
}) })
} }
} }
pub fn execute_local_method( pub fn execute_local_method(
&self, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
method: &str, method: &str,
data: &[u8], message: Message,
) -> Result<Vec<u8>, ScenegraphError> { response: MethodResponseSender,
if let Some(alias) = self.alias.get() { ) {
if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_methods.iter().any(|e| e == &method) { if !alias.info.server_methods.iter().any(|e| e == &method) {
return Err(ScenegraphError::MethodNotFound); response.send(Err(ScenegraphError::MethodNotFound));
return;
} }
alias let Some(alias) = alias.original.upgrade() else {
.original response.send(Err(ScenegraphError::BrokenAlias));
.upgrade() return;
.ok_or(ScenegraphError::BrokenAlias)? };
.execute_local_method(calling_client, method, data) alias.execute_local_method(
calling_client,
method,
Message {
data: message.data.clone(),
fds: Vec::new(),
},
response,
)
} else { } else {
let method = self let Some(method) = self.local_methods.lock().get(method).cloned() else {
.local_methods response.send(Err(ScenegraphError::MethodNotFound));
.get(method) return;
.ok_or(ScenegraphError::MethodNotFound)?; };
method(self, calling_client, message, response);
debug_span!("Handle method").in_scope(|| {
method(self, calling_client, data).map_err(|error| ScenegraphError::MethodError {
error: error.to_string(),
})
})
} }
} }
#[instrument(level = "debug", skip_all)] pub fn send_remote_signal(&self, method: &str, message: impl Into<Message>) -> Result<()> {
pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> { let message = message.into();
self.aliases self.aliases
.get_valid_contents() .get_valid_contents()
.iter() .iter()
.filter(|alias| alias.info.client_signals.iter().any(|e| e == &method)) .filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
.filter_map(|alias| alias.node.upgrade()) .filter_map(|alias| alias.node.upgrade())
.for_each(|node| { .for_each(|node| {
let _ = node.send_remote_signal(method, data); // Beware! file descriptors will not be sent to aliases!!!
let _ = node.send_remote_signal(
method,
Message {
data: message.data.clone(),
fds: Vec::new(),
},
);
}); });
let path = self.path.clone(); let path = self.path.clone();
let method = method.to_string(); let method = method.to_string();
let data = data.to_vec();
if let Some(handle) = self.message_sender_handle.as_ref() { if let Some(handle) = self.message_sender_handle.as_ref() {
handle.signal(path.as_str(), method.as_str(), data.as_slice())?; handle.signal(path.as_str(), method.as_str(), &message.data, message.fds)?;
} }
Ok(()) Ok(())
} }
// #[instrument(level = "debug", skip_all)] pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
// pub fn execute_remote_method( &self,
// &self, method: &str,
// method: &str, input: S,
// data: Vec<u8>, fds: Vec<OwnedFd>,
// ) -> Result<impl Future<Output = Result<Vec<u8>>>> { ) -> Result<(D, Vec<OwnedFd>)> {
// let message_sender_handle = self let message_sender_handle = self
// .message_sender_handle .message_sender_handle
// .as_ref() .as_ref()
// .ok_or(eyre!("Messenger does not exist for this node"))?; .ok_or(eyre!("Messenger does not exist for this node"))?;
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?; let serialized = serialize(input)?;
let result = message_sender_handle
.method(self.path.as_str(), method, &serialized, fds)?
.await
.map_err(|e| eyre!(e))?;
// Ok(async { future.await.map_err(|e| eyre!(e)) }) let (message, fds) = result.into_components();
// } let deserialized: D = deserialize(&message)?;
Ok((deserialized, fds))
}
} }
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 {
@@ -271,8 +264,54 @@ impl Debug for Node {
.finish() .finish()
} }
} }
impl NodeAspect for Node {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.enabled.store(enabled, Ordering::Relaxed);
Ok(())
}
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
}
}
impl Drop for Node { impl Drop for Node {
fn drop(&mut self) { fn drop(&mut self) {
// Debug breakpoint // Debug breakpoint
} }
} }
pub trait Aspect: Any + Send + Sync + 'static {
const NAME: &'static str;
}
#[derive(Default)]
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
impl Aspects {
fn add<A: Aspect>(&self, t: A) -> Arc<A> {
let aspect = Arc::new(t);
self.add_raw(aspect.clone());
aspect
}
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
self.0.lock().insert(Self::type_key::<A>(), aspect);
}
fn get<A: Aspect + Any + Send + Sync + 'static>(&self) -> Result<Arc<A>> {
self.0
.lock()
.get(&Self::type_key::<A>())
.and_then(|a| Arc::downcast(a.clone()).ok())
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
}
fn type_key<A: 'static>() -> TypeId {
TypeId::of::<A>()
}
}
impl Drop for Aspects {
fn drop(&mut self) {
self.0.lock().clear()
}
}

View File

@@ -1,37 +1,40 @@
use super::spatial::Spatial; use super::spatial::Spatial;
use super::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;
#[cfg(feature = "wayland")]
use crate::wayland::WAYLAND_DISPLAY;
#[cfg(feature = "xwayland")]
use crate::wayland::X_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 std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use tracing::instrument;
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 {
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> { pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
let node = Node::create(client, "", "", false); let node = Node::create_parent_name(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,
@@ -39,7 +42,11 @@ impl Root {
})) }))
} }
fn subscribe_frame_flex(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { fn subscribe_frame_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
calling_client calling_client
.root .root
.get() .get()
@@ -54,20 +61,46 @@ impl Root {
if let Ok(data) = serialize((delta, 0.0)) { if let Ok(data) = serialize((delta, 0.0)) {
for root in ROOT_REGISTRY.get_valid_contents() { for root in ROOT_REGISTRY.get_valid_contents() {
if root.send_frame_event.load(Ordering::Relaxed) { if root.send_frame_event.load(Ordering::Relaxed) {
let _ = root.node.send_remote_signal("frame", &data); let _ = root.node.send_remote_signal("frame", data.clone());
} }
} }
} }
} }
fn set_base_prefixes_flex( fn set_base_prefixes_flex(
_node: &Node, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], message: Message,
) -> Result<()> { ) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(data)?; *calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
Ok(()) Ok(())
} }
fn state_token_flex(
_node: Arc<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.get_aspect::<Spatial>().unwrap();
spatial.set_spatial_parent(None).unwrap();
spatial.set_local_transform(transform);
}
pub async fn save_state(&self) -> Result<ClientStateInternal> {
Ok(self
.node
.execute_remote_method_typed("save_state", (), Vec::new())
.await?
.0)
}
} }
impl Drop for Root { impl Drop for Root {
@@ -75,3 +108,36 @@ 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: Arc<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")]
env.insert(
"DISPLAY".to_string(),
format!(":{}", X_DISPLAY.get().unwrap()),
);
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,49 +1,66 @@
pub mod zone; pub mod zone;
use self::zone::{create_zone_flex, Zone}; use self::zone::Zone;
use super::Node; use super::fields::Field;
use super::{Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use color_eyre::eyre::{ensure, eyre, Result}; use crate::create_interface;
use color_eyre::eyre::{eyre, Result};
use glam::{vec3a, Mat4, Quat}; use glam::{vec3a, Mat4, Quat};
use mint::Vector3; use mint::Vector3;
use nanoid::nanoid; use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::fmt::Debug; use std::fmt::Debug;
use std::ptr; use std::ptr;
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, Weak};
use stereokit::{bounds_grow_to_fit_box, Bounds}; use stereokit::{bounds_grow_to_fit_box, Bounds};
use tracing::instrument;
stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform {
pub fn to_mat4(self, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(self.translation)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(self.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(self.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
}
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new(); static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial { pub struct Spatial {
uid: String, uid: String,
pub(super) node: Weak<Node>, pub(super) node: Weak<Node>,
self_ref: Weak<Spatial>,
parent: Mutex<Option<Arc<Spatial>>>, parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>, old_parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>, pub(super) transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>, zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>, pub(super) bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
} }
impl Spatial { impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> { pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new_cyclic(|self_ref| Spatial { Arc::new(Spatial {
uid: nanoid!(), uid: nanoid!(),
node, node,
self_ref: self_ref.clone(),
parent: Mutex::new(parent), parent: Mutex::new(parent),
old_parent: Mutex::new(None), old_parent: Mutex::new(None),
transform: Mutex::new(transform), transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()), zone: Mutex::new(Weak::new()),
children: Registry::new(), children: Registry::new(),
bounding_box_calc: OnceLock::default(), bounding_box_calc: OnceCell::default(),
}) })
} }
pub fn add_to( pub fn add_to(
@@ -51,36 +68,24 @@ impl Spatial {
parent: Option<Arc<Spatial>>, parent: Option<Arc<Spatial>>,
transform: Mat4, transform: Mat4,
zoneable: bool, zoneable: bool,
) -> Result<Arc<Spatial>> { ) -> Arc<Spatial> {
ensure!( let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
node.spatial.get().is_none(), <Spatial as SpatialAspect>::add_node_members(node);
"Internal: Node already has a Spatial aspect!"
);
let spatial = Spatial::new(Arc::downgrade(node), parent, transform);
node.add_local_method("get_bounding_box", Spatial::get_bounding_box_flex);
node.add_local_method("get_transform", Spatial::get_transform_flex);
node.add_local_signal("set_transform", Spatial::set_transform_flex);
node.add_local_signal("set_spatial_parent", Spatial::set_spatial_parent_flex);
node.add_local_signal(
"set_spatial_parent_in_place",
Spatial::set_spatial_parent_in_place_flex,
);
node.add_local_signal("set_zoneable", Spatial::set_zoneable_flex);
node.add_local_method("field_distance", Spatial::field_distance_flex);
node.add_local_method("field_normal", Spatial::field_normal_flex);
node.add_local_method("field_closest_point", Spatial::field_closest_point_flex);
if zoneable { if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial); ZONEABLE_REGISTRY.add_raw(&spatial);
} }
let _ = node.spatial.set(spatial.clone()); if let Some(parent) = parent {
Ok(spatial) parent.children.add_raw(&spatial);
}
<Spatial as SpatialAspect>::add_node_members(node);
node.add_aspect_raw(spatial.clone());
spatial
} }
pub fn node(&self) -> Option<Arc<Node>> { pub fn node(&self) -> Option<Arc<Node>> {
self.node.upgrade() self.node.upgrade()
} }
#[instrument(level = "debug", skip_all)]
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 { pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform()); let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse()); let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
@@ -88,9 +93,10 @@ impl Spatial {
} }
// the output bounds are probably way bigger than they need to be // the output bounds are probably way bigger than they need to be
#[instrument(level = "debug")]
pub fn get_bounding_box(&self) -> Bounds { pub fn get_bounding_box(&self) -> Bounds {
let Some(node) = self.node() else {return Bounds::default()}; let Some(node) = self.node() else {
return Bounds::default();
};
let mut bounds = self let mut bounds = self
.bounding_box_calc .bounding_box_calc
.get() .get()
@@ -106,21 +112,20 @@ impl Spatial {
bounds bounds
} }
#[instrument(level = "debug", skip_all)]
pub fn local_transform(&self) -> Mat4 { pub fn local_transform(&self) -> Mat4 {
*self.transform.lock() *self.transform.lock()
} }
pub fn global_transform(&self) -> Mat4 { pub fn global_transform(&self) -> Mat4 {
match self.get_parent() { let parent_transform = self
Some(value) => value.global_transform() * *self.transform.lock(), .get_parent()
None => *self.transform.lock(), .as_deref()
} .map(Self::global_transform)
.unwrap_or_default();
parent_transform * self.local_transform()
} }
#[instrument]
pub fn set_local_transform(&self, transform: Mat4) { pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.lock() = transform; *self.transform.lock() = transform;
} }
#[instrument(level = "debug", skip(self, reference_space))]
pub fn set_local_transform_components( pub fn set_local_transform_components(
&self, &self,
reference_space: Option<&Spatial>, reference_space: Option<&Spatial>,
@@ -142,7 +147,7 @@ impl Spatial {
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) = let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
local_transform_in_reference_space.to_scale_rotation_translation(); local_transform_in_reference_space.to_scale_rotation_translation();
if let Some(pos) = transform.position { if let Some(pos) = transform.translation {
reference_space_pos = pos.into() reference_space_pos = pos.into()
} }
if let Some(rot) = transform.rotation { if let Some(rot) = transform.rotation {
@@ -164,7 +169,6 @@ impl Spatial {
); );
} }
#[instrument(level = "debug", skip_all)]
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool { pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
let mut current_ancestor = spatial; let mut current_ancestor = spatial;
loop { loop {
@@ -183,24 +187,21 @@ impl Spatial {
fn get_parent(&self) -> Option<Arc<Spatial>> { fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.lock().clone() self.parent.lock().clone()
} }
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) { fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) {
if let Some(parent) = self.get_parent() { if let Some(parent) = self.get_parent() {
parent.children.remove(self); parent.children.remove(&self);
} }
if let Some(new_parent) = &new_parent { if let Some(new_parent) = &new_parent {
new_parent new_parent.children.add_raw(self);
.children
.add_raw(&self.self_ref.upgrade().unwrap());
} }
*self.parent.lock() = new_parent; *self.parent.lock() = new_parent.cloned();
} }
#[instrument(level = "debug", skip_all)] pub fn set_spatial_parent(self: &Arc<Self>, parent: Option<&Arc<Spatial>>) -> Result<()> {
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent let is_ancestor = parent
.as_ref() .as_ref()
.map(|parent| self.is_ancestor_of(parent.clone())) .map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false); .unwrap_or(false);
if is_ancestor { if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop")); return Err(eyre!("Setting spatial parent would cause a loop"));
@@ -209,209 +210,27 @@ impl Spatial {
Ok(()) Ok(())
} }
pub fn set_spatial_parent_in_place(
#[instrument(level = "debug", skip_all)] self: &Arc<Self>,
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> { parent: Option<&Arc<Spatial>>,
) -> Result<()> {
let is_ancestor = parent let is_ancestor = parent
.as_ref() .as_ref()
.map(|parent| self.is_ancestor_of(parent.clone())) .map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false); .unwrap_or(false);
if is_ancestor { if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop")); return Err(eyre!("Setting spatial parent would cause a loop"));
} }
self.set_local_transform(Spatial::space_to_space_matrix( self.set_local_transform(Spatial::space_to_space_matrix(
Some(self), Some(&self),
parent.as_deref(), parent.map(AsRef::as_ref),
)); ));
self.set_parent(parent); self.set_parent(parent);
Ok(()) Ok(())
} }
pub fn get_bounding_box_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial_path: Option<&str> = deserialize(data)?;
let bounds = if let Some(relative_spatial_path) = relative_spatial_path {
let relative_spatial = find_reference_space(&calling_client, relative_spatial_path)?;
let center =
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let bounds: Bounds = Bounds {
center,
dimensions: [0.0; 3].into(),
};
bounds_grow_to_fit_box(
bounds,
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
)
} else {
this_spatial.get_bounding_box()
};
serialize((
mint::Vector3::from(bounds.center),
mint::Vector3::from(bounds.dimensions),
))
.map_err(|e| e.into())
}
pub fn get_transform_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial = find_reference_space(&calling_client, deserialize(data)?)?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
serialize((
mint::Vector3::from(position),
mint::Quaternion::from(rotation),
mint::Vector3::from(scale),
))
.map_err(|e| e.into())
}
pub fn set_transform_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct TransformArgs<'a> {
reference_space_path: Option<&'a str>,
transform: Transform,
}
let transform_args: TransformArgs = deserialize(data)?;
let reference_space_transform = transform_args
.reference_space_path
.map(|path| find_reference_space(&calling_client, path))
.transpose()?;
node.spatial.get().unwrap().set_local_transform_components(
reference_space_transform.as_deref(),
transform_args.transform,
);
Ok(())
}
pub fn set_spatial_parent_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
}
pub fn set_spatial_parent_in_place_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
node.spatial
.get()
.unwrap()
.set_spatial_parent_in_place(Some(parent))?;
Ok(())
}
pub fn set_zoneable_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let zoneable: bool = deserialize(data)?;
let spatial = node.spatial.get().unwrap();
if zoneable {
ZONEABLE_REGISTRY.add_raw(spatial);
} else {
ZONEABLE_REGISTRY.remove(spatial);
zone::release(spatial);
}
Ok(())
}
pub fn field_distance_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| f.distance(spatial, point.into())))
.collect::<Vec<Option<f32>>>();
Ok(serialize(output)?)
}
pub fn field_normal_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.normal(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?)
}
pub fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.closest_point(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?)
}
#[instrument]
pub(self) fn zone_distance(&self) -> f32 { pub(self) fn zone_distance(&self) -> f32 {
self.zone self.zone
.lock() .lock()
@@ -421,6 +240,128 @@ impl Spatial {
.unwrap_or(f32::MAX) .unwrap_or(f32::MAX)
} }
} }
impl Aspect for Spatial {
const NAME: &'static str = "Spatial";
}
impl SpatialAspect for Spatial {
async fn get_local_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: mint::Vector3::from(bounds.center),
size: mint::Vector3::from(bounds.dimensions),
})
}
async fn get_relative_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let bounds = bounds_grow_to_fit_box(
Bounds {
center,
dimensions: [0.0; 3].into(),
},
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
);
Ok(BoundingBox {
center: mint::Vector3::from(bounds.center),
size: mint::Vector3::from(bounds.dimensions),
})
}
async fn get_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<Transform> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
Ok(Transform {
translation: Some(position.into()),
rotation: Some(rotation.into()),
scale: Some(scale.into()),
})
}
fn set_local_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(None, transform);
Ok(())
}
fn set_relative_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
Ok(())
}
fn set_spatial_parent(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent(Some(&parent))?;
Ok(())
}
fn set_spatial_parent_in_place(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent_in_place(Some(&parent))?;
Ok(())
}
fn set_zoneable(node: Arc<Node>, _calling_client: Arc<Client>, zoneable: bool) -> Result<()> {
let spatial = node.get_aspect::<Spatial>()?;
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
} else {
ZONEABLE_REGISTRY.remove(&spatial);
zone::release(&spatial);
}
Ok(())
}
}
impl PartialEq for Spatial { impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.uid == other.uid self.uid == other.uid
@@ -438,14 +379,14 @@ impl Debug for Spatial {
} }
impl Drop for Spatial { impl Drop for Spatial {
fn drop(&mut self) { fn drop(&mut self) {
zone::release_drop(self);
ZONEABLE_REGISTRY.remove(self); ZONEABLE_REGISTRY.remove(self);
zone::release(self);
} }
} }
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 { pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position let position = position
.then_some(transform.position) .then_some(transform.translation)
.flatten() .flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3])); .unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation let rotation = rotation
@@ -460,49 +401,41 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into()) Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
} }
pub fn find_spatial( pub struct SpatialInterface;
calling_client: &Arc<Client>, impl SpatialInterfaceAspect for SpatialInterface {
node_name: &'static str, fn create_spatial(
node_path: &str, _node: Arc<Node>,
) -> color_eyre::eyre::Result<Arc<Spatial>> { calling_client: Arc<Client>,
calling_client name: String,
.get_node(node_name, node_path)? parent: Arc<Node>,
.get_aspect(node_name, "spatial", |n| &n.spatial)
.cloned()
}
pub fn find_spatial_parent(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Spatial parent", node_path)
}
pub fn find_reference_space(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Reference space", node_path)
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "spatial", false);
node.add_local_signal("create_spatial", create_spatial_flex);
node.add_local_signal("create_zone", create_zone_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_spatial_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateSpatialInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
zoneable: bool, zoneable: bool,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, true);
let node = Node::create_parent_name(&calling_client, "/spatial/spatial", &name, true)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(())
}
fn create_zone(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let field = field.get_aspect::<Field>()?;
let node = Node::create_parent_name(&calling_client, "/spatial/zone", &name, true)
.add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent.clone()), transform, false);
Zone::add_to(&node, space, &field);
Ok(())
} }
let info: CreateSpatialInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, info.zoneable)?;
Ok(())
} }
create_interface!(SpatialInterface, SpatialInterfaceAspect, "/spatial");

View File

@@ -1,22 +1,15 @@
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY}; use super::{Spatial, ZoneAspect, ZONEABLE_REGISTRY};
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, registry::Registry},
nodes::{ nodes::{
alias::{Alias, AliasInfo}, alias::{Alias, AliasInfo},
fields::{find_field, Field}, fields::Field,
spatial::{find_spatial_parent, parse_transform}, Aspect, Node,
Node,
}, },
}; };
use color_eyre::eyre::Result;
use glam::vec3a; use glam::vec3a;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::{
schemas::flex::{deserialize, serialize},
values::Transform,
};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) { pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
@@ -31,20 +24,34 @@ pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
*spatial.old_parent.lock() = spatial.get_parent(); *spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone); *spatial.zone.lock() = Arc::downgrade(zone);
zone.captured.add_raw(spatial); zone.captured.add_raw(spatial);
let node = zone.spatial.node.upgrade().unwrap(); let Some(node) = zone.spatial.node.upgrade() else {
let _ = node.send_remote_signal("capture", &serialize(&spatial.uid).unwrap()); return;
};
let _ = super::zone_client::capture(&node, &spatial.uid);
} }
} }
pub fn release(spatial: &Spatial) { pub fn release(spatial: &Arc<Spatial>) {
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take()); let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref());
let mut spatial_zone = spatial.zone.lock(); let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() { if let Some(spatial_zone) = spatial_zone.upgrade() {
let node = spatial_zone.spatial.node.upgrade().unwrap(); let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
spatial_zone.captured.remove(spatial); spatial_zone.captured.remove(spatial);
let _ = node.send_remote_signal("release", &serialize(&spatial.uid).unwrap()); let _ = super::zone_client::release(&node, &spatial.uid);
} }
*spatial_zone = Weak::new(); *spatial_zone = Weak::new();
} }
pub(super) fn release_drop(spatial: &Spatial) {
let spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
spatial_zone.captured.remove(spatial);
let _ = super::zone_client::release(&node, &spatial.uid);
}
}
pub struct Zone { pub struct Zone {
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
@@ -60,33 +67,28 @@ impl Zone {
zoneables: Mutex::new(FxHashMap::default()), zoneables: Mutex::new(FxHashMap::default()),
captured: Registry::new(), captured: Registry::new(),
}); });
node.add_local_signal("capture", Zone::capture_flex); <Zone as ZoneAspect>::add_node_members(node);
node.add_local_signal("release", Zone::release_flex); node.add_aspect_raw(zone.clone());
node.add_local_signal("update", Zone::update);
let _ = node.zone.set(zone.clone());
zone zone
} }
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { }
let zone = node.zone.get().unwrap(); impl Aspect for Zone {
let capture_path: &str = deserialize(data)?; const NAME: &'static str = "Zone";
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?; }
capture(&spatial, zone); impl ZoneAspect for Zone {
Ok(()) fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
} let zone = node.get_aspect::<Zone>()?;
fn release_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { let Some(field) = zone.field.upgrade() else {
let capture_path: &str = deserialize(data)?; return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed"));
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?; };
release(&spatial);
Ok(())
}
fn update(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
let zone = node.zone.get().unwrap();
let Some(field) = zone.field.upgrade() else { return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed")) };
let Some((zone_client, zone_node)) = zone let Some((zone_client, zone_node)) = zone
.spatial .spatial
.node .node
.upgrade() .upgrade()
.and_then(|n| n.get_client().zip(Some(n))) else { return Err(color_eyre::eyre::eyre!("No client on node?")) }; .and_then(|n| n.get_client().zip(Some(n)))
else {
return Err(color_eyre::eyre::eyre!("No client on node?"));
};
let mut old_zoneables = zone.zoneables.lock(); let mut old_zoneables = zone.zoneables.lock();
for (_uid, zoneable) in old_zoneables.iter() { for (_uid, zoneable) in old_zoneables.iter() {
zoneable.destroy(); zoneable.destroy();
@@ -128,17 +130,41 @@ impl Zone {
}) })
.collect::<FxHashMap<String, Arc<Node>>>(); .collect::<FxHashMap<String, Arc<Node>>>();
for entered_uid in zoneables.keys().filter(|k| !old_zoneables.contains_key(*k)) { for (uid, zoneable) in zoneables
node.send_remote_signal("enter", &serialize(entered_uid)?)?; .iter()
.filter(|(k, _)| !old_zoneables.contains_key(*k))
{
super::zone_client::enter(&node, uid, zoneable)?;
} }
for left_uid in old_zoneables.keys().filter(|k| !zoneables.contains_key(*k)) { for left_uid in old_zoneables.keys().filter(|k| !zoneables.contains_key(*k)) {
node.send_remote_signal("leave", &serialize(left_uid)?)?; super::zone_client::leave(&node, &left_uid)?;
} }
*old_zoneables = zoneables; *old_zoneables = zoneables;
Ok(()) Ok(())
} }
fn capture(
node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let zone = node.get_aspect::<Zone>()?;
let spatial = spatial.get_aspect()?;
capture(&spatial, &zone);
Ok(())
}
fn release(
_node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let spatial = spatial.get_aspect()?;
release(&spatial);
Ok(())
}
} }
impl Drop for Zone { impl Drop for Zone {
fn drop(&mut self) { fn drop(&mut self) {
@@ -147,23 +173,3 @@ impl Drop for Zone {
} }
} }
} }
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateZoneInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
}
let info: CreateZoneInfo = deserialize(data)?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let node =
Node::create(&calling_client, "/spatial/zone", info.name, true).add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent), transform, false)?;
Zone::add_to(&node, space, &field);
Ok(())
}

View File

@@ -1,146 +0,0 @@
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
use super::{
items::{ItemAcceptor, TypeInfo},
spatial::find_spatial,
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>, data: &[u8]) -> Result<()> {
let spatial = find_spatial(&calling_client, "Root spatial", deserialize(data)?)?;
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
Ok(())
}
fn add_automatic_acceptor_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let acceptor_node = calling_client.get_node("Item acceptor", deserialize(data)?)?;
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>,
_data: &[u8],
) -> Result<Vec<u8>> {
let id = nanoid::nanoid!();
let data = serialize(&id)?;
STARTUP_SETTINGS
.lock()
.insert(id, node.startup_settings.get().unwrap().lock().clone());
Ok(data)
}
}
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>,
data: &[u8],
) -> Result<()> {
let node = Node::create(
&calling_client,
"/startup/settings",
deserialize(data)?,
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>,
_data: &[u8],
) -> Result<Vec<u8>> {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_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)?)
}

Binary file not shown.

Binary file not shown.

View File

@@ -10,10 +10,9 @@ use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use nanoid::nanoid; use nanoid::nanoid;
use serde::Serialize; use serde::Serialize;
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers}; use stardust_xr::{schemas::flex::flexbuffers, values::Datamap};
use std::sync::Arc; use std::sync::Arc;
use stereokit::StereoKitMultiThread; use stereokit::StereoKitMultiThread;
use tracing::instrument;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct KeyboardEvent { pub struct KeyboardEvent {
@@ -29,14 +28,14 @@ pub struct EyePointer {
} }
impl EyePointer { impl EyePointer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?; let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap(); .add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let pointer = let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap(); InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
Ok(EyePointer { spatial, pointer }) Ok(EyePointer { spatial, pointer })
} }
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
pub fn update(&self, sk: &impl StereoKitMultiThread) { pub fn update(&self, sk: &impl StereoKitMultiThread) {
let ray = sk.input_eyes(); let ray = sk.input_eyes();
self.spatial self.spatial
@@ -50,7 +49,7 @@ impl EyePointer {
let mut map = fbb.start_map(); let mut map = fbb.start_map();
map.push("eye", 2); map.push("eye", 2);
map.end_map(); map.end_map();
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok(); *self.pointer.datamap.lock() = Datamap::from_raw(fbb.take_buffer()).ok();
} }
} }
} }

View File

@@ -1,63 +1,92 @@
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY}, data::{
fields::Ray, mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
},
fields::{Field, Ray},
input::{pointer::Pointer, InputMethod, InputType}, input::{pointer::Pointer, InputMethod, InputType},
spatial::Spatial, spatial::Spatial,
Node, Node,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec3, Mat4, Vec3}; use glam::{vec2, vec3, Mat4, Vec2, Vec3};
use nanoid::nanoid; use nanoid::nanoid;
use serde::Serialize; use serde::{Deserialize, Serialize};
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers}; use stardust_xr::values::Datamap;
use std::{convert::TryFrom, sync::Arc}; use std::{convert::TryFrom, sync::Arc};
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread}; use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
use tracing::instrument; use xkbcommon::xkb::{Context, Keymap, FORMAT_TEXT_V1};
const SK_KEYMAP: &str = include_str!("sk.kmp"); #[derive(Default, Deserialize, Serialize)]
struct MouseEvent {
select: f32,
middle: f32,
context: f32,
grab: f32,
scroll_continuous: Vec2,
scroll_discrete: Vec2,
raw_input_events: Vec<u32>,
}
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct KeyboardEvent { pub struct KeyboardEvent {
pub keyboard: String, pub keyboard: (),
pub keymap: Option<String>, pub xkbv1: (),
pub keys_up: Option<Vec<u32>>, pub keymap_id: String,
pub keys_down: Option<Vec<u32>>, pub keys: Vec<i32>,
}
impl Default for KeyboardEvent {
fn default() -> Self {
Self {
keyboard: (),
xkbv1: (),
keymap_id: "flatscreen".to_string(),
keys: Default::default(),
}
}
} }
pub struct MousePointer { pub struct MousePointer {
node: Arc<Node>, node: Arc<Node>,
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent,
keyboard_sender: Arc<PulseSender>, keyboard_sender: Arc<PulseSender>,
} }
impl MousePointer { impl MousePointer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?; let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap(); .add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let pointer = let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap(); InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
let keyboard_mask = { KEYMAPS.lock().insert(
let mut fbb = flexbuffers::Builder::default(); "flatscreen".to_string(),
let mut map = fbb.start_map(); Keymap::new_from_names(&Context::new(0), "evdev", "", "", "", None, 0)
map.push("keyboard", "xkbv1"); .unwrap()
map.end_map(); .get_as_string(FORMAT_TEXT_V1),
Mask(fbb.take_buffer()) );
};
let keyboard_sender = PulseSender::add_to(&node, keyboard_mask).unwrap(); let keyboard_sender = PulseSender::add_to(
&node,
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
)
.unwrap();
Ok(MousePointer { Ok(MousePointer {
node, node,
spatial, spatial,
pointer, pointer,
mouse_datamap: Default::default(),
keyboard_datamap: Default::default(),
keyboard_sender, keyboard_sender,
}) })
} }
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)] pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let mouse = sk.input_mouse(); let mouse = sk.input_mouse();
let ray = ray_from_mouse(mouse.pos).unwrap(); let ray = ray_from_mouse(mouse.pos).unwrap();
@@ -71,41 +100,47 @@ impl MousePointer {
); );
{ {
// Set pointer input datamap // Set pointer input datamap
let mut fbb = flexbuffers::Builder::default(); self.mouse_datamap.select =
let mut map = fbb.start_map();
map.push(
"select",
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) { if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
1.0f32 1.0f32
} else { } else {
0.0f32 0.0f32
}, };
); self.mouse_datamap.middle =
map.push( if sk.input_key(Key::MouseCenter).contains(ButtonState::ACTIVE) {
"grab", 1.0f32
} else {
0.0f32
};
self.mouse_datamap.context =
if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) { if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
1.0f32 1.0f32
} else { } else {
0.0f32 0.0f32
}, };
); self.mouse_datamap.grab = if sk.input_key(Key::MouseBack).contains(ButtonState::ACTIVE)
let mut scroll_vec = map.start_vector("scroll"); || sk
scroll_vec.push(0_f32); .input_key(Key::MouseForward)
scroll_vec.push(mouse.scroll_change / 120.0); .contains(ButtonState::ACTIVE)
scroll_vec.end_vector(); {
map.end_map(); 1.0f32
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok(); } else {
0.0f32
};
self.mouse_datamap.scroll_continuous = vec2(0.0, mouse.scroll_change / 120.0);
self.mouse_datamap.scroll_discrete = vec2(0.0, mouse.scroll_change / 120.0);
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).ok();
} }
self.send_keyboard_input(sk); self.send_keyboard_input(sk);
} }
fn send_keyboard_input(&self, sk: &impl StereoKitMultiThread) { fn send_keyboard_input(&mut self, sk: &impl StereoKitMultiThread) {
let rx = PULSE_RECEIVER_REGISTRY let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask)) .filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| { .map(|rx| {
let result = rx.field.ray_march(Ray { let result = rx.field_node.get_aspect::<Field>().unwrap().ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0), origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0), direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(), space: self.spatial.clone(),
@@ -125,29 +160,131 @@ impl MousePointer {
.map(|(rx, _)| rx); .map(|(rx, _)| rx);
if let Some(rx) = rx { if let Some(rx) = rx {
let mut keys_up = vec![];
let mut keys_down = vec![];
let keys = (8_u32..254) let keys = (8_u32..254)
.filter_map(|i| Some((i, Key::try_from(i).ok()?))) .filter_map(|i| Key::try_from(i).ok())
.map(|(i, k)| (i - 8, sk.input_key(k))); .filter_map(|k| Some((map_key(k)?, sk.input_key(k))))
for (key, state) in keys { .filter_map(|(i, k)| {
if state.contains(ButtonState::JUST_ACTIVE) { if k.contains(ButtonState::JUST_ACTIVE) {
keys_down.push(key); Some(i as i32)
} else if state.contains(ButtonState::JUST_INACTIVE) { } else if k.contains(ButtonState::JUST_INACTIVE) {
keys_up.push(key); Some(-(i as i32))
} } else {
} None
}
})
.collect();
let key_event = KeyboardEvent { self.keyboard_datamap.keys = keys;
keyboard: "xkbv1".to_string(), if !self.keyboard_datamap.keys.is_empty() {
keymap: Some(SK_KEYMAP.to_string()), pulse_receiver_client::data(
keys_up: Some(keys_up), &rx.node.upgrade().unwrap(),
keys_down: Some(keys_down), &self.node.uid,
}; &Datamap::from_typed(&self.keyboard_datamap).unwrap(),
let mut serializer = flexbuffers::FlexbufferSerializer::new(); )
let _ = key_event.serialize(&mut serializer);
rx.send_data(&self.node.uid, serializer.take_buffer())
.unwrap(); .unwrap();
}
} }
} }
} }
fn map_key(key: Key) -> Option<u32> {
match key {
Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
Key::Tab => Some(input_event_codes::KEY_TAB!()),
Key::Return => Some(input_event_codes::KEY_ENTER!()),
Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
Key::Ctrl => Some(input_event_codes::KEY_LEFTCTRL!()),
Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
Key::Esc => Some(input_event_codes::KEY_ESC!()),
Key::Space => Some(input_event_codes::KEY_SPACE!()),
Key::End => Some(input_event_codes::KEY_END!()),
Key::Home => Some(input_event_codes::KEY_HOME!()),
Key::Left => Some(input_event_codes::KEY_LEFT!()),
Key::Right => Some(input_event_codes::KEY_RIGHT!()),
Key::Up => Some(input_event_codes::KEY_UP!()),
Key::Down => Some(input_event_codes::KEY_DOWN!()),
Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
Key::KeyInsert => Some(input_event_codes::KEY_INSERT!()),
Key::Del => Some(input_event_codes::KEY_DELETE!()),
Key::Key0 => Some(input_event_codes::KEY_0!()),
Key::Key1 => Some(input_event_codes::KEY_1!()),
Key::Key2 => Some(input_event_codes::KEY_2!()),
Key::Key3 => Some(input_event_codes::KEY_3!()),
Key::Key4 => Some(input_event_codes::KEY_4!()),
Key::Key5 => Some(input_event_codes::KEY_5!()),
Key::Key6 => Some(input_event_codes::KEY_6!()),
Key::Key7 => Some(input_event_codes::KEY_7!()),
Key::Key8 => Some(input_event_codes::KEY_8!()),
Key::Key9 => Some(input_event_codes::KEY_9!()),
Key::A => Some(input_event_codes::KEY_A!()),
Key::B => Some(input_event_codes::KEY_B!()),
Key::C => Some(input_event_codes::KEY_C!()),
Key::D => Some(input_event_codes::KEY_D!()),
Key::E => Some(input_event_codes::KEY_E!()),
Key::F => Some(input_event_codes::KEY_F!()),
Key::G => Some(input_event_codes::KEY_G!()),
Key::H => Some(input_event_codes::KEY_H!()),
Key::I => Some(input_event_codes::KEY_I!()),
Key::J => Some(input_event_codes::KEY_J!()),
Key::K => Some(input_event_codes::KEY_K!()),
Key::L => Some(input_event_codes::KEY_L!()),
Key::M => Some(input_event_codes::KEY_M!()),
Key::N => Some(input_event_codes::KEY_N!()),
Key::O => Some(input_event_codes::KEY_O!()),
Key::P => Some(input_event_codes::KEY_P!()),
Key::Q => Some(input_event_codes::KEY_Q!()),
Key::R => Some(input_event_codes::KEY_R!()),
Key::S => Some(input_event_codes::KEY_S!()),
Key::T => Some(input_event_codes::KEY_T!()),
Key::U => Some(input_event_codes::KEY_U!()),
Key::V => Some(input_event_codes::KEY_V!()),
Key::W => Some(input_event_codes::KEY_W!()),
Key::X => Some(input_event_codes::KEY_X!()),
Key::Y => Some(input_event_codes::KEY_Y!()),
Key::Z => Some(input_event_codes::KEY_Z!()),
Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
Key::F1 => Some(input_event_codes::KEY_F1!()),
Key::F2 => Some(input_event_codes::KEY_F2!()),
Key::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()),
Key::F6 => Some(input_event_codes::KEY_F6!()),
Key::F7 => Some(input_event_codes::KEY_F7!()),
Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()),
Key::F12 => Some(input_event_codes::KEY_F12!()),
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
Key::Period => Some(input_event_codes::KEY_DOT!()),
Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
Key::Minus => Some(input_event_codes::KEY_MINUS!()),
Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
Key::Backtick => None,
Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
Key::Decimal => Some(input_event_codes::KEY_DOT!()),
Key::Divide => Some(input_event_codes::KEY_SLASH!()),
_ => None,
}
}

View File

@@ -1,275 +0,0 @@
xkb_keymap {
default xkb_keycodes "basic" {
minimum = 8;
maximum = 255;
<backspace> = 8;
<tab> = 9;
<return> = 13;
<shift> = 16;
<ctrl> = 17;
<alt> = 18;
<caps_lock> = 20;
<esc> = 27;
<space> = 32;
<end> = 35;
<home> = 36;
<left> = 37;
<right> = 39;
<up> = 38;
<down> = 40;
<page_up> = 33;
<page_down> = 34;
<printscreen> = 42;
<key_insert> = 45;
<del> = 46;
<0> = 48;
<1> = 49;
<2> = 50;
<3> = 51;
<4> = 52;
<5> = 53;
<6> = 54;
<7> = 55;
<8> = 56;
<9> = 57;
<a> = 65;
<b> = 66;
<c> = 67;
<d> = 68;
<e> = 69;
<f> = 70;
<g> = 71;
<h> = 72;
<i> = 73;
<j> = 74;
<k> = 75;
<l> = 76;
<m> = 77;
<n> = 78;
<o> = 79;
<p> = 80;
<q> = 81;
<r> = 82;
<s> = 83;
<t> = 84;
<u> = 85;
<v> = 86;
<w> = 87;
<x> = 88;
<y> = 89;
<z> = 90;
<num0> = 96;
<num1> = 97;
<num2> = 98;
<num3> = 99;
<num4> = 100;
<num5> = 101;
<num6> = 102;
<num7> = 103;
<num8> = 104;
<num9> = 105;
<f1> = 112;
<f2> = 113;
<f3> = 114;
<f4> = 115;
<f5> = 116;
<f6> = 117;
<f7> = 118;
<f8> = 119;
<f9> = 120;
<f10> = 121;
<f11> = 122;
<f12> = 123;
<comma> = 188;
<period> = 190;
<slash_fwd> = 191;
<slash_back> = 220;
<semicolon> = 186;
<apostrophe> = 222;
<bracket_open> = 219;
<bracket_close> = 221;
<minus> = 189;
<equals> = 187;
<backtick> = 192;
<lcmd> = 91;
<rcmd> = 92;
<multiply> = 106;
<add> = 107;
<subtract> = 109;
<decimal> = 110;
<divide> = 111;
};
partial default xkb_types "basic" {
virtual_modifiers Alt;
type "ONE_LEVEL" {
modifiers= none;
level_name[1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= 2;
level_name[1]= "Base";
level_name[2]= "Shift";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= 2;
map[Lock]= 2;
level_name[1]= "Base";
level_name[2]= "Caps";
};
type "SHIFT+ALT" {
modifiers= Shift+Alt;
map[Shift+Alt]= 2;
level_name[1]= "Base";
level_name[2]= "Shift+Alt";
};
type "PC_CONTROL_LEVEL2" {
modifiers= Control;
map[Control]= 2;
level_name[1]= "Base";
level_name[2]= "Control";
};
};
partial default xkb_compatibility "basic" {
interpret.useModMapMods= AnyLevel;
interpret.repeat= False;
interpret ISO_Level2_Latch+Exactly(Shift) {
useModMapMods=level1;
action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
};
interpret Caps_Lock+AnyOfOrNone(all) {
action= LockMods(modifiers=Lock);
};
indicator "Caps Lock" {
whichModState= locked;
modifiers= Lock;
};
};
default xkb_symbols "basic" {
name[Group1]="English (US)";
key <backspace> { [ BackSpace, BackSpace ] };
key <tab> { [ Tab, ISO_Left_Tab ] };
key <return> { [ Return ] };
key <shift> { [ Shift_L ] };
key <shift> { [ Shift_R ] };
key <ctrl> { [ Control_L ] };
key <ctrl> { [ Control_R ] };
key <alt> { [ Alt_L ] };
key <alt> { [ Alt_R ] };
key <caps_lock> { [ Caps_Lock ] };
key <esc> { [ Escape ] };
key <space> { [ space ] };
key <end> { [ End ] };
key <home> { [ Home ] };
key <left> { [ Left ] };
key <right> { [ Right ] };
key <up> { [ Up ] };
key <down> { [ Down ] };
key <page_up> { [ Page_Up ] };
key <page_down> { [ Page_Down ] };
key <printscreen> { [ Print ] };
key <key_insert> { [ Insert ] };
key <del> { [ Delete ] };
key <1> { [ 1, exclam ] };
key <2> { [ 2, at ] };
key <3> { [ 3, numbersign ] };
key <4> { [ 4, dollar ] };
key <5> { [ 5, percent ] };
key <6> { [ 6, asciicircum ] };
key <7> { [ 7, ampersand ] };
key <8> { [ 8, asterisk ] };
key <9> { [ 9, parenleft ] };
key <0> { [ 0, parenright ] };
key <a> { [ a, A ] };
key <b> { [ b, B ] };
key <c> { [ c, C ] };
key <d> { [ d, D ] };
key <e> { [ e, E ] };
key <f> { [ f, F ] };
key <g> { [ g, G ] };
key <h> { [ h, H ] };
key <i> { [ i, I ] };
key <j> { [ j, J ] };
key <k> { [ k, K ] };
key <l> { [ l, L ] };
key <m> { [ m, M ] };
key <n> { [ n, N ] };
key <o> { [ o, O ] };
key <p> { [ p, P ] };
key <q> { [ q, Q ] };
key <r> { [ r, R ] };
key <s> { [ s, S ] };
key <t> { [ t, T ] };
key <u> { [ u, U ] };
key <v> { [ v, V ] };
key <w> { [ w, W ] };
key <x> { [ x, X ] };
key <y> { [ y, Y ] };
key <z> { [ z, Z ] };
key <num0> { [ KP_0 ] };
key <num1> { [ KP_1 ] };
key <num2> { [ KP_2 ] };
key <num3> { [ KP_3 ] };
key <num4> { [ KP_4 ] };
key <num5> { [ KP_5 ] };
key <num6> { [ KP_6 ] };
key <num7> { [ KP_7 ] };
key <num8> { [ KP_8 ] };
key <num9> { [ KP_9 ] };
key <f1> { [ F1 ] };
key <f2> { [ F2 ] };
key <f3> { [ F3 ] };
key <f4> { [ F4 ] };
key <f5> { [ F5 ] };
key <f6> { [ F6 ] };
key <f7> { [ F7 ] };
key <f8> { [ F8 ] };
key <f9> { [ F9 ] };
key <f10> { [ F10 ] };
key <f11> { [ F11 ] };
key <f12> { [ F12 ] };
key <comma> { [ comma, less ] };
key <period> { [ period, greater ] };
key <slash_fwd> { [ slash, question ] };
key <slash_back> { [ backslash, bar ] };
key <semicolon> { [ semicolon, colon ] };
key <apostrophe> { [ apostrophe ] };
key <bracket_open> { [ bracketleft, braceleft ] };
key <bracket_close> { [ bracketright, braceright ] };
key <minus> { [ minus, underscore ] };
key <equals> { [ equal, plus ] };
key <backtick> { [ grave, asciitilde ] };
key <lcmd> { [ Super_L ] };
key <rcmd> { [ Super_R ] };
key <multiply> { [ KP_Multiply ] };
key <add> { [ KP_Add ] };
key <subtract> { [ KP_Subtract ] };
key <decimal> { [ KP_Decimal ] };
key <divide> { [ KP_Divide ] };
modifier_map Shift { <shift> };
modifier_map Lock { <caps_lock> };
modifier_map Control { <caps_lock> };
modifier_map Mod1 { <alt> };
modifier_map Mod4 { <lcmd>, <rcmd> };
};
};

View File

@@ -7,51 +7,73 @@ use crate::{
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use glam::{Mat4, Vec2, Vec3};
use nanoid::nanoid; use serde::{Deserialize, Serialize};
use stardust_xr::{ use stardust_xr::values::Datamap;
schemas::{flat::Datamap, flex::flexbuffers},
values::Transform,
};
use std::sync::Arc; use std::sync::Arc;
use stereokit::{ButtonState, Handed, StereoKitMultiThread}; use stereokit::{
use tracing::instrument; named_colors::WHITE, ButtonState, Handed, Model, RenderLayer, StereoKitDraw,
StereoKitMultiThread,
};
#[derive(Default, Deserialize, Serialize)]
struct ControllerDatamap {
select: f32,
grab: f32,
scroll: Vec2,
}
pub struct SkController { pub struct SkController {
_node: Arc<Node>, _node: Arc<Node>,
input: Arc<InputMethod>, input: Arc<InputMethod>,
model: Model,
handed: Handed, handed: Handed,
datamap: ControllerDatamap,
} }
impl SkController { impl SkController {
pub fn new(handed: Handed) -> Result<Self> { pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> {
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?; let _node = Node::create_parent_name(
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?; &INTERNAL_CLIENT,
"",
if handed == Handed::Left {
"controller_left"
} else {
"controller_right"
},
false,
)
.add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false);
let model = sk.model_create_mem("cursor.glb", include_bytes!("cursor.glb"), None)?;
let tip = InputType::Tip(Tip::default()); let tip = InputType::Tip(Tip::default());
let input = InputMethod::add_to(&_node, tip, None)?; let input = InputMethod::add_to(&_node, tip, None)?;
Ok(SkController { Ok(SkController {
_node, _node,
input, input,
handed, handed,
model,
datamap: Default::default(),
}) })
} }
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)] pub fn update(&mut self, sk: &impl StereoKitDraw) {
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let controller = sk.input_controller(self.handed); let controller = sk.input_controller(self.handed);
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE); *self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
if *self.input.enabled.lock() { if *self.input.enabled.lock() {
self.input.spatial.set_local_transform_components( let world_transform = Mat4::from_rotation_translation(
None, controller.aim.orientation,
Transform::from_position_rotation( controller.aim.position,
controller.pose.position,
controller.pose.orientation,
),
); );
sk.model_draw(
&self.model,
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
WHITE,
RenderLayer::LAYER0,
);
self.input.spatial.set_local_transform(world_transform);
} }
let mut fbb = flexbuffers::Builder::default(); self.datamap.select = controller.trigger;
let mut map = fbb.start_map(); self.datamap.grab = controller.grip;
map.push("select", controller.trigger); self.datamap.scroll = controller.stick;
map.push("grab", controller.grip); *self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
map.end_map();
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
} }
} }

View File

@@ -9,31 +9,40 @@ use crate::{
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use nanoid::nanoid; use nanoid::nanoid;
use stardust_xr::schemas::{ use serde::{Deserialize, Serialize};
flat::{Datamap, Hand as FlatHand, Joint}, use stardust_xr::{
flex::flexbuffers, schemas::flat::{Hand as FlatHand, Joint},
values::Datamap,
}; };
use std::sync::Arc; use std::sync::Arc;
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread}; use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
use tracing::instrument;
fn convert_joint(joint: HandJoint) -> Joint { fn convert_joint(joint: HandJoint) -> Joint {
Joint { Joint {
position: joint.position.into(), position: joint.position.into(),
rotation: joint.orientation.into(), rotation: joint.orientation.into(),
radius: joint.radius, radius: joint.radius,
distance: 0.0,
} }
} }
#[derive(Default, Deserialize, Serialize)]
struct HandDatamap {
pinch_strength: f32,
grab_strength: f32,
}
pub struct SkHand { pub struct SkHand {
_node: Arc<Node>, _node: Arc<Node>,
input: Arc<InputMethod>, input: Arc<InputMethod>,
handed: Handed, handed: Handed,
datamap: HandDatamap,
} }
impl SkHand { impl SkHand {
pub fn new(handed: Handed) -> Result<Self> { pub fn new(handed: Handed) -> Result<Self> {
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?; let _node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?; .add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false);
let hand = InputType::Hand(Box::new(Hand { let hand = InputType::Hand(Box::new(Hand {
base: FlatHand { base: FlatHand {
right: handed == Handed::Right, right: handed == Handed::Right,
@@ -45,15 +54,20 @@ impl SkHand {
_node, _node,
input, input,
handed, handed,
datamap: Default::default(),
}) })
} }
#[instrument(level = "debug", name = "Update Hand Input Method", skip_all)] pub fn update(&mut self, controller_enabled: bool, sk: &impl StereoKitMultiThread) {
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let sk_hand = sk.input_hand(self.handed); let sk_hand = sk.input_hand(self.handed);
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() { if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
let controller = sk.input_controller(self.handed); let controller_active = controller_enabled
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE) && sk
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE); .input_controller(self.handed)
.tracked
.contains(ButtonState::ACTIVE);
*self.input.enabled.lock() =
!controller_active && sk_hand.tracked_state.contains(ButtonState::ACTIVE);
sk.input_hand_visible(self.handed, *self.input.enabled.lock());
if *self.input.enabled.lock() { if *self.input.enabled.lock() {
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]); hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]); hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
@@ -86,11 +100,8 @@ impl SkHand {
hand.base.elbow = None; hand.base.elbow = None;
} }
} }
let mut fbb = flexbuffers::Builder::default(); self.datamap.pinch_strength = sk_hand.pinch_activation;
let mut map = fbb.start_map(); self.datamap.grab_strength = sk_hand.grip_activation;
map.push("grab_strength", sk_hand.grip_activation); *self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
map.push("pinch_strength", sk_hand.pinch_activation);
map.end_map();
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
} }
} }

View File

@@ -5,12 +5,13 @@ use glam::Mat4;
use mint::Vector2; use mint::Vector2;
use nanoid::nanoid; use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use stereokit::StereoKitMultiThread; use stereokit::StereoKitMultiThread;
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
data::{Mask, PulseReceiver}, data::PulseReceiver,
fields::{r#box::BoxField, Field}, fields::{r#box::BoxField, Field},
spatial::Spatial, spatial::Spatial,
Node, Node,
@@ -39,12 +40,17 @@ pub struct PlaySpace {
} }
impl PlaySpace { impl PlaySpace {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?; let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false)?; .add_to_scenegraph()?;
let field = BoxField::add_to(&node, [0.0; 3].into())?; let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
BoxField::add_to(&node, [0.0; 3].into());
let field = node.get_aspect::<Field>()?.clone();
let pulse_rx = let pulse_rx = PulseReceiver::add_to(
PulseReceiver::add_to(&node, field.clone(), Mask::from_struct::<PlaySpaceMap>())?; &node,
node.clone(),
Datamap::from_typed(PlaySpaceMap::default())?,
)?;
Ok(PlaySpace { Ok(PlaySpace {
_node: node, _node: node,
@@ -60,7 +66,9 @@ impl PlaySpace {
pose.orientation, pose.orientation,
pose.position, pose.position,
)); ));
let Field::Box(box_field) = self.field.as_ref() else {return}; let Field::Box(box_field) = self.field.as_ref() else {
return;
};
box_field.set_size( box_field.set_size(
[ [
sk.world_get_bounds_size().x, sk.world_get_bounds_size().x,

View File

@@ -2,6 +2,8 @@ use crate::wayland::surface::CoreSurface;
use super::state::{ClientState, WaylandState}; use super::state::{ClientState, WaylandState};
use portable_atomic::{AtomicU32, Ordering}; use portable_atomic::{AtomicU32, Ordering};
#[cfg(feature = "xwayland")]
use smithay::xwayland::XWaylandClientData;
use smithay::{ use smithay::{
delegate_compositor, delegate_compositor,
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
@@ -23,11 +25,9 @@ impl CompositorHandler for WaylandState {
.data_map .data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0)); .insert_if_missing_threadsafe(|| AtomicU32::new(0));
if !count_new { if !count_new {
count = data if let Some(stored_count) = data.data_map.get::<AtomicU32>() {
.data_map count = stored_count.fetch_add(1, Ordering::Relaxed);
.get::<AtomicU32>() }
.unwrap()
.fetch_add(1, Ordering::Relaxed);
} }
data.data_map.get::<Arc<CoreSurface>>().cloned() data.data_map.get::<Arc<CoreSurface>>().cloned()
@@ -38,7 +38,15 @@ impl CompositorHandler for WaylandState {
} }
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
&client.get_data::<ClientState>().unwrap().compositor_state if let Some(client_state) = client.get_data::<ClientState>() {
&client_state.compositor_state
} else {
#[cfg(feature = "xwayland")]
if let Some(xwayland_client_data) = client.get_data::<XWaylandClientData>() {
return &xwayland_client_data.compositor_state;
}
unimplemented!()
}
} }
} }

View File

@@ -88,9 +88,10 @@ impl KdeDecorationHandler for WaylandState {
&mut self, &mut self,
_surface: &WlSurface, _surface: &WlSurface,
decoration: &OrgKdeKwinServerDecoration, decoration: &OrgKdeKwinServerDecoration,
_mode: WEnum<KdeMode>, mode: WEnum<KdeMode>,
) { ) {
decoration.mode(KdeMode::Server); let Ok(mode) = mode.into_result() else {return};
decoration.mode(mode);
} }
} }
delegate_kde_decoration!(WaylandState); delegate_kde_decoration!(WaylandState);

139
src/wayland/drm.rs Normal file
View File

@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-3.0-only
// Re-export only the actual code, and then only use this re-export
// The `generated` module below is just some boilerplate to properly isolate stuff
// and avoid exposing internal details.
//
// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
pub use generated::wl_drm;
#[allow(non_upper_case_globals, non_camel_case_types)]
mod generated {
use smithay::reexports::wayland_server::{self, protocol::*};
pub mod __interfaces {
use smithay::reexports::wayland_server::protocol::__interfaces::*;
wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml");
}
use self::__interfaces::*;
wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml");
}
use super::state::WaylandState;
use smithay::{
backend::allocator::{
dmabuf::{Dmabuf, DmabufFlags},
Fourcc, Modifier,
},
reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
},
};
use std::convert::TryFrom;
impl GlobalDispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn bind(
state: &mut WaylandState,
_dh: &DisplayHandle,
_client: &Client,
resource: New<wl_drm::WlDrm>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
let drm_instance = data_init.init(resource, ());
drm_instance.device("/dev/dri/renderD128".to_string());
if drm_instance.version() >= 2 {
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
}
for format in state.drm_formats.iter() {
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
drm_instance.format(converted as u32);
}
}
}
fn can_view(_client: Client, _global_dataa: &()) -> bool {
true
}
}
impl Dispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
_client: &Client,
drm: &wl_drm::WlDrm,
request: wl_drm::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_drm::Request::Authenticate { .. } => drm.authenticated(),
wl_drm::Request::CreateBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePrimeBuffer {
id,
name,
width,
height,
format,
offset0,
stride0,
..
} => {
let format = match Fourcc::try_from(format) {
Ok(format) => {
if !state.drm_formats.contains(&format) {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format not advertised by wl_drm"),
);
return;
}
format
}
Err(_) => {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format unknown / not advertised by wl_drm"),
);
return;
}
};
if width < 1 || height < 1 {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("width or height not positive"),
);
return;
}
let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty());
dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid);
match dma.build() {
Some(dmabuf) => {
state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap();
data_init.init(id, dmabuf);
}
None => {
// Buffer import failed. The protocol documentation heavily implies killing the
// client is the right thing to do here.
drm.post_error(
wl_drm::Error::InvalidName,
"dmabuf global was destroyed on server",
);
}
}
}
}
}
}

View File

@@ -1,15 +1,25 @@
mod compositor; mod compositor;
mod data_device; mod data_device;
mod decoration; mod decoration;
pub mod panel_item;
mod seat; mod seat;
mod shaders;
mod state; mod state;
mod surface; mod surface;
// mod xdg_activation; // mod xdg_activation;
mod drm;
mod xdg_shell; mod xdg_shell;
#[cfg(feature = "xwayland_rootful")]
pub mod xwayland_rootful;
#[cfg(feature = "xwayland_rootful")]
use self::xwayland_rootful::X11Lock;
#[cfg(feature = "xwayland_rootful")]
use crate::wayland::xwayland_rootful::start_xwayland;
#[cfg(feature = "xwayland_rootless")]
pub mod xwayland_rootless;
#[cfg(feature = "xwayland_rootless")]
use self::xwayland_rootless::XWaylandState;
use self::{state::WaylandState, surface::CORE_SURFACES}; use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::wayland::seat::SeatData;
use crate::{core::task, wayland::state::ClientState}; use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{ensure, Result}; use color_eyre::eyre::{ensure, Result};
use global_counter::primitive::exact::CounterU32; use global_counter::primitive::exact::CounterU32;
@@ -20,7 +30,12 @@ use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::egl::EGLContext; use smithay::backend::egl::EGLContext;
use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportDma; use smithay::backend::renderer::ImportDma;
use smithay::reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket}; use smithay::reexports::wayland_server::backend::ClientId;
use smithay::reexports::wayland_server::DisplayHandle;
use smithay::reexports::wayland_server::{Display, ListeningSocket};
use smithay::wayland::dmabuf;
use std::ffi::OsStr;
use std::os::fd::OwnedFd;
use std::os::unix::prelude::AsRawFd; use std::os::unix::prelude::AsRawFd;
use std::{ use std::{
ffi::c_void, ffi::c_void,
@@ -32,10 +47,10 @@ use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{ use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle, io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
}; };
use tracing::{debug, debug_span, info, instrument}; use tracing::{debug_span, info, instrument};
pub static X_DISPLAY: OnceCell<u32> = OnceCell::new();
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new(); pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0); pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
struct EGLRawHandles { struct EGLRawHandles {
@@ -59,15 +74,35 @@ fn get_sk_egl() -> Result<EGLRawHandles> {
}) })
} }
static GLOBAL_DESTROY_QUEUE: OnceCell<mpsc::Sender<GlobalId>> = OnceCell::new(); pub struct DisplayWrapper(Mutex<Display<WaylandState>>, DisplayHandle);
impl DisplayWrapper {
pub fn handle(&self) -> DisplayHandle {
self.1.clone()
}
pub fn dispatch_clients(&self, state: &mut WaylandState) -> Result<usize, std::io::Error> {
self.0.lock().dispatch_clients(state)
}
pub fn flush_clients(&self, client: Option<ClientId>) {
if let Some(mut lock) = self.0.try_lock() {
let _ = lock.backend().flush(client);
}
}
pub fn poll_fd(&self) -> Result<OwnedFd, std::io::Error> {
self.0.lock().backend().poll_fd().try_clone_to_owned()
}
}
pub struct Wayland { pub struct Wayland {
display: Arc<Mutex<Display<WaylandState>>>, display: Arc<DisplayWrapper>,
pub socket_name: String, pub socket_name: Option<String>,
join_handle: JoinHandle<Result<()>>, join_handle: JoinHandle<Result<()>>,
renderer: GlesRenderer, renderer: GlesRenderer,
dmabuf_rx: UnboundedReceiver<Dmabuf>, dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
state: Arc<Mutex<WaylandState>>, wayland_state: Arc<Mutex<WaylandState>>,
#[cfg(feature = "xwayland_rootful")]
pub x_lock: X11Lock,
#[cfg(feature = "xwayland_rootless")]
pub xwayland_state: XWaylandState,
} }
impl Wayland { impl Wayland {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
@@ -84,21 +119,25 @@ impl Wayland {
let display_handle = display.handle(); let display_handle = display.handle();
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel(); let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let display = Arc::new(Mutex::new(display)); let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone()));
let state = WaylandState::new(display.clone(), display_handle, &renderer, dmabuf_tx);
let (global_destroy_queue_in, global_destroy_queue) = mpsc::channel(8); #[cfg(feature = "xwayland_rootless")]
GLOBAL_DESTROY_QUEUE.set(global_destroy_queue_in).unwrap(); let xwayland_state = XWaylandState::create(&display_handle)?;
let wayland_state = WaylandState::new(display_handle, &renderer, dmabuf_tx);
let socket = ListeningSocket::bind_auto("wayland", 0..33)?; let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
let socket_name = socket.socket_name().unwrap().to_str().unwrap().to_string(); let socket_name = socket
WAYLAND_DISPLAY .socket_name()
.set(socket_name.clone()) .and_then(OsStr::to_str)
.expect("seriously message nova this time they screwed up big time"); .map(ToString::to_string);
if let Some(socket_name) = &socket_name {
let _ = WAYLAND_DISPLAY.set(socket_name.clone());
}
#[cfg(feature = "xwayland_rootful")]
let x_display = start_xwayland(socket.as_raw_fd())?;
info!(socket_name, "Wayland active"); info!(socket_name, "Wayland active");
let join_handle = let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state.clone())?;
Wayland::start_loop(display.clone(), socket, state.clone(), global_destroy_queue)?;
Ok(Wayland { Ok(Wayland {
display, display,
@@ -106,45 +145,48 @@ impl Wayland {
join_handle, join_handle,
renderer, renderer,
dmabuf_rx, dmabuf_rx,
state, wayland_state,
#[cfg(feature = "xwayland_rootful")]
x_lock: x_display,
#[cfg(feature = "xwayland_rootless")]
xwayland_state,
}) })
} }
fn start_loop( fn start_loop(
display: Arc<Mutex<Display<WaylandState>>>, display: Arc<DisplayWrapper>,
socket: ListeningSocket, socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>, state: Arc<Mutex<WaylandState>>,
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
) -> Result<JoinHandle<Result<()>>> { ) -> Result<JoinHandle<Result<()>>> {
let listen_async = let listen_async =
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?; AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?; let dispatch_poll_fd = display.poll_fd()?;
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?; let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
let dh1 = display.lock().handle(); let dh1 = display.handle();
let mut dh2 = dh1.clone(); let mut dh2 = dh1.clone();
Ok(task::new(|| "wayland loop", async move { Ok(task::new(|| "wayland loop", async move {
let _socket = socket; // Keep the socket alive let _socket = socket; // Keep the socket alive
loop { loop {
tokio::select! { tokio::select! {
e = global_destroy_queue.recv() => { // New global to destroy
debug!(?e, "destroy global");
dh1.remove_global::<WaylandState>(e.unwrap());
}
acc = listen_async.accept() => { // New client connected acc = listen_async.accept() => { // New client connected
let (stream, _) = acc?; let (stream, _) = acc?;
let client = dh2.insert_client(stream.into_std()?, Arc::new(ClientState::default()))?; let client_state = Arc::new(ClientState {
id: OnceCell::new(),
state.lock().new_client(client.id(), &dh2); compositor_state: Default::default(),
display: Arc::downgrade(&display),
seat: SeatData::new(&dh1)
});
let client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
let _ = client_state.seat.client.set(client.id());
} }
e = dispatch_poll_listener.readable() => { // Dispatch e = dispatch_poll_listener.readable() => { // Dispatch
let mut guard = e?; let mut guard = e?;
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> { debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
let mut display = display.lock();
display.dispatch_clients(&mut *state.lock())?; display.dispatch_clients(&mut *state.lock())?;
display.flush_clients()?; display.flush_clients(None);
Ok(()) Ok(())
})?; })?;
guard.clear_ready(); guard.clear_ready();
@@ -156,27 +198,31 @@ impl Wayland {
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))] #[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
pub fn update(&mut self, sk: &impl StereoKitDraw) { pub fn update(&mut self, sk: &impl StereoKitDraw) {
while let Ok(dmabuf) = self.dmabuf_rx.try_recv() { while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
let _ = self.renderer.import_dmabuf(&dmabuf, None); if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
if let Some(notifier) = notifier {
notifier.failed();
}
}
} }
for core_surface in CORE_SURFACES.get_valid_contents() { for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.process(sk, &mut self.renderer); core_surface.process(sk, &mut self.renderer);
} }
self.display.lock().flush_clients().unwrap(); self.display.flush_clients(None);
} }
pub fn frame_event(&self, sk: &impl StereoKitDraw) { pub fn frame_event(&self, sk: &impl StereoKitDraw) {
let state = self.state.lock(); let output = self.wayland_state.lock().output.clone();
for core_surface in CORE_SURFACES.get_valid_contents() { for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.frame(sk, state.output.clone()); core_surface.frame(sk, output.clone());
} }
} }
pub fn make_context_current(&self) { pub fn make_context_current(&self) {
unsafe { unsafe {
self.renderer.egl_context().make_current().unwrap(); let _ = self.renderer.egl_context().make_current();
} }
} }
} }

View File

@@ -1,663 +0,0 @@
use super::{
seat::{Cursor, SeatData},
surface::CoreSurface,
xdg_shell::{PopupData, ToplevelData, XdgSurfaceData},
SERIAL_COUNTER,
};
use crate::{
core::{
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
drawable::Drawable,
items::{self, Item, ItemSpecialization, ItemType, TypeInfo},
spatial::Spatial,
Node,
},
wayland::seat::{KeyboardEvent, PointerEvent},
};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use nanoid::nanoid;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{
de::{Deserializer, Error, SeqAccess, Visitor},
ser::Serializer,
Deserialize, Serialize,
};
use smithay::{
reexports::{
wayland_protocols::xdg::shell::server::{
xdg_popup::XdgPopup,
xdg_surface::XdgSurface,
xdg_toplevel::{XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE},
},
wayland_server::{
backend::Credentials, protocol::wl_surface::WlSurface, Resource, Weak as WlWeak,
},
},
wayland::compositor,
};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak};
use tracing::debug;
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
lazy_static! {
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel",
aliased_local_signals: vec![
"apply_surface_material",
"configure_toplevel",
"set_toplevel_capabilities",
"pointer_scroll",
"pointer_button",
"pointer_motion",
"keyboard_key",
"keyboard_set_keymap_names",
"keyboard_set_keymap_string",
"close",
],
aliased_local_methods: vec![],
aliased_remote_signals: vec![
"commit_toplevel",
"recommend_toplevel_state",
"set_cursor",
"new_popup",
"reposition_popup",
"drop_popup",
],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
/// An ID for a surface inside this panel item
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum SurfaceID {
Cursor,
Toplevel,
Popup(String),
}
impl Default for SurfaceID {
fn default() -> Self {
Self::Toplevel
}
}
impl<'de> serde::Deserialize<'de> for SurfaceID {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_seq(SurfaceIDVisitor)
}
}
struct SurfaceIDVisitor;
impl<'de> Visitor<'de> for SurfaceIDVisitor {
type Value = SurfaceID;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("idk")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let Some(discrim) = seq.next_element()? else {
return Err(A::Error::missing_field("discrim"));
};
// idk if you wanna check for extraneous elements
// I didn't bother
match discrim {
"Cursor" => Ok(SurfaceID::Cursor),
"Toplevel" => Ok(SurfaceID::Toplevel),
"Popup" => {
let Some(text) = seq.next_element()? else {
return Err(A::Error::missing_field("popup_text"));
};
Ok(SurfaceID::Popup(text))
}
_ => Err(A::Error::unknown_variant(
discrim,
&["Cursor", "Toplevel", "Popup"],
)),
}
}
}
impl serde::Serialize for SurfaceID {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Cursor => ["Cursor"].serialize(serializer),
Self::Toplevel => ["Toplevel"].serialize(serializer),
Self::Popup(text) => ["Popup", text].serialize(serializer),
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(tag = "type", content = "content")]
pub enum RecommendedState {
Maximize(bool),
Fullscreen(bool),
Minimize,
Move,
Resize(u32),
}
pub struct PanelItem {
pub uid: String,
node: Weak<Node>,
cursor: Mutex<Option<WlWeak<WlSurface>>>,
pub seat_data: Arc<SeatData>,
toplevel: WlWeak<XdgToplevel>,
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
pointer_grab: Mutex<Option<SurfaceID>>,
keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl PanelItem {
pub fn create(
toplevel: XdgToplevel,
wl_surface: WlSurface,
client_credentials: Option<Credentials>,
seat_data: Arc<SeatData>,
) -> (Arc<Node>, Arc<PanelItem>) {
debug!(?toplevel, ?client_credentials, "Create panel item");
let startup_settings = client_credentials
.and_then(|cred| get_env(cred.pid).ok())
.and_then(|env| startup_settings(&env));
let uid = nanoid!();
let node = Arc::new(Node::create(
&INTERNAL_CLIENT,
"/item/panel/item",
&uid,
true,
));
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let panel_item = Arc::new(PanelItem {
uid: uid.clone(),
node: Arc::downgrade(&node),
cursor: Mutex::new(None),
seat_data,
toplevel: toplevel.downgrade(),
popups: Mutex::new(FxHashMap::default()),
pointer_grab: Mutex::new(None),
keyboard_grab: Mutex::new(None),
});
if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(
spatial.global_transform().inverse() * startup_settings.transform,
);
}
panel_item
.seat_data
.new_surface(&wl_surface, Arc::downgrade(&panel_item));
let item = Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(panel_item.clone()),
);
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",
PanelItem::apply_surface_material_flex,
);
node.add_local_signal("configure_toplevel", PanelItem::configure_toplevel_flex);
node.add_local_signal(
"set_toplevel_capabilities",
PanelItem::set_toplevel_capabilities_flex,
);
node.add_local_signal("pointer_scroll", PanelItem::pointer_scroll_flex);
node.add_local_signal("pointer_button", PanelItem::pointer_button_flex);
node.add_local_signal("pointer_motion", PanelItem::pointer_motion_flex);
node.add_local_signal(
"keyboard_set_keymap_string",
PanelItem::keyboard_set_keymap_string_flex,
);
node.add_local_signal(
"keyboard_set_keymap_names",
PanelItem::keyboard_set_keymap_names_flex,
);
node.add_local_signal("keyboard_key", PanelItem::keyboard_key_flex);
(node, panel_item)
}
pub fn from_node(node: &Node) -> Option<Arc<PanelItem>> {
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
Some(panel_item.clone())
}
fn toplevel(&self) -> XdgToplevel {
self.toplevel.upgrade().unwrap()
}
fn toplevel_xdg_surface(&self) -> XdgSurface {
let toplevel = self.toplevel();
let data = ToplevelData::get(&toplevel).lock();
data.xdg_surface()
}
fn toplevel_wl_surface(&self) -> WlSurface {
XdgSurfaceData::get(&self.toplevel_xdg_surface())
.lock()
.wl_surface()
}
fn core_surface(&self) -> Option<Arc<CoreSurface>> {
compositor::with_states(&self.toplevel_wl_surface(), |data| {
data.data_map.get::<Arc<CoreSurface>>().cloned()
})
}
fn flush_clients(&self) {
if let Some(core_surface) = self.core_surface() {
core_surface.flush_clients();
}
}
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self.cursor.lock().clone()?.upgrade().ok(),
SurfaceID::Toplevel => Some(self.toplevel_wl_surface()),
SurfaceID::Popup(popup) => {
let popups = self.popups.lock();
let popup = popups.get(popup)?.upgrade().ok()?;
let surf = PopupData::get(&popup).lock().wl_surface();
Some(surf)
}
}
}
fn wl_surface_from_id_result(&self, id: &SurfaceID) -> Result<WlSurface> {
self.wl_surface_from_id(id)
.ok_or(eyre!("Surface with ID not found"))
}
fn apply_surface_material_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct SurfaceMaterialInfo<'a> {
surface: SurfaceID,
model_node_path: &'a str,
}
let info: SurfaceMaterialInfo = deserialize(data)?;
let Some(wl_surface) = panel_item.wl_surface_from_id(&info.surface) else { return Ok(()) };
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { return Ok(()) };
let model_node = calling_client
.scenegraph
.get_node(info.model_node_path)
.ok_or_else(|| eyre!("Model node not found"))?;
let Some(Drawable::ModelPart(model_node)) = model_node.drawable.get() else {bail!("Node is not a model")};
debug!(?info, "Apply surface material");
core_surface.apply_material(model_node);
Ok(())
}
fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let (surface_id, position): (SurfaceID, Vector2<f64>) = deserialize(data)?;
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
debug!(?surface_id, ?position, "Pointer deactivate");
panel_item
.seat_data
.pointer_event(&wl_surface, PointerEvent::Motion(position));
panel_item.flush_clients();
Ok(())
}
fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(data)?;
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
debug!(?surface_id, button, state, "Pointer button");
panel_item
.seat_data
.pointer_event(&wl_surface, PointerEvent::Button { button, state });
panel_item.flush_clients();
Ok(())
}
fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct PointerScrollInfo {
surface_id: SurfaceID,
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
}
let info: PointerScrollInfo = deserialize(data)?;
let wl_surface = panel_item.wl_surface_from_id_result(&info.surface_id)?;
debug!(?info, "Pointer scroll");
panel_item.seat_data.pointer_event(
&wl_surface,
PointerEvent::Scroll {
axis_continuous: info.axis_continuous,
axis_discrete: info.axis_discrete,
},
);
panel_item.flush_clients();
Ok(())
}
fn keyboard_set_keymap_string_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let context = xkb::Context::new(0);
let keymap =
Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
PanelItem::keyboard_set_keymap_flex(node, &keymap)
}
fn keyboard_set_keymap_names_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Debug, Deserialize)]
struct Names<'a> {
rules: &'a str,
model: &'a str,
layout: &'a str,
variant: &'a str,
options: Option<String>,
}
let names: Names = deserialize(data)?;
let context = xkb::Context::new(0);
let keymap = Keymap::new_from_names(
&context,
names.rules,
names.model,
names.layout,
names.variant,
names.options,
XKB_KEYMAP_FORMAT_TEXT_V1,
)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
PanelItem::keyboard_set_keymap_flex(node, &keymap)
}
fn keyboard_set_keymap_flex(node: &Node, keymap: &Keymap) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let toplevel = panel_item.toplevel_wl_surface();
debug!(?toplevel, "Keyboard set keymap");
let mut surfaces = vec![toplevel];
surfaces.extend(panel_item.popups.lock().values().filter_map(|p| {
let popup = p.upgrade().ok()?;
let popup_data = PopupData::get(&popup).lock();
let xdg_surface = popup_data.xdg_surface();
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
Some(xdg_surface_data.wl_surface())
}));
panel_item.seat_data.set_keymap(keymap, surfaces);
Ok(())
}
fn keyboard_key_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(data)?;
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
debug!(key, state, "Set keyboard key state");
panel_item
.seat_data
.keyboard_event(&wl_surface, KeyboardEvent::Key { key, state });
Ok(())
}
fn configure_toplevel_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
let xdg_surface = panel_item.toplevel_xdg_surface();
#[derive(Debug, Deserialize)]
struct ConfigureToplevelInfo {
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
}
let info: ConfigureToplevelInfo = deserialize(data)?;
debug!(info = ?&info, "Configure toplevel info");
if let Some(bounds) = info.bounds {
if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE {
xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32);
}
}
let zero_size = Vector2::from([0; 2]);
let size = info.size.unwrap_or(zero_size);
// if size == zero_size && (info.states.contains(1) || info.states.contains(2)) {
// xdg_toplevel.configure(
// size.x as i32,
// size.y as i32,
// info.states
// .into_iter()
// .flat_map(|state| state.to_ne_bytes())
// .collect(),
// );
// }
xdg_toplevel.configure(
size.x as i32,
size.y as i32,
info.states.into_iter().flat_map(u32::to_ne_bytes).collect(),
);
xdg_surface.configure(SERIAL_COUNTER.inc());
core_surface.flush_clients();
Ok(())
}
fn set_toplevel_capabilities_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
if xdg_toplevel.version() < EVT_WM_CAPABILITIES_SINCE {
return Ok(());
}
let xdg_surface = panel_item.toplevel_xdg_surface();
let capabilities: Vec<u8> = deserialize(data)?;
debug!("Set toplevel capabilities");
xdg_toplevel.wm_capabilities(capabilities);
xdg_surface.configure(SERIAL_COUNTER.inc());
core_surface.flush_clients();
Ok(())
}
pub fn commit_toplevel(&self) {
// let mapped_size = self.core_surface().and_then(|c| c.size());
let toplevel = self.toplevel();
let state = ToplevelData::get(&toplevel);
let state = state.lock();
// let mut queued_state = state.queued_state.take().unwrap();
// queued_state.mapped = mapped_size.is_some();
// if let Some(size) = mapped_size {
// queued_state.size = size;
// queued_state.geometry.update_to_surface_size(size);
// }
// *state = (*queued_state).clone();
// state.queued_state = Some(queued_state);
debug!(state = ?&*state, "Commit toplevel");
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("commit_toplevel", &serialize(&*state).unwrap());
}
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
let Some(node) = self.node.upgrade() else { return };
let data = serialize(state).unwrap();
debug!(?state, "Recommend toplevel state");
let _ = node.send_remote_signal("recommend_toplevel_state", &data);
}
pub fn new_popup(&self, popup: &XdgPopup, data: &PopupData) {
let uid = data.uid.clone();
self.popups.lock().insert(uid.clone(), popup.downgrade());
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("new_popup", &serialize(&(&uid, data)).unwrap());
}
// pub fn commit_popup(&self, data: &PopupData) {
// let xdg_surf = data.xdg_surface.upgrade().unwrap();
// let surf = xdg_surf
// .data::<XdgSurfaceData>()
// .unwrap()
// .wl_surface
// .upgrade()
// .unwrap();
// let core_surface =
// compositor::with_states(&surf, |s| s.data_map.get::<Arc<CoreSurface>>().cloned())
// .unwrap();
// let mut popup_state = data.state.lock();
// popup_state.mapped = core_surface.size().is_some();
// }
pub fn reposition_popup(&self, popup_state: &PopupData) {
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal(
"reposition_popup",
&serialize(popup_state.positioner_data().unwrap()).unwrap(),
);
}
pub fn drop_popup(&self, uid: &str) {
if let Some(popup) = self
.popups
.lock()
.remove(uid)
.and_then(|popup| popup.upgrade().ok()?.data::<Arc<PopupData>>().cloned())
{
self.seat_data.drop_surface(&popup.wl_surface());
}
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("drop_popup", &serialize(uid).unwrap());
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("grab_keyboard", &serialize(sid).unwrap());
}
pub fn set_cursor(&self, surface: Option<&WlSurface>, hotspot_x: i32, hotspot_y: i32) {
let Some(node) = self.node.upgrade() else { return };
debug!(?surface, hotspot_x, hotspot_y, "Set cursor size");
let mut data = serialize(()).unwrap();
let cursor_size = surface
.and_then(|c| CoreSurface::from_wl_surface(c))
.and_then(|c| c.size());
if let Some(size) = cursor_size {
data = serialize((size, (hotspot_x, hotspot_y))).unwrap();
}
let _ = node.send_remote_signal("set_cursor", &data);
*self.cursor.lock() = surface.map(|surf| surf.downgrade());
}
pub fn on_drop(&self) {
let toplevel = self.toplevel_wl_surface();
self.seat_data.drop_surface(&toplevel);
debug!("Drop panel item");
}
}
impl ItemSpecialization for PanelItem {
fn serialize_start_data(&self, id: &str) -> Vec<u8> {
let cursor = self.cursor.lock().as_ref().and_then(|c| c.upgrade().ok());
let cursor_size = cursor
.as_ref()
.and_then(|c| CoreSurface::from_wl_surface(&c))
.and_then(|c| c.size());
let cursor_hotspot = cursor
.and_then(|c| {
compositor::with_states(&c, |data| data.data_map.get::<Arc<Cursor>>().cloned())
})
.map(|cursor| cursor.hotspot);
let toplevel = self.toplevel();
let toplevel_state = ToplevelData::get(&toplevel);
let toplevel_state = toplevel_state.lock().clone();
let popups = self
.popups
.lock()
.values()
.filter_map(|v| Some(v.upgrade().ok()?.data::<Mutex<PopupData>>()?.lock().clone()))
.collect::<Vec<_>>();
let pointer_grab = self.pointer_grab.lock().clone();
let keyboard_grab = self.keyboard_grab.lock().clone();
serialize((
id,
(
cursor_size.zip(cursor_hotspot),
toplevel_state,
popups,
pointer_grab,
keyboard_grab,
),
))
.unwrap()
}
}
impl Drop for PanelItem {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
}
}

View File

@@ -1,9 +1,13 @@
use super::{ use super::{
panel_item::PanelItem, state::WaylandState, surface::CoreSurface, GLOBAL_DESTROY_QUEUE, state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER, SERIAL_COUNTER,
}; };
use crate::core::task; use crate::{
use color_eyre::eyre::Result; core::task,
nodes::items::panel::{Backend, Geometry, PanelItem},
};
use color_eyre::eyre::{bail, eyre, Result};
use mint::Vector2; use mint::Vector2;
use nanoid::nanoid; use nanoid::nanoid;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -27,39 +31,51 @@ use smithay::{
}; };
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
sync::{Arc, Weak}, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use tokio::sync::watch;
use tracing::{debug, warn}; use tracing::{debug, warn};
use xkbcommon::xkb::{self, Keymap}; use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keycode, Keymap};
pub fn handle_cursor<B: Backend>(
panel_item: &Arc<PanelItem<B>>,
mut cursor: watch::Receiver<Option<CursorInfo>>,
) {
let panel_item_weak = Arc::downgrade(panel_item);
let _ = task::new(|| "cursor handler", async move {
while cursor.changed().await.is_ok() {
let Some(panel_item) = panel_item_weak.upgrade() else {continue};
let cursor_info = cursor.borrow();
panel_item.set_cursor(cursor_info.as_ref().and_then(CursorInfo::cursor_data));
}
});
}
pub struct KeyboardInfo { pub struct KeyboardInfo {
keymap_string: String,
keymap: KeymapFile, keymap: KeymapFile,
state: xkb::State, state: xkb::State,
mods: ModifiersState, mods: ModifiersState,
keys: FxHashSet<u32>, keys: FxHashSet<u32>,
} }
impl KeyboardInfo { impl KeyboardInfo {
pub fn new(keymap: &Keymap) -> Self { pub fn new(keymap_string: String, keymap: &Keymap) -> Self {
KeyboardInfo { KeyboardInfo {
keymap_string,
state: xkb::State::new(keymap), state: xkb::State::new(keymap),
keymap: KeymapFile::new(keymap), keymap: KeymapFile::new(keymap),
mods: ModifiersState::default(), mods: ModifiersState::default(),
keys: FxHashSet::default(), keys: FxHashSet::default(),
} }
} }
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<usize> { pub fn process(&mut self, key: u32, pressed: bool, keyboard: &WlKeyboard) -> Result<usize> {
let wl_key_state = match state { let xkb_key_state = if pressed {
0 => KeyState::Released, xkb::KeyDirection::Down
1 => KeyState::Pressed, } else {
_ => color_eyre::eyre::bail!("Invalid key state!"), xkb::KeyDirection::Up
}; };
let xkb_key_state = match state { let state_components = self.state.update_key(Keycode::new(key + 8), xkb_key_state);
0 => xkb::KeyDirection::Up,
1 => xkb::KeyDirection::Down,
_ => color_eyre::eyre::bail!("Invalid key state!"),
};
let state_components = self.state.update_key(key + 8, xkb_key_state);
if state_components != 0 { if state_components != 0 {
self.mods.update_with(&self.state); self.mods.update_with(&self.state);
keyboard.modifiers( keyboard.modifiers(
@@ -70,6 +86,17 @@ impl KeyboardInfo {
0, 0,
); );
} }
// if pressed {
// println!("Key {key} is being pressed with {state_components} modifiers");
// } else {
// println!("Key {key} is being released with {state_components} modifiers");
// }
let wl_key_state = if pressed {
KeyState::Pressed
} else {
KeyState::Released
};
keyboard.key(SERIAL_COUNTER.inc(), 0, key, wl_key_state); keyboard.key(SERIAL_COUNTER.inc(), 0, key, wl_key_state);
match wl_key_state { match wl_key_state {
KeyState::Pressed => { KeyState::Pressed => {
@@ -87,7 +114,7 @@ unsafe impl Send for KeyboardInfo {}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum PointerEvent { pub enum PointerEvent {
Motion(Vector2<f64>), Motion(Vector2<f32>),
Button { Button {
button: u32, button: u32,
state: u32, state: u32,
@@ -100,29 +127,36 @@ pub enum PointerEvent {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum KeyboardEvent { pub enum KeyboardEvent {
Keymap, Keymap,
Key { key: u32, state: u32 }, Key { key: u32, state: bool },
} }
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1); const POINTER_EVENT_TIMEOUT: Duration = Duration::from_millis(50);
struct SurfaceInfo { struct SurfaceInfo {
wl_surface: WlWeak<WlSurface>, wl_surface: WlWeak<WlSurface>,
panel_item: Weak<PanelItem>, cursor_sender: watch::Sender<Option<CursorInfo>>,
pointer_queue: VecDeque<PointerEvent>, pointer_queue: VecDeque<PointerEvent>,
pointer_latest_event: Instant, pointer_latest_event: Instant,
keyboard_queue: VecDeque<KeyboardEvent>, keyboard_queue: VecDeque<KeyboardEvent>,
keyboard_info: Option<KeyboardInfo>, keyboard_info: Option<KeyboardInfo>,
} }
impl SurfaceInfo { impl SurfaceInfo {
fn new(wl_surface: &WlSurface, panel_item: Weak<PanelItem>) -> Self { fn new(wl_surface: &WlSurface, cursor_sender: watch::Sender<Option<CursorInfo>>) -> Self {
SurfaceInfo { SurfaceInfo {
wl_surface: wl_surface.downgrade(), wl_surface: wl_surface.downgrade(),
panel_item, cursor_sender,
pointer_queue: VecDeque::new(), pointer_queue: VecDeque::new(),
pointer_latest_event: Instant::now(), pointer_latest_event: Instant::now(),
keyboard_queue: VecDeque::new(), keyboard_queue: VecDeque::new(),
keyboard_info: None, keyboard_info: None,
} }
} }
fn flush(&self) {
if let Some(client) = self.wl_surface.upgrade().ok().and_then(|s| s.client()) {
if let Some(client_data) = client.get_data::<ClientState>() {
client_data.flush();
}
}
}
fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool { fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool {
let Ok(focus) = self.wl_surface.upgrade() else { return false; }; let Ok(focus) = self.wl_surface.upgrade() else { return false; };
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; }; let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
@@ -137,18 +171,20 @@ impl SurfaceInfo {
pointer.enter( pointer.enter(
SERIAL_COUNTER.inc(), SERIAL_COUNTER.inc(),
&focus, &focus,
pos.x.clamp(0.0, focus_size.x as f64), (pos.x as f64).clamp(0.0, focus_size.x as f64),
pos.y.clamp(0.0, focus_size.y as f64), (pos.y as f64).clamp(0.0, focus_size.y as f64),
); );
locked = true; locked = true;
} }
(true, PointerEvent::Motion(pos)) => { (true, PointerEvent::Motion(pos)) => {
pointer.motion( pointer.motion(
0, 0,
pos.x.clamp(0.0, focus_size.x as f64), (pos.x as f64).clamp(0.0, focus_size.x as f64),
pos.y.clamp(0.0, focus_size.y as f64), (pos.y as f64).clamp(0.0, focus_size.y as f64),
); );
pointer.frame(); if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
} }
(true, PointerEvent::Button { button, state }) => { (true, PointerEvent::Button { button, state }) => {
pointer.button( pointer.button(
@@ -161,7 +197,9 @@ impl SurfaceInfo {
_ => continue, _ => continue,
}, },
); );
pointer.frame(); if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
} }
( (
true, true,
@@ -172,17 +210,24 @@ impl SurfaceInfo {
) => { ) => {
if let Some(axis_continuous) = axis_continuous { if let Some(axis_continuous) = axis_continuous {
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64); pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64); pointer.axis(0, Axis::VerticalScroll, -axis_continuous.y as f64);
} }
if let Some(axis_discrete) = axis_discrete { if pointer.version() >= wl_pointer::EVT_AXIS_DISCRETE_SINCE {
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32); if let Some(axis_discrete) = axis_discrete {
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32); pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
pointer.axis_discrete(Axis::VerticalScroll, -axis_discrete.y as i32);
}
} }
if axis_discrete.is_none() && axis_continuous.is_none() { if pointer.version() >= wl_pointer::EVT_AXIS_STOP_SINCE
&& axis_discrete.is_none()
&& axis_continuous.is_none()
{
pointer.axis_stop(0, Axis::HorizontalScroll); pointer.axis_stop(0, Axis::HorizontalScroll);
pointer.axis_stop(0, Axis::VerticalScroll); pointer.axis_stop(0, Axis::VerticalScroll);
} }
pointer.frame(); if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
} }
(locked, event) => { (locked, event) => {
warn!(locked, ?event, "Invalid pointer event!"); warn!(locked, ?event, "Invalid pointer event!");
@@ -193,6 +238,7 @@ impl SurfaceInfo {
pointer.leave(SERIAL_COUNTER.inc(), &focus); pointer.leave(SERIAL_COUNTER.inc(), &focus);
locked = false; locked = false;
} }
self.flush();
locked locked
} }
@@ -202,7 +248,9 @@ impl SurfaceInfo {
if !locked { if !locked {
keyboard.enter(0, &focus, vec![]); keyboard.enter(0, &focus, vec![]);
keyboard.repeat_info(0, 0); if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
locked = info.keymap.send(keyboard).is_ok(); locked = info.keymap.send(keyboard).is_ok();
} }
while let Some(event) = self.keyboard_queue.pop_front() { while let Some(event) = self.keyboard_queue.pop_front() {
@@ -224,50 +272,62 @@ impl SurfaceInfo {
} }
} }
} }
self.flush();
locked locked
} }
} }
pub struct SeatData { pub struct SeatData {
client: ClientId, pub client: OnceCell<ClientId>,
global_id: OnceCell<GlobalId>, global_id: OnceCell<GlobalId>,
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>, surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>, pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>, keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
touch: OnceCell<WlTouch>, touch: OnceCell<WlTouch>,
touches: Mutex<FxHashMap<ObjectId, u32>>,
} }
impl SeatData { impl SeatData {
pub fn new(dh: &DisplayHandle, client: ClientId) -> Arc<Self> { pub fn new(dh: &DisplayHandle) -> Arc<Self> {
let seat_data = Arc::new(SeatData { let seat_data = Arc::new(SeatData {
client, client: OnceCell::new(),
global_id: OnceCell::new(), global_id: OnceCell::new(),
surfaces: Mutex::new(FxHashMap::default()), surfaces: Mutex::new(FxHashMap::default()),
pointer: OnceCell::new(), pointer: OnceCell::new(),
keyboard: OnceCell::new(), keyboard: OnceCell::new(),
touch: OnceCell::new(), touch: OnceCell::new(),
touches: Mutex::new(FxHashMap::default()),
}); });
seat_data let _ = seat_data
.global_id .global_id
.set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone())) .set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone()));
.unwrap();
seat_data seat_data
} }
pub fn set_keymap(&self, keymap: &Keymap, surfaces: Vec<WlSurface>) { pub fn set_keymap(&self, keymap_str: String, surfaces: Vec<WlSurface>) -> Result<()> {
let context = xkb::Context::new(0);
let keymap =
Keymap::new_from_string(&context, keymap_str.clone(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
let mut panels = self.surfaces.lock(); let mut panels = self.surfaces.lock();
let Some((_, focus)) = self.keyboard.get() else {return}; let Some((_, focus)) = self.keyboard.get() else {bail!("Could not get keyboard")};
for surface in surfaces { for surface in surfaces {
let Some(surface_info) = panels.get_mut(&surface.id()) else {continue}; let Some(surface_info) = panels.get_mut(&surface.id()) else {continue};
if let Some(keyboard_info) = &mut surface_info.keyboard_info {
if &keyboard_info.keymap_string == &keymap_str {
continue;
}
}
surface_info surface_info
.keyboard_info .keyboard_info
.replace(KeyboardInfo::new(keymap)); .replace(KeyboardInfo::new(keymap_str.clone(), &keymap));
if *focus.lock() == surface.id() { if *focus.lock() == surface.id() {
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap); surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
} }
} }
Ok(())
} }
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) { pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
@@ -343,10 +403,13 @@ impl SeatData {
} }
} }
pub fn new_surface(&self, surface: &WlSurface, panel_item: Weak<PanelItem>) { pub fn new_surface(&self, surface: &WlSurface) -> watch::Receiver<Option<CursorInfo>> {
let (tx, rx) = watch::channel(None);
self.surfaces self.surfaces
.lock() .lock()
.insert(surface.id(), SurfaceInfo::new(surface, panel_item)); .insert(surface.id(), SurfaceInfo::new(surface, tx));
rx
} }
pub fn drop_surface(&self, surface: &WlSurface) { pub fn drop_surface(&self, surface: &WlSurface) {
self.surfaces.lock().remove(&surface.id()); self.surfaces.lock().remove(&surface.id());
@@ -362,14 +425,51 @@ impl SeatData {
*keyboard_focus = ObjectId::null(); *keyboard_focus = ObjectId::null();
} }
} }
self.touches.lock().remove(&surface.id());
}
pub fn touch_down(&self, surface: &WlSurface, id: u32, position: Vector2<f32>) {
let Some(touch) = self.touch.get() else {return};
touch.down(
SERIAL_COUNTER.inc(),
0,
surface,
id as i32,
position.x as f64,
position.y as f64,
);
self.touches.lock().insert(surface.id(), id);
}
pub fn touch_move(&self, id: u32, position: Vector2<f32>) {
let Some(touch) = self.touch.get() else {return};
touch.motion(0, id as i32, position.x as f64, position.y as f64);
}
pub fn touch_up(&self, id: u32) {
let Some(touch) = self.touch.get() else {return};
touch.up(SERIAL_COUNTER.inc(), 0, id as i32);
let mut touches = self.touches.lock();
touches.retain(|_, tid| *tid != id);
}
pub fn reset_touches(&self) {
let Some(touch) = self.touch.get() else {return};
for (_, touch_id) in self.touches.lock().drain() {
touch.up(SERIAL_COUNTER.inc(), 0, touch_id as i32);
}
} }
} }
impl Drop for SeatData {
fn drop(&mut self) { pub struct CursorInfo {
let id = self.global_id.take().unwrap(); pub surface: WlWeak<WlSurface>,
let _ = task::new(|| "global destroy queue garbage collection", async move { pub hotspot_x: i32,
GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await pub hotspot_y: i32,
}); }
impl CursorInfo {
pub fn cursor_data(&self) -> Option<Geometry> {
let cursor_size = CoreSurface::from_wl_surface(&self.surface.upgrade().ok()?)?.size()?;
Some(Geometry {
origin: [self.hotspot_x, self.hotspot_y].into(),
size: cursor_size,
})
} }
} }
@@ -388,11 +488,12 @@ impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
resource.name(nanoid!()); resource.name(nanoid!());
} }
resource.capabilities(Capability::Pointer | Capability::Keyboard); resource.capabilities(Capability::Pointer | Capability::Keyboard | Capability::Touch);
} }
fn can_view(client: Client, data: &Arc<SeatData>) -> bool { fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
client.id() == data.client let Some(seat_client) = data.client.get().cloned() else {return false};
client.id() == seat_client
} }
} }
@@ -413,7 +514,9 @@ impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
} }
wl_seat::Request::GetKeyboard { id } => { wl_seat::Request::GetKeyboard { id } => {
let keyboard = data_init.init(id, data.clone()); let keyboard = data_init.init(id, data.clone());
keyboard.repeat_info(0, 0); if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null()))); let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null())));
} }
wl_seat::Request::GetTouch { id } => { wl_seat::Request::GetTouch { id } => {
@@ -425,12 +528,9 @@ impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
} }
} }
pub struct Cursor {
pub hotspot: Vector2<i32>,
}
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState { impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
fn request( fn request(
state: &mut WaylandState, _state: &mut WaylandState,
_client: &Client, _client: &Client,
_resource: &WlPointer, _resource: &WlPointer,
request: wl_pointer::Request, request: wl_pointer::Request,
@@ -446,16 +546,8 @@ impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
hotspot_y, hotspot_y,
} => { } => {
if let Some(surface) = surface.as_ref() { if let Some(surface) = surface.as_ref() {
CoreSurface::add_to(&state.display, dh.clone(), surface, || (), |_| ()); CoreSurface::add_to(dh.clone(), surface, || (), |_| ());
compositor::with_states(surface, |data| { compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| {
Arc::new(Mutex::new(Cursor {
hotspot: Vector2::from([hotspot_x, hotspot_y]),
}))
});
let mut cursor = data.data_map.get::<Arc<Mutex<Cursor>>>().unwrap().lock();
cursor.hotspot = Vector2::from([hotspot_x, hotspot_y]);
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() { if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1); core_surface.set_material_offset(1);
} }
@@ -466,8 +558,12 @@ impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
let focus = focus.lock(); let focus = focus.lock();
let surfaces = seat_data.surfaces.lock(); let surfaces = seat_data.surfaces.lock();
let Some(surface_info) = surfaces.get(&focus) else {return}; let Some(surface_info) = surfaces.get(&focus) else {return};
let Some(panel_item) = surface_info.panel_item.upgrade() else {return}; let cursor_info = surface.map(|surface| CursorInfo {
panel_item.set_cursor(surface.as_ref(), hotspot_x, hotspot_y); surface: surface.downgrade(),
hotspot_x,
hotspot_y,
});
let _ = surface_info.cursor_sender.send_replace(cursor_info);
} }
wl_pointer::Request::Release => (), wl_pointer::Request::Release => (),
_ => unreachable!(), _ => unreachable!(),

View File

@@ -1,16 +0,0 @@
#version 320 es
precision mediump float;
precision highp int;
layout(binding = 0) uniform highp sampler2D diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 _entryPointOutput;
void main()
{
highp vec4 _101 = texture(diffuse, fs_uv);
highp vec3 _104 = pow(_101.xyz, vec3(2.2000000476837158203125));
_entryPointOutput = vec4(_104.x, _104.y, _104.z, _101.w);
}

View File

@@ -1,13 +0,0 @@
#![allow(dead_code)]
// Basic gamma correction shader
// pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
// Simula text shader (fragment)
// pub const SIMULA_FRAG_STR: &str = include_str!("simula.frag");
// Simula text shader (vertex)
// pub const SIMULA_VERT_STR: &str = include_str!("simula.vert");

View File

@@ -1,75 +0,0 @@
#version 320 es
#extension GL_OES_EGL_image_external : require
precision mediump float;
precision highp int;
layout(binding = 0, std140) uniform _Global
{
highp vec4 diffuse_i;
highp vec2 uv_scale;
highp vec2 uv_offset;
highp float fcFactor;
highp float ripple;
highp float alpha_min;
highp float alpha_max;
} uniforms;
layout(binding = 0) uniform highp samplerExternalOES diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 fragColor;
void main()
{
highp vec2 dx_uv = dFdx(fs_uv);
highp vec2 dy_uv = dFdy(fs_uv);
highp vec2 width = fs_uv * uniforms.diffuse_i.xy;
ivec2 _475 = ivec2(width);
highp vec2 _477 = clamp(floor(abs(vec2(dx_uv.x, dy_uv.y)) * uniforms.diffuse_i.xy), vec2(1.0), vec2(2.0));
ivec2 _480 = ivec2(_477);
ivec2 _481 = _475 - _480;
ivec2 _485 = _475 + _480;
int _487 = _481.y;
highp vec4 _671;
highp float _672;
_672 = 0.0;
_671 = vec4(0.0);
highp vec4 _679;
highp float _681;
for (int _670 = _487; _670 <= _485.y; _672 = _681, _671 = _679, _670++)
{
int _496 = _481.x;
_681 = _672;
_679 = _671;
highp vec4 _553;
highp float _556;
for (int _673 = _496; _673 <= _485.x; _681 = _556, _679 = _553, _673++)
{
highp float _509 = float(_673);
highp float _514 = (uniforms.fcFactor * (width.x - _509)) / _477.x;
highp float _520 = float(_670);
highp float _525 = (uniforms.fcFactor * (width.y - _520)) / _477.y;
highp float _533 = sqrt((_514 * _514) + (_525 * _525));
highp float _675;
do
{
if (_533 > 1.0)
{
_675 = 0.0;
break;
}
highp float _592 = pow(uniforms.ripple * sqrt(1.0 - (_533 * _533)), 2.0);
_675 = 1.0 + (_592 * (0.25 + (_592 * (0.015625 + (_592 * (0.00043402801384218037128448486328125 + (_592 * (6.7816799855791032314300537109375e-06 + (_592 * (6.7816799287356843706220388412476e-08 + (_592 * (4.709500012189948847662890329957e-10 + (_592 * (2.4028099388645474121517509047408e-12 + (_592 * (9.3859703944590075486154034933861e-15 + (_592 * (2.8968999943407451927966655969016e-17 + (7.242260299760125752555485045131e-20 * _592)))))))))))))))))));
break;
} while(false);
_553 = _679 + (texture2D(diffuse, (vec2(_509, _520) + vec2(0.5)) / uniforms.diffuse_i.xy) * _675);
_556 = _681 + _675;
}
}
highp vec4 _568 = _671 / vec4(_672);
highp vec3 _417 = pow(_568.xyz, vec3(2.2000000476837158203125));
highp vec4 _669 = vec4(_417.x, _417.y, _417.z, _568.w);
_669.w = uniforms.alpha_min + (_568.w * (uniforms.alpha_max - uniforms.alpha_min));
fragColor = _669;
}

View File

@@ -1,61 +0,0 @@
#version 320 es
// #ifdef GL_AMD_vertex_shader_layer
// #extension GL_AMD_vertex_shader_layer : enable
// #elif defined(GL_NV_viewport_array2)
// #extension GL_NV_viewport_array2 : enable
// #else
// #define gl_Layer int _dummy_gl_layer_var
// #endif
struct Inst
{
mat4 world;
vec4 color;
};
layout(binding = 1, std140) uniform StereoKitBuffer
{
layout(row_major) mat4 sk_view[2];
layout(row_major) mat4 sk_proj[2];
layout(row_major) mat4 sk_proj_inv[2];
layout(row_major) mat4 sk_viewproj[2];
vec4 sk_lighting_sh[9];
vec4 sk_camera_pos[2];
vec4 sk_camera_dir[2];
vec4 sk_fingertip[2];
vec4 sk_cubemap_i;
float sk_time;
uint sk_view_count;
} _38;
layout(binding = 2, std140) uniform TransformBuffer
{
layout(row_major) Inst sk_inst[819];
} _56;
layout(binding = 0, std140) uniform _Global
{
vec4 diffuse_i;
vec2 uv_scale;
vec2 uv_offset;
float fcFactor;
float ripple;
float alpha_min;
float alpha_max;
} _91;
layout(location = 0) in vec4 input_pos;
layout(location = 1) in vec3 input_norm;
layout(location = 2) in vec2 input_uv;
layout(location = 0) out vec2 fs_uv;
mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; }
void main()
{
uint _155 = uint(gl_InstanceID) % _38.sk_view_count;
gl_Position = spvWorkaroundRowMajor(_38.sk_viewproj[_155]) * vec4((spvWorkaroundRowMajor(_56.sk_inst[uint(gl_InstanceID) / _38.sk_view_count].world) * vec4(input_pos.xyz, 1.0)).xyz, 1.0);
fs_uv = (input_uv + _91.uv_offset) * _91.uv_scale;
// gl_Layer = int(_155);
}

View File

@@ -1,11 +1,12 @@
use crate::wayland::seat::SeatData; use super::DisplayWrapper;
use crate::wayland::{drm::wl_drm::WlDrm, seat::SeatData};
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::{ use smithay::{
backend::{ backend::{
allocator::dmabuf::Dmabuf, allocator::{dmabuf::Dmabuf, Fourcc},
egl::EGLDevice, egl::EGLDevice,
renderer::{gles::GlesRenderer, ImportDma}, renderer::gles::GlesRenderer,
}, },
delegate_dmabuf, delegate_output, delegate_shm, delegate_dmabuf, delegate_output, delegate_shm,
output::{Mode, Output, Scale, Subpixel}, output::{Mode, Output, Scale, Subpixel},
@@ -18,7 +19,7 @@ use smithay::{
wayland_server::{ wayland_server::{
backend::{ClientData, ClientId, DisconnectReason}, backend::{ClientData, ClientId, DisconnectReason},
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager}, protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
Display, DisplayHandle, DisplayHandle,
}, },
}, },
utils::{Size, Transform}, utils::{Size, Transform},
@@ -27,7 +28,6 @@ use smithay::{
compositor::{CompositorClientState, CompositorState}, compositor::{CompositorClientState, CompositorState},
dmabuf::{ dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
ImportError,
}, },
shell::kde::decoration::KdeDecorationState, shell::kde::decoration::KdeDecorationState,
shm::{ShmHandler, ShmState}, shm::{ShmHandler, ShmState},
@@ -37,13 +37,24 @@ use std::sync::{Arc, Weak};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn}; use tracing::{info, warn};
#[derive(Default)]
pub struct ClientState { pub struct ClientState {
pub id: OnceCell<ClientId>,
pub compositor_state: CompositorClientState, pub compositor_state: CompositorClientState,
pub display: Weak<DisplayWrapper>,
pub seat: Arc<SeatData>,
}
impl ClientState {
pub fn flush(&self) {
let Some(display) = self.display.upgrade() else {
return;
};
let _ = display.flush_clients(self.id.get().cloned());
}
} }
impl ClientData for ClientState { impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) { fn initialized(&self, client_id: ClientId) {
info!("Wayland client {:?} connected", client_id); info!("Wayland client {:?} connected", client_id);
let _ = self.id.set(client_id);
} }
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) { fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
@@ -56,7 +67,6 @@ impl ClientData for ClientState {
pub struct WaylandState { pub struct WaylandState {
pub weak_ref: Weak<Mutex<WaylandState>>, pub weak_ref: Weak<Mutex<WaylandState>>,
pub display: Arc<Mutex<Display<WaylandState>>>,
pub display_handle: DisplayHandle, pub display_handle: DisplayHandle,
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
@@ -64,17 +74,16 @@ pub struct WaylandState {
pub kde_decoration_state: KdeDecorationState, pub kde_decoration_state: KdeDecorationState,
pub shm_state: ShmState, pub shm_state: ShmState,
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>), dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
dmabuf_tx: UnboundedSender<Dmabuf>, pub drm_formats: Vec<Fourcc>,
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
pub output: Output, pub output: Output,
pub seats: FxHashMap<ClientId, Arc<SeatData>>,
} }
impl WaylandState { impl WaylandState {
pub fn new( pub fn new(
display: Arc<Mutex<Display<WaylandState>>>,
display_handle: DisplayHandle, display_handle: DisplayHandle,
renderer: &GlesRenderer, renderer: &GlesRenderer,
dmabuf_tx: UnboundedSender<Dmabuf>, dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
) -> Arc<Mutex<Self>> { ) -> Arc<Mutex<Self>> {
let compositor_state = CompositorState::new::<Self>(&display_handle); let compositor_state = CompositorState::new::<Self>(&display_handle);
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle); // let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
@@ -83,16 +92,18 @@ impl WaylandState {
let shm_state = ShmState::new::<Self>(&display_handle, vec![]); let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
let render_node = EGLDevice::device_for_display(renderer.egl_context().display()) let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
.and_then(|device| device.try_get_render_node()); .and_then(|device| device.try_get_render_node());
let dmabuf_formats = renderer
.egl_context()
.dmabuf_render_formats()
.iter()
.cloned()
.collect::<Vec<_>>();
let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect();
let dmabuf_default_feedback = match render_node { let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => { Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>(); .build()
let dmabuf_default_feedback = .ok(),
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => { Ok(None) => {
warn!("failed to query render node, dmabuf will use v3"); warn!("failed to query render node, dmabuf will use v3");
None None
@@ -112,10 +123,9 @@ impl WaylandState {
); );
(dmabuf_state, dmabuf_global, Some(default_feedback)) (dmabuf_state, dmabuf_global, Some(default_feedback))
} else { } else {
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
let mut dmabuf_state = DmabufState::new(); let mut dmabuf_state = DmabufState::new();
let dmabuf_global = let dmabuf_global =
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats); dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats.clone());
(dmabuf_state, dmabuf_global, None) (dmabuf_state, dmabuf_global, None)
}; };
@@ -130,7 +140,7 @@ impl WaylandState {
); );
let _output_global = output.create_global::<Self>(&display_handle); let _output_global = output.create_global::<Self>(&display_handle);
let mode = Mode { let mode = Mode {
size: (4096, 4096).into(), size: (2048, 2048).into(),
refresh: 60000, refresh: 60000,
}; };
output.change_current_state( output.change_current_state(
@@ -143,31 +153,26 @@ impl WaylandState {
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ()); display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, XdgWmBase, _>(5, ()); display_handle.create_global::<Self, XdgWmBase, _>(5, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ()); display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
display_handle.create_global::<Self, WlDrm, _>(2, ());
info!("Init Wayland compositor"); info!("Init Wayland compositor");
Arc::new_cyclic(|weak| { Arc::new_cyclic(|weak| {
Mutex::new(WaylandState { Mutex::new(WaylandState {
weak_ref: weak.clone(), weak_ref: weak.clone(),
display,
display_handle, display_handle,
compositor_state, compositor_state,
// xdg_activation_state, // xdg_activation_state,
kde_decoration_state, kde_decoration_state,
shm_state, shm_state,
drm_formats,
dmabuf_state, dmabuf_state,
dmabuf_tx, dmabuf_tx,
output, output,
seats: FxHashMap::default(),
}) })
}) })
} }
pub fn new_client(&mut self, client: ClientId, dh: &DisplayHandle) {
let seat_data = SeatData::new(dh, client.clone());
self.seats.insert(client, seat_data);
}
} }
impl Drop for WaylandState { impl Drop for WaylandState {
fn drop(&mut self) { fn drop(&mut self) {
@@ -191,8 +196,9 @@ impl DmabufHandler for WaylandState {
&mut self, &mut self,
_global: &DmabufGlobal, _global: &DmabufGlobal,
dmabuf: Dmabuf, dmabuf: Dmabuf,
) -> Result<(), dmabuf::ImportError> { notifier: dmabuf::ImportNotifier,
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed) ) {
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap();
} }
} }
delegate_dmabuf!(WaylandState); delegate_dmabuf!(WaylandState);

View File

@@ -1,7 +1,7 @@
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState}; use super::state::WaylandState;
use crate::{ use crate::{
core::{delta::Delta, destroy_queue, registry::Registry}, core::{delta::Delta, destroy_queue, registry::Registry},
nodes::drawable::model::ModelPart, nodes::drawable::{model::ModelPart, shaders::PANEL_SHADER_BYTES},
}; };
use mint::Vector2; use mint::Vector2;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -15,19 +15,13 @@ use smithay::{
}, },
desktop::utils::send_frames_surface_tree, desktop::utils::send_frames_surface_tree,
output::Output, output::Output,
reexports::wayland_server::{ reexports::wayland_server::{self, protocol::wl_surface::WlSurface, DisplayHandle, Resource},
self, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
},
wayland::compositor::{self, SurfaceData}, wayland::compositor::{self, SurfaceData},
}; };
use std::{ use std::{cell::RefCell, ffi::c_void, sync::Arc, time::Duration};
ffi::c_void,
sync::{Arc, Weak},
time::Duration,
};
use stereokit::{ use stereokit::{
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType, Material, Shader, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample,
Transparency, TextureType, Transparency,
}; };
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new(); pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
@@ -43,12 +37,11 @@ impl Drop for CoreSurfaceData {
} }
pub struct CoreSurface { pub struct CoreSurface {
display: Weak<Mutex<Display<WaylandState>>>,
pub dh: DisplayHandle, pub dh: DisplayHandle,
pub weak_surface: wayland_server::Weak<WlSurface>, pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>, mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceCell<SendWrapper<Tex>>, sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<SendWrapper<Material>>>, sk_mat: OnceCell<Arc<Material>>,
material_offset: Mutex<Delta<u32>>, material_offset: Mutex<Delta<u32>>,
on_mapped: Box<dyn Fn() + Send + Sync>, on_mapped: Box<dyn Fn() + Send + Sync>,
on_commit: Box<dyn Fn(u32) + Send + Sync>, on_commit: Box<dyn Fn(u32) + Send + Sync>,
@@ -57,7 +50,6 @@ pub struct CoreSurface {
impl CoreSurface { impl CoreSurface {
pub fn add_to( pub fn add_to(
display: &Arc<Mutex<Display<WaylandState>>>,
dh: DisplayHandle, dh: DisplayHandle,
surface: &WlSurface, surface: &WlSurface,
on_mapped: impl Fn() + Send + Sync + 'static, on_mapped: impl Fn() + Send + Sync + 'static,
@@ -66,7 +58,6 @@ impl CoreSurface {
compositor::with_states(surface, |data| { compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| { data.data_map.insert_if_missing_threadsafe(|| {
CORE_SURFACES.add(CoreSurface { CORE_SURFACES.add(CoreSurface {
display: Arc::downgrade(display),
dh, dh,
weak_surface: surface.downgrade(), weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None), mapped_data: Mutex::new(None),
@@ -92,21 +83,21 @@ impl CoreSurface {
} }
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) { pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else { return }; let Some(wl_surface) = self.wl_surface() else {return};
let sk_tex = self.sk_tex.get_or_init(|| { let sk_tex = self
SendWrapper::new(sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32)) .sk_tex
}); .get_or_init(|| sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32));
self.sk_mat.get_or_init(|| { self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES).unwrap(); let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES);
// let _ = renderer.with_context(|c| unsafe { // let _ = renderer.with_context(|c| unsafe {
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR) // shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
// }); // });
let mat = sk.material_create(&shader); let mat = sk.material_create(shader.as_ref().unwrap_or(Shader::UI.as_ref()));
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref()); sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
sk.material_set_transparency(&mat, Transparency::Blend); sk.material_set_transparency(&mat, Transparency::Blend);
Arc::new(SendWrapper::new(mat)) Arc::new(mat)
}); });
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe) // Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
@@ -130,18 +121,16 @@ impl CoreSurface {
let mut mapped_data = self.mapped_data.lock(); let mut mapped_data = self.mapped_data.lock();
let just_mapped = mapped_data.is_none(); let just_mapped = mapped_data.is_none();
self.with_states(|data| { self.with_states(|data| {
let renderer_surface_state = data let Some(renderer_surface_state) = data
.data_map .data_map
.get::<RendererSurfaceStateUserData>() .get::<RendererSurfaceStateUserData>()
.unwrap() .map(RefCell::borrow) else {return};
.borrow(); let Some(smithay_tex) = renderer_surface_state
let smithay_tex = renderer_surface_state
.texture::<GlesRenderer>(renderer.id()) .texture::<GlesRenderer>(renderer.id())
.unwrap() .cloned() else {return};
.clone();
let sk_tex = self.sk_tex.get().unwrap(); let Some(sk_tex) = self.sk_tex.get() else {return};
let sk_mat = self.sk_mat.get().unwrap(); let Some(sk_mat) = self.sk_mat.get() else {return};
unsafe { unsafe {
sk.tex_set_surface( sk.tex_set_surface(
sk_tex.as_ref(), sk_tex.as_ref(),
@@ -160,7 +149,7 @@ impl CoreSurface {
sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32); sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32);
} }
let surface_size = renderer_surface_state.surface_size().unwrap(); let Some(surface_size) = renderer_surface_state.surface_size() else {return};
let new_mapped_data = CoreSurfaceData { let new_mapped_data = CoreSurfaceData {
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]), size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
wl_tex: Some(SendWrapper::new(smithay_tex)), wl_tex: Some(SendWrapper::new(smithay_tex)),
@@ -175,7 +164,7 @@ impl CoreSurface {
} }
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) { pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
let Some(wl_surface) = self.wl_surface() else { return }; let Some(wl_surface) = self.wl_surface() else {return};
send_frames_surface_tree( send_frames_surface_tree(
&wl_surface, &wl_surface,
@@ -190,15 +179,17 @@ impl CoreSurface {
*self.material_offset.lock().value_mut() = material_offset; *self.material_offset.lock().value_mut() = material_offset;
} }
pub fn apply_material(&self, model_node: &Arc<ModelPart>) { pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
self.pending_material_applications.add_raw(model_node) self.pending_material_applications.add_raw(model_part)
} }
fn apply_surface_materials(&self) { fn apply_surface_materials(&self) {
for model_node in self.pending_material_applications.get_valid_contents() { if let Some(sk_mat) = self.sk_mat.get() {
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone()); for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(sk_mat.clone());
}
self.pending_material_applications.clear();
} }
self.pending_material_applications.clear();
} }
pub fn wl_surface(&self) -> Option<WlSurface> { pub fn wl_surface(&self) -> Option<WlSurface> {
@@ -216,15 +207,6 @@ impl CoreSurface {
pub fn size(&self) -> Option<Vector2<u32>> { pub fn size(&self) -> Option<Vector2<u32>> {
self.mapped_data.lock().as_ref().map(|d| d.size) self.mapped_data.lock().as_ref().map(|d| d.size)
} }
pub fn flush_clients(&self) {
self.display
.upgrade()
.unwrap()
.lock()
.flush_clients()
.unwrap();
}
} }
impl Drop for CoreSurface { impl Drop for CoreSurface {
fn drop(&mut self) { fn drop(&mut self) {

189
src/wayland/wayland-drm.xml Normal file
View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="drm">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<!-- drm support. This object is created by the server and published
using the display's global event. -->
<interface name="wl_drm" version="2">
<enum name="error">
<entry name="authenticate_fail" value="0" />
<entry name="invalid_format" value="1" />
<entry name="invalid_name" value="2" />
</enum>
<enum name="format">
<!-- The drm format codes match the #defines in drm_fourcc.h.
The formats actually supported by the compositor will be
reported by the format event. New codes must not be added,
unless directly taken from drm_fourcc.h. -->
<entry name="c8" value="0x20203843" />
<entry name="rgb332" value="0x38424752" />
<entry name="bgr233" value="0x38524742" />
<entry name="xrgb4444" value="0x32315258" />
<entry name="xbgr4444" value="0x32314258" />
<entry name="rgbx4444" value="0x32315852" />
<entry name="bgrx4444" value="0x32315842" />
<entry name="argb4444" value="0x32315241" />
<entry name="abgr4444" value="0x32314241" />
<entry name="rgba4444" value="0x32314152" />
<entry name="bgra4444" value="0x32314142" />
<entry name="xrgb1555" value="0x35315258" />
<entry name="xbgr1555" value="0x35314258" />
<entry name="rgbx5551" value="0x35315852" />
<entry name="bgrx5551" value="0x35315842" />
<entry name="argb1555" value="0x35315241" />
<entry name="abgr1555" value="0x35314241" />
<entry name="rgba5551" value="0x35314152" />
<entry name="bgra5551" value="0x35314142" />
<entry name="rgb565" value="0x36314752" />
<entry name="bgr565" value="0x36314742" />
<entry name="rgb888" value="0x34324752" />
<entry name="bgr888" value="0x34324742" />
<entry name="xrgb8888" value="0x34325258" />
<entry name="xbgr8888" value="0x34324258" />
<entry name="rgbx8888" value="0x34325852" />
<entry name="bgrx8888" value="0x34325842" />
<entry name="argb8888" value="0x34325241" />
<entry name="abgr8888" value="0x34324241" />
<entry name="rgba8888" value="0x34324152" />
<entry name="bgra8888" value="0x34324142" />
<entry name="xrgb2101010" value="0x30335258" />
<entry name="xbgr2101010" value="0x30334258" />
<entry name="rgbx1010102" value="0x30335852" />
<entry name="bgrx1010102" value="0x30335842" />
<entry name="argb2101010" value="0x30335241" />
<entry name="abgr2101010" value="0x30334241" />
<entry name="rgba1010102" value="0x30334152" />
<entry name="bgra1010102" value="0x30334142" />
<entry name="yuyv" value="0x56595559" />
<entry name="yvyu" value="0x55595659" />
<entry name="uyvy" value="0x59565955" />
<entry name="vyuy" value="0x59555956" />
<entry name="ayuv" value="0x56555941" />
<entry name="xyuv8888" value="0x56555958" />
<entry name="nv12" value="0x3231564e" />
<entry name="nv21" value="0x3132564e" />
<entry name="nv16" value="0x3631564e" />
<entry name="nv61" value="0x3136564e" />
<entry name="yuv410" value="0x39565559" />
<entry name="yvu410" value="0x39555659" />
<entry name="yuv411" value="0x31315559" />
<entry name="yvu411" value="0x31315659" />
<entry name="yuv420" value="0x32315559" />
<entry name="yvu420" value="0x32315659" />
<entry name="yuv422" value="0x36315559" />
<entry name="yvu422" value="0x36315659" />
<entry name="yuv444" value="0x34325559" />
<entry name="yvu444" value="0x34325659" />
<entry name="abgr16f" value="0x48344241" />
<entry name="xbgr16f" value="0x48344258" />
</enum>
<!-- Call this request with the magic received from drmGetMagic().
It will be passed on to the drmAuthMagic() or
DRIAuthConnection() call. This authentication must be
completed before create_buffer could be used. -->
<request name="authenticate">
<arg name="id" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="stride" type="uint" />
<arg name="format" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_planar_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
<!-- Notification of the path of the drm device which is used by
the server. The client should use this device for creating
local buffers. Only buffers created from this device should
be be passed to the server using this drm object's
create_buffer request. -->
<event name="device">
<arg name="name" type="string" />
</event>
<event name="format">
<arg name="format" type="uint" />
</event>
<!-- Raised if the authenticate request succeeded -->
<event name="authenticated" />
<enum name="capability" since="2">
<description summary="wl_drm capability bitmask">
Bitmask of capabilities.
</description>
<entry name="prime" value="1" summary="wl_drm prime available" />
</enum>
<event name="capabilities">
<arg name="value" type="uint" />
</event>
<!-- Version 2 additions -->
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
buffers. Pass 0 for offset and stride for unused planes. -->
<request name="create_prime_buffer" since="2">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="fd" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
use crate::core::{client::get_env, task};
use crate::STOP_NOTIFIER;
use smithay::reexports::rustix;
use smithay::reexports::rustix::io::{fcntl_setfd, Errno, FdFlags};
use smithay::reexports::rustix::net::SocketAddrUnix;
use std::io::{Read, Write};
use std::{
io::ErrorKind,
os::{
fd::{AsRawFd, BorrowedFd, RawFd},
unix::process::CommandExt,
},
process::{ChildStdout, Command, Stdio},
};
use tokio::net::{UnixListener, UnixStream};
use tokio::task::AbortHandle;
use tracing::{debug, info, warn};
use super::X_DISPLAY;
pub fn start_xwayland(wayland_socket: RawFd) -> std::io::Result<X11Lock> {
let (mut lock, listener) = bind_socket()?;
let abort_handle = task::new(|| "X11 Client Acceptor", async move {
loop {
let Ok((stream, _)) = tokio::select! {
_ = STOP_NOTIFIER.notified() => break,
e = listener.accept() => e,
} else {
continue;
};
let Ok((x_wm_x11, _x_wm_me)) = UnixStream::pair() else {
continue;
};
let Ok(env) = stream
.peer_cred()
.and_then(|c| c.pid().ok_or(ErrorKind::Other.into()))
.and_then(get_env)
else {
continue;
};
let _ = spawn_xwayland(
lock.display,
wayland_socket,
x_wm_x11,
stream.as_raw_fd(),
env.get("STARDUST_STARTUP_TOKEN").cloned(),
);
}
})
.map_err(|_| ErrorKind::Other)?
.abort_handle();
lock.x_abort_handle.replace(abort_handle);
let _ = X_DISPLAY.set(lock.display);
Ok(lock)
}
/// Find a free X11 display slot and setup
pub(crate) fn bind_socket() -> Result<(X11Lock, UnixListener), std::io::Error> {
for d in 0..33 {
// if fails, try the next one
if let Ok(lock) = X11Lock::grab(d) {
// we got a lockfile, try and create the socket
match open_x11_socket_for_display(d) {
Ok(socket) => return Ok((lock, socket)),
Err(err) => warn!(display = d, "Failed to create sockets: {}", err),
}
}
}
// If we reach here, all values from 0 to 32 failed
// we need to stop trying at some point
Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"Could not find a free socket for the XServer.",
))
}
#[derive(Debug)]
pub(crate) struct X11Lock {
display: u32,
x_abort_handle: Option<AbortHandle>,
}
impl X11Lock {
/// Try to grab a lockfile for given X display number
fn grab(number: u32) -> Result<X11Lock, ()> {
debug!(display = number, "Attempting to aquire an X11 display lock");
let filename = format!("/tmp/.X{}-lock", number);
let lockfile = ::std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&filename);
match lockfile {
Ok(mut file) => {
// we got it, write our PID in it and we're good
let ret = file.write_fmt(format_args!(
"{:>10}\n",
rustix::process::Pid::as_raw(Some(rustix::process::getpid()))
));
if ret.is_err() {
// write to the file failed ? we abandon
::std::mem::drop(file);
let _ = ::std::fs::remove_file(&filename);
Err(())
} else {
debug!(display = number, "X11 lock acquired");
// we got the lockfile and wrote our pid to it, all is good
Ok(X11Lock {
display: number,
x_abort_handle: None,
})
}
}
Err(_) => {
debug!(display = number, "Failed to acquire lock");
// we could not open the file, now we try to read it
// and if it contains the pid of a process that no longer
// exist (so if a previous x server claimed it and did not
// exit gracefully and remove it), we claim it
// if we can't open it, give up
let mut file = ::std::fs::File::open(&filename).map_err(|_| ())?;
let mut spid = [0u8; 11];
file.read_exact(&mut spid).map_err(|_| ())?;
::std::mem::drop(file);
let pid = rustix::process::Pid::from_raw(
::std::str::from_utf8(&spid)
.map_err(|_| ())?
.trim()
.parse::<i32>()
.map_err(|_| ())?,
)
.ok_or(())?;
if let Err(Errno::SRCH) = rustix::process::test_kill_process(pid) {
// no process whose pid equals the contents of the lockfile exists
// remove the lockfile and try grabbing it again
if let Ok(()) = ::std::fs::remove_file(filename) {
debug!(
display = number,
"Lock was blocked by a defunct X11 server, trying again"
);
return X11Lock::grab(number);
} else {
// we could not remove the lockfile, abort
return Err(());
}
}
// if we reach here, this lockfile exists and is probably in use, give up
Err(())
}
}
}
pub(crate) fn display(&self) -> u32 {
self.display
}
}
impl Drop for X11Lock {
fn drop(&mut self) {
info!("Cleaning up X11 lock.");
// Cleanup all the X11 files
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X11-unix/X{}", self.display)) {
warn!(error = ?e, "Failed to remove X11 socket");
}
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X{}-lock", self.display)) {
warn!(error = ?e, "Failed to remove X11 lockfile");
}
if let Some(join_handle) = self.x_abort_handle.take() {
join_handle.abort();
}
}
}
/// Open the two unix sockets an X server listens on
///
/// Should only be done after the associated lockfile is acquired!
fn open_x11_socket_for_display(display: u32) -> rustix::io::Result<UnixListener> {
let path = format!("/tmp/.X11-unix/X{}", display);
let _ = ::std::fs::remove_file(&path);
// We know this path is not too long, these unwrap cannot fail
let fs_addr = SocketAddrUnix::new(path.as_bytes()).unwrap();
open_socket(fs_addr)
}
/// Open an unix socket for listening and bind it to given path
fn open_socket(addr: SocketAddrUnix) -> rustix::io::Result<UnixListener> {
// create an unix stream socket
let fd = rustix::net::socket_with(
rustix::net::AddressFamily::UNIX,
rustix::net::SocketType::STREAM,
rustix::net::SocketFlags::CLOEXEC,
None,
)?;
// bind it to requested address
rustix::net::bind_unix(&fd, &addr)?;
rustix::net::listen(&fd, 1)?;
Ok(UnixListener::from_std(std::os::unix::net::UnixListener::from(fd)).unwrap())
}
fn spawn_xwayland(
display: u32,
wayland_socket: RawFd,
wm_socket: UnixStream,
listen_socket: RawFd,
stardust_startup_token: Option<String>,
) -> std::io::Result<ChildStdout> {
let mut command = Command::new("sh");
// We use output stream to communicate because FD is easier to handle than exit code.
command.stdout(Stdio::piped());
let mut xwayland_args = format!(":{} -geometry 1920x1080", display);
xwayland_args.push_str(&format!(" -listenfd {}", listen_socket));
// This command let sh to:
// * Set up signal handler for USR1
// * Launch Xwayland with USR1 ignored so Xwayland will signal us when it is ready (also redirect
// Xwayland's STDOUT to STDERR so its output, if any, won't distract us)
// * Print "S" and exit if USR1 is received
command.arg("-c").arg(format!(
"trap 'echo S' USR1; (trap '' USR1; exec Xwayland {}) 1>&2 & wait",
xwayland_args
));
// Setup the environment: clear everything except PATH and XDG_RUNTIME_DIR
command.env_clear();
for (key, value) in std::env::vars_os() {
if key.to_str() == Some("PATH") || key.to_str() == Some("XDG_RUNTIME_DIR") {
command.env(key, value);
continue;
}
}
command.env("WAYLAND_SOCKET", format!("{}", wayland_socket.as_raw_fd()));
command.env(
"STARDUST_STARTUP_TOKEN",
stardust_startup_token.unwrap_or_default(),
);
unsafe {
let wayland_socket_fd = wayland_socket.as_raw_fd();
let wm_socket_fd = wm_socket.as_raw_fd();
command.pre_exec(move || {
// unset the CLOEXEC flag from the sockets we need to pass
// to xwayland
unset_cloexec(wayland_socket_fd)?;
unset_cloexec(wm_socket_fd)?;
unset_cloexec(listen_socket)?;
Ok(())
});
}
let mut child = command.spawn()?;
Ok(child.stdout.take().expect("stdout should be piped"))
}
/// Remove the `O_CLOEXEC` flag from this `Fd`
///
/// This means that the `Fd` will *not* be automatically
/// closed when we `exec()` into XWayland
unsafe fn unset_cloexec(fd: RawFd) -> std::io::Result<()> {
let fd = BorrowedFd::borrow_raw(fd);
fcntl_setfd(fd, FdFlags::empty())?;
Ok(())
}

View File

@@ -0,0 +1,430 @@
use super::{
seat::{KeyboardEvent, PointerEvent, SeatData},
X_DISPLAY,
};
use crate::{
nodes::{
data::KEYMAPS,
drawable::model::ModelPart,
items::panel::{Backend, Geometry, PanelItem, PanelItemInitData, SurfaceID, ToplevelInfo},
},
wayland::surface::CoreSurface,
};
use color_eyre::eyre::Result;
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::{
reexports::{
calloop::{EventLoop, LoopSignal},
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
x11rb::protocol::xproto::Window,
},
utils::{Logical, Rectangle},
wayland::compositor,
xwayland::{
xwm::{Reorder, ResizeEdge, XwmId},
X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler,
},
};
use std::{ffi::OsStr, iter::empty, sync::Arc, time::Duration};
use tokio::sync::oneshot;
use tracing::debug;
pub struct XWaylandState {
pub display: u32,
event_loop_signal: LoopSignal,
}
impl XWaylandState {
pub fn create(dh: &DisplayHandle) -> Result<Self> {
let dh = dh.clone();
let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
let mut event_loop: EventLoop<XWaylandHandler> = EventLoop::try_new()?;
let (xwayland, connection) = XWayland::new(&dh);
let handle = event_loop.handle();
event_loop
.handle()
.insert_source(connection, {
let dh = dh.clone();
move |event, _, handler| match event {
XWaylandEvent::Ready {
connection,
client,
client_fd: _,
display: _,
} => {
handler.seat.client.set(client.id()).unwrap();
handler
.wm
.set(
X11Wm::start_wm(handle.clone(), dh.clone(), connection, client)
.unwrap(),
)
.unwrap();
}
XWaylandEvent::Exited => (),
}
})
.map_err(|e| e.error)?;
let display = xwayland.start(
event_loop.handle(),
None,
empty::<(&OsStr, &OsStr)>(),
true,
|_| (),
)?;
let _ = tx.send(XWaylandState {
display,
event_loop_signal: event_loop.get_signal(),
});
let mut handler = XWaylandHandler {
wm: OnceCell::new(),
seat: SeatData::new(&dh),
wayland_display_handle: dh,
};
event_loop.run(Duration::from_millis(100), &mut handler, |_| ())
});
let state = rx.blocking_recv()?;
let _ = X_DISPLAY.set(state.display);
Ok(state)
}
}
impl Drop for XWaylandState {
fn drop(&mut self) {
self.event_loop_signal.stop();
}
}
struct XWaylandHandler {
wayland_display_handle: DisplayHandle,
wm: OnceCell<X11Wm>,
seat: Arc<SeatData>,
}
impl XWaylandHandler {
fn panel_item(&self, window: &X11Surface) -> Option<Arc<PanelItem<X11Backend>>> {
compositor::with_states(&window.wl_surface()?, |s| {
s.data_map.get::<Arc<PanelItem<X11Backend>>>().cloned()
})
}
}
impl XwmHandler for XWaylandHandler {
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
self.wm.get_mut().unwrap()
}
fn new_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "New X window");
}
fn new_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "New X override redirect window");
}
fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map window request");
window.set_mapped(true).unwrap();
}
fn map_window_notify(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map window notify");
let _ = window.set_maximized(true);
let dh = self.wayland_display_handle.clone();
let seat = self.seat.clone();
CoreSurface::add_to(
self.wayland_display_handle.clone(),
&window.wl_surface().unwrap(),
{
let window = window.clone();
move || {
let Some(wl_surface) = window.wl_surface() else {
return;
};
let seat = seat.clone();
window.user_data().insert_if_missing_threadsafe(|| {
let panel_item = PanelItem::create(
Box::new(X11Backend {
toplevel_parent: None,
toplevel: window.clone(),
seat,
_pointer_grab: Mutex::new(None),
_keyboard_grab: Mutex::new(None),
}),
wl_surface
.client()
.and_then(|c| c.get_credentials(&dh).ok())
.map(|c| c.pid),
);
panel_item
});
}
},
move |_| {
let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>()
else {
return;
};
panel_item.toplevel_size_changed(
[
window.geometry().size.w as u32,
window.geometry().size.h as u32,
]
.into(),
);
},
);
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map override redirect window");
}
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Unmap X window");
if let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>() {
panel_item.drop_toplevel();
}
}
fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Destroy X window");
}
fn configure_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
x: Option<i32>,
y: Option<i32>,
w: Option<u32>,
h: Option<u32>,
reorder: Option<Reorder>,
) {
debug!(?window, x, y, w, h, ?reorder, "Configure X window");
}
fn configure_notify(
&mut self,
_xwm: XwmId,
window: X11Surface,
geometry: Rectangle<i32, Logical>,
above: Option<Window>,
) {
debug!(?window, ?geometry, above, "Configure X window");
}
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, button: u32) {
let Some(panel_item) = self.panel_item(&window) else {
return;
};
debug!(?window, button, "X window requests move");
panel_item.toplevel_move_request();
}
fn resize_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
button: u32,
resize_edge: ResizeEdge,
) {
let Some(panel_item) = self.panel_item(&window) else {
return;
};
debug!(?window, button, ?resize_edge, "X window requests resize");
let (up, down, left, right) = match resize_edge {
ResizeEdge::Top => (true, false, false, false),
ResizeEdge::Bottom => (false, true, false, false),
ResizeEdge::Left => (false, false, true, false),
ResizeEdge::TopLeft => (true, false, true, false),
ResizeEdge::BottomLeft => (false, true, true, false),
ResizeEdge::Right => (false, false, false, true),
ResizeEdge::TopRight => (true, false, false, true),
ResizeEdge::BottomRight => (false, true, false, true),
// _ => (false, false, false, false),
};
panel_item.toplevel_resize_request(up, down, left, right)
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let _ = window.set_fullscreen(true);
let Some(panel_item) = self.panel_item(&window) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let _ = window.set_fullscreen(false);
let Some(panel_item) = self.panel_item(&window) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
}
}
pub struct X11Backend {
pub toplevel_parent: Option<X11Surface>,
pub toplevel: X11Surface,
pub seat: Arc<SeatData>,
_pointer_grab: Mutex<Option<SurfaceID>>,
_keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl X11Backend {
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => None,
SurfaceID::Toplevel => self.toplevel.wl_surface(),
SurfaceID::Child(_) => None,
}
}
// fn flush_client(&self) {
// let Some(client) = self.toplevel.wl_surface().and_then(|s| s.client()) else {return};
// if let Some(client_state) = client.get_data::<ClientState>() {
// client_state.flush();
// }
// }
}
impl Backend for X11Backend {
fn start_data(&self) -> Result<PanelItemInitData> {
Ok(PanelItemInitData {
cursor: None,
toplevel: ToplevelInfo {
parent: None,
title: Some(self.toplevel.title()),
app_id: Some(self.toplevel.instance()),
size: [
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
]
.into(),
min_size: self
.toplevel
.min_size()
.map(|s| [s.w as u32, s.h as u32].into()),
max_size: self
.toplevel
.max_size()
.map(|s| [s.w as u32, s.h as u32].into()),
logical_rectangle: Geometry {
origin: [0, 0].into(),
size: [
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
]
.into(),
},
},
children: FxHashMap::default(),
pointer_grab: self._pointer_grab.lock().clone(),
keyboard_grab: self._keyboard_grab.lock().clone(),
})
}
fn close_toplevel(&self) {
let _ = self.toplevel.close();
}
fn auto_size_toplevel(&self) {
let _ = self.toplevel.configure(None);
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ = self.toplevel.configure(Some(Rectangle {
loc: self.toplevel.geometry().loc,
size: (size.x as i32, size.y as i32).into(),
}));
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self.toplevel.set_activated(focused);
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
button,
state: if pressed { 1 } else { 0 },
},
)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
axis_continuous: scroll_distance,
axis_discrete: scroll_steps,
},
)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(keymap_id).cloned() else {
return;
};
if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() {
return;
}
for key in keys {
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key: key.abs() as u32,
state: key < 0,
},
);
}
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.touch_down(&surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.seat.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.seat.touch_up(id)
}
fn reset_touches(&self) {
self.seat.reset_touches()
}
}