116 Commits

Author SHA1 Message Date
Nova
c12300e756 refactor(cargo.toml): upgrade and refine 2024-09-05 08:52:10 -04:00
Nova
4683710f09 feat(main): debug launched clients flag 2024-09-04 05:14:43 -04:00
Nova
1ca054c2b3 feat: proper version bump 2024-08-24 20:59:38 -04:00
Nova
5f7e9d4eb6 Bump version to 0.45.0 2024-08-24 20:47:40 -04:00
6543
c9fe1be10b cargo fmt (#23) 2024-08-22 01:24:12 +00:00
6543
f58c748f80 Return dedicated error message if dbus session could not be opened (#22)
* Return dedicated error message if dbus session could not be opened

* cargo fmt
2024-08-22 01:23:27 +00:00
Nova
499aa2be28 fix(nix): FINALLY 2024-08-21 00:49:11 -04:00
Taylor Coffelt
7ba710e8b7 fix(nix): fixes nix build (#21) 2024-08-21 04:10:13 +00:00
Nova
62802367eb fix: clippy 2024-08-20 17:24:57 -04:00
Nova
853f779930 fix(model): don't keep material references 2024-08-20 17:18:48 -04:00
Nova
21d10a15ee clippy: cleanup 2024-08-20 16:59:37 -04:00
Nova
6146a5b63a fix(model): rebatch identical materials to save on draw calls 2024-08-20 16:59:15 -04:00
Nova
01485e2020 fix: stereokit rust to make it compile at all 2024-08-19 10:18:44 -04:00
Nova
6e77c3667d fix(nix): force local deps only 2024-08-15 16:13:01 -04:00
Nova
003dd9fcc1 fix: nix meshoptimizer 2024-08-13 21:51:32 -04:00
Nova
42738b739f fix(objects/input): push all buttons through 2024-08-12 19:27:51 -04:00
Nova
fe83a69bb9 feat: upgrade dependencies 2024-08-10 22:07:07 -04:00
Nova
e0a7d4f44a fix: upgrade stereokit-rust and attempt nix upgrade 2024-08-07 14:36:19 -04:00
Nova
14ebe85493 refactor(wayland): update data in user data map for everything 2024-08-06 20:42:46 -04:00
Nova
08135b03a2 feat(wayland): receives_input 2024-08-06 19:12:44 -04:00
Nova
4827561f88 refactor(objects/input): cleanup 2024-08-06 19:12:29 -04:00
Nova
72f7fd8e9d refactor: modularize input object capture system 2024-08-06 17:41:39 -04:00
Nova
827c630a70 refactor(input): rename internal capture requests 2024-08-06 12:08:19 -04:00
Nova
98a6ad6f32 refactor: cleanup 2024-07-28 06:14:02 -04:00
Nova
c09afba366 feat: subsurface support 2024-07-27 15:58:55 -04:00
Nova
48f15e848d fix(wayland): lock on commit 2024-07-26 17:42:58 -04:00
Nova
78912c8f1b fix: vram issues 2024-07-26 06:32:36 -04:00
Nova
13815d4d20 refactor(tracy): mark/demark relevant functions 2024-07-26 05:53:13 -04:00
Nova
36d91fd999 fix(popups): unconstrain them 2024-07-24 17:25:26 -04:00
Nova
b7191c3183 fix(wayland): popups don't crash anymore 2024-07-24 17:02:02 -04:00
Nova
796ee1a34e feat(main): disable hands/controllers args 2024-07-24 10:21:54 -04:00
Nova
de10a7101e feat: update stereokit 2024-07-23 04:33:00 -04:00
Nova
41d22da691 fix: force origin local mode 2024-07-23 04:32:37 -04:00
Nova
73ed1210ed fix(wayland): set virtual output to 1024x1024 2024-07-20 09:19:14 -04:00
Nova
a8e16a8411 fix(objects/input): filter out handlers with disabled fields 2024-07-19 00:34:50 -04:00
Nova
4d8d38e4e5 fix(zone): if parents none 2024-07-18 23:46:51 -04:00
Nova
01f4a8fbcd feat: stereokit hax 2024-07-18 13:44:33 -04:00
Nova
f639a1662a feat(objects): add palm 2024-07-18 12:09:38 -04:00
Nova
6350444559 fix(input): only allow handlers with enabled field nodes 2024-07-18 11:24:06 -04:00
Nova
ecc7d7b912 feat(node): not enabled if spatial and size is 0 2024-07-18 10:49:50 -04:00
Nova
ae40158dec feat(objects): controllers 2024-07-18 09:19:06 -04:00
Nova
1b28290cbb feat(objects): rename inputs 2024-07-18 08:07:35 -04:00
Nova
a3bcff035a refactor(objects): move into objects module 2024-07-18 07:56:36 -04:00
Nova
71ca32a560 cleanup(wayland/compositor) 2024-07-18 03:09:46 -04:00
Nova
d28b477c8a feat(main): use d-bus object manager
Signed-off-by: Nova <technobaboo@gmail.com>
2024-07-10 07:03:14 -04:00
Nova
643697ea33 refactor(cargo.toml): reorder deps 2024-07-08 06:04:13 -04:00
Nova
f5259f2977 refactor(wayland): use latest smithay goods 2024-07-07 20:05:57 -04:00
Nova
7bedd2c27f fix(wayland): send pending configure on resize just in case 2024-07-05 04:09:01 -04:00
Nova
707b81871c fix(main): new tracy api 2024-07-05 04:08:49 -04:00
Nova
1fddcd9033 fix(main): overlays 2024-07-05 04:08:35 -04:00
Nova
7ff470e1db fix(wayland): touches not being properly released 2024-07-04 20:37:52 -04:00
Nova
a7df6d8a08 fix(wayland): only emit toplevel_sized_changed when it changes 2024-07-04 20:31:54 -04:00
Nova
a9ff97e39c feat(main): better errors 2024-07-03 22:04:43 -04:00
Nova
126e9f4ca6 fix(zone): set zoneable false reparents to nothing 2024-07-01 22:01:42 -04:00
Nova
e379dae4ad fix(root): elapsed time 2024-07-01 21:55:26 -04:00
Nova
b3fa529f77 fix(objects): properly destroy nodes 2024-06-30 22:45:20 -04:00
Nova
1e8d3a3d4c fix(input): send events even when not in handler queue 2024-06-30 22:22:41 -04:00
Nova
2988ae3c28 refactor(main): pass args into stereokit loop 2024-06-28 21:00:07 -04:00
Nova
9c04b5710e feat(objects): simulated input switching 2024-06-28 20:55:02 -04:00
Nova
1324c26c74 fix(fields): actually add the methods onto the nodes 2024-06-28 20:07:28 -04:00
Nova
0b9f79158c feat(objects/controller): color signifier 2024-06-27 17:06:27 -04:00
Nova
e5d0906d73 feat(objects/hand): make fingertips pointy 2024-06-27 16:54:08 -04:00
Nova
f08f3e4e4b fix: polish stuff a bit 2024-06-27 16:51:33 -04:00
Nova
d17841ec7a fix(model): no panics 2024-06-26 15:21:48 -04:00
Nova
ebaf113d94 feat: use d-bus for server objects 2024-06-25 16:17:44 -04:00
Nova
5be25da46f feat: use dbus for hmd 2024-06-24 19:26:01 -04:00
Nova
f5e8156400 fix(anchors.txt): get that outta here :/ 2024-06-24 19:25:43 -04:00
Nova
02bf210c51 feat(fields): use new field ref and field types 2024-06-21 17:22:52 -04:00
Nova
eba317ace9 fix(release): make it not glitch 2024-06-19 13:29:39 -04:00
Nova
84e42499e7 feat: todos 2024-06-17 12:11:04 -04:00
Nova
9b6b450d18 fix(objects/input) mouse pointer gives correct keymap now 2024-06-16 21:42:25 -04:00
Nova
53979ce167 fix(item): item destroy not sending correctly 2024-06-15 13:58:36 -04:00
Nova
e2c9e06cd3 refactor: change println to tracing 2024-06-15 12:40:09 -04:00
Nova
65a9957be1 fix(wayland): context in xr 2024-06-14 22:33:08 -04:00
Nova
fbe941749a refactor(everything): streamline stereokit and main loops 2024-06-13 18:55:46 -04:00
Nova
36fd3216c7 fix(wayland): strip out all the xwayland stuff finally 2024-06-13 13:34:42 -04:00
Nova
f73c8f968d fix(wayland): properly kill off xwayland 2024-06-13 10:18:42 -04:00
Nova
83e3a913c5 fix(wayland): remove unnecessary backend impl 2024-06-11 14:33:59 -04:00
Nova
de56aa53d6 feat: update 2024-06-11 07:13:58 -04:00
Nova
9f0043f406 refactor: remove xwayland (we can always just use the old code) 2024-06-11 07:08:41 -04:00
Nova
99eb0ea547 fix: clippy 2024-06-09 19:58:41 -04:00
Nova
04535895e8 fix: stereokit not exiting on ctrl+c 2024-06-09 19:40:32 -04:00
Nova
69311125ba fix(objects/input): multimodal hand input 2024-06-07 15:13:13 -04:00
Nova
45b832455b fix: stupid dbg statements 2024-06-07 09:01:22 -04:00
Nova
b590e82b05 fix: input capturing 2024-06-07 08:22:51 -04:00
Nova
f770357010 fix: state 2024-06-06 09:24:25 -04:00
Nova
3cbc10d91e fix(model): no panic on material change 2024-06-06 09:02:32 -04:00
Nova
9425d30cb3 fix(input): input not working 2024-06-06 06:41:27 -04:00
Nova
8d2aac12d6 feat: upgrade to numerical IDs 2024-06-05 14:34:45 -04:00
Nova
5f9d9d4714 refactor(items): codegen prototocol 2024-05-30 00:34:29 -04:00
Nova
eda50b7d51 feat: upgrade stereokit 2024-05-28 09:13:15 -04:00
Nova
01c5ad3b04 feat: update dependencies 2024-05-28 03:17:02 -04:00
Nova
d7fba79be9 fix: new apis 2024-05-28 01:24:49 -04:00
Nova
89fcc55cca feat(codegen): inherits 2024-05-27 02:25:05 -04:00
Nova
a831fcb99f feat(nodes/input): update protocol 2024-05-27 02:24:11 -04:00
Nova
8e37d27130 fix(objects/input): grave key 2024-05-27 02:23:43 -04:00
Nova
149131f322 fix(objects/sk_hand): addref to material 2024-04-24 20:02:11 -04:00
Nova
4a90b936b1 fix(wayland): stop unwrap of repositioned popup 2024-04-24 20:00:02 -04:00
Nova
ab6998407b feat(flake.nix): default app 2024-04-24 16:28:47 -04:00
Nova
be2f5b8e37 refactor(input): switch to manual handler order 2024-04-16 08:00:07 -04:00
Nova
226554fadc refactor(input): use idl 2024-04-14 09:46:29 -04:00
Nova
47cbc2b8fc feat(nodes/root): log base prefixes change 2024-04-13 05:07:46 -04:00
Nova
90f60c9178 refactor(wayland/xwayland_rootless): use tokio 2024-04-04 23:55:49 -04:00
Nova
49253567cb fix(wayland/decoration): make decoration SSD 2024-03-28 19:30:35 -04:00
Nova
350007cbaf fix(wayland): remove nonexistent capabilities 2024-03-28 19:29:50 -04:00
Nova
7318d5317c feat: lapce run toml 2024-03-09 23:51:07 -05:00
Nova
15f771ff93 fix(drawable/text): alignment 2024-03-09 23:17:56 -05:00
Nova
302a64785d fix(wayland): warnings 2024-03-09 23:16:32 -05:00
Nova
34aab266a3 refactor(wayland): use smithay xdg_shell handler 2024-03-09 02:23:40 -05:00
Nova
fba4e10611 refactor(wayland): use smithay seats 2024-03-08 05:57:52 -05:00
Nova
807928a8d3 feat: version update 2024-03-06 21:02:26 -05:00
matthewcroughan
f74c891907 flake.lock: Update
closes https://github.com/StardustXR/server/pull/19

Flake lock file updates:

• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/59cf3f1447cfc75087e7273b04b31e689a8599fb' (2023-08-01)
  → 'github:hercules-ci/flake-parts/f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2' (2024-03-01)
• Updated input 'flake-parts/nixpkgs-lib':
    'github:NixOS/nixpkgs/9e1960bc196baf6881340d53dccb203a951745a2?dir=lib' (2023-08-01)
  → 'github:NixOS/nixpkgs/1536926ef5621b09bba54035ae2bb6d806d72ac8?dir=lib' (2024-02-29)
• Updated input 'flatland':
    'github:StardustXR/flatland/3867067452761959a95497d6589f9336b347270c' (2023-09-08)
  → 'github:StardustXR/flatland/db1639bc6ca738bf9cb52f2c20159a612019454c' (2024-02-06)
• Updated input 'flatland/fenix':
    'github:nix-community/fenix/ee59e1c769657b1e27e608f8b981fa8f6b715583' (2023-03-14)
  → 'github:nix-community/fenix/4378e7e5f5bdef438eee5ce967f37593b9b5cd16' (2023-11-16)
• Updated input 'flatland/fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/95497533524537b1cc7a2870ce94b0b14503be8b' (2023-03-13)
  → 'github:rust-lang/rust-analyzer/58de0b130a763f3a2d373f508ac0c18a8e7d0acd' (2023-11-15)
• Updated input 'flatland/nixpkgs':
    'github:NixOS/nixpkgs/67f26c1cfc5d5783628231e776a81c1ade623e0b' (2023-03-13)
  → 'github:NixOS/nixpkgs/e44462d6021bfe23dfb24b775cc7c390844f773d' (2023-11-12)
• Updated input 'hercules-ci-effects':
    'github:hercules-ci/hercules-ci-effects/0a63bfa3f00a3775ea3a6722b247880f1ffe91ce' (2023-07-15)
  → 'github:hercules-ci/hercules-ci-effects/0ca27bd58e4d5be3135a4bef66b582e57abe8f4a' (2024-02-21)
• Updated input 'hercules-ci-effects/flake-parts':
    'github:hercules-ci/flake-parts/8e8d955c22df93dbe24f19ea04f47a74adbdc5ec' (2023-07-04)
  → 'github:hercules-ci/flake-parts/34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5' (2023-12-01)
• Updated input 'hercules-ci-effects/flake-parts/nixpkgs-lib':
    'github:NixOS/nixpkgs/4bc72cae107788bf3f24f30db2e2f685c9298dc9?dir=lib' (2023-06-29)
  → follows 'hercules-ci-effects/nixpkgs'
• Removed input 'hercules-ci-effects/hercules-ci-agent'
• Removed input 'hercules-ci-effects/hercules-ci-agent/flake-parts'
• Removed input 'hercules-ci-effects/hercules-ci-agent/flake-parts/nixpkgs-lib'
• Removed input 'hercules-ci-effects/hercules-ci-agent/haskell-flake'
• Removed input 'hercules-ci-effects/hercules-ci-agent/nixpkgs'
• Updated input 'hercules-ci-effects/nixpkgs':
    'github:NixOS/nixpkgs/27fcd46fa18df36d270174246e7bd8f1787129ff' (2023-07-15)
  → 'github:NixOS/nixpkgs/cfc3698c31b1fb9cdcf10f36c9643460264d0ca8' (2023-12-27)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/b85ed9dcbf187b909ef7964774f8847d554fab3b' (2023-08-22)
  → 'github:nixos/nixpkgs/1536926ef5621b09bba54035ae2bb6d806d72ac8' (2024-02-29)
2024-03-06 19:37:44 -05:00
Nova
2dc0fdadfd fix(client_state): backwards compatibility 2024-02-20 03:59:18 -05:00
Nova
8e317a8e08 feat: session restore! 2024-02-20 03:02:57 -05:00
Nova
c41179a437 refactor(wayland): put everything in wl_surface user data 2024-02-15 12:42:34 -05:00
Nova
43910cce78 feat(input): capturing handler's distance is halved 2024-02-12 11:35:27 -05:00
77 changed files with 6861 additions and 7125 deletions

3
.gitignore vendored
View File

@@ -12,4 +12,5 @@
/libs/
*.AppImage
*.blend1
*.blend1
anchors.txt

29
.lapce/run.toml Normal file
View File

@@ -0,0 +1,29 @@
# The run config is used for both run mode and debug mode
[[configs]]
# the name of this task
name = "task"
# the type of the debugger. If not set, it can't be debugged but can still be run
# type = "lldb"
# the program to run
program = "./target/debug/stardust-xr-server"
# the program arguments, e.g. args = ["arg1", "arg2"], optional
# args = []
# current working directory, optional
cwd = "${workspace}"
# enviroment variables, optional
# [configs.env]
# VAR1 = "VAL1"
# VAR2 = "VAL2"
# task to run before the run/debug session is started, optional
# [configs.prelaunch]
# program = "cargo"
# args = [
# "build",
# ]

2571
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
edition = "2021"
rust-version = "1.75"
name = "stardust-xr-server"
version = "0.44.0"
version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"]
description = "Stardust XR reference display server"
license = "GPLv2"
@@ -18,96 +18,100 @@ path = "src/main.rs"
[features]
default = ["wayland"]
wayland = ["dep:smithay", "dep:xkbcommon"]
xwayland_rootful = []
xwayland_rootless = ["smithay/xwayland"]
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-tracy"]
local_deps = ["stereokit-rust/force-local-deps"]
[package.metadata.appimage]
auto_link = true
auto_link_exclude_list = [
"libc*",
"libdl*",
"libpthread*",
"ld-linux*",
"libGL*",
"libEGL*",
"libc*",
"libdl*",
"libpthread*",
"ld-linux*",
"libGL*",
"libEGL*",
]
[profile.dev.package."*"]
opt-level = 3
debug = true
strip = "none"
debug-assertions = true
overflow-checks = true
[profile.release]
strip = true
lto = true
opt-level = 3
debug = "line-tables-only"
strip = "none"
debug-assertions = true
overflow-checks = false
[dependencies]
color-eyre = { version = "0.6.2", default-features = false }
clap = { version = "4.2.4", features = ["derive"] }
glam = { version = "0.23.0", features = ["mint"] }
lazy_static = "1.4.0"
mint = "0.5.9"
# small utility thingys
once_cell = "1.19.0"
nanoid = "0.4.0"
once_cell = "1.17.1"
parking_lot = "0.12.1"
portable-atomic = { version = "1.2.0", features = ["float", "std"] }
rustc-hash = "1.1.0"
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal", "time"] }
send_wrapper = "0.6.0"
prisma = "0.1.1"
directories = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_repr = "0.1.16"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
global_counter = "0.2.2"
lazy_static = "1.5.0"
rand = "0.8.5"
atty = "0.2.14"
xkbcommon = { version = "0.7.0", default-features = false, optional = true }
ctrlc = "3.4.1"
libc = "0.2.148"
input-event-codes = "5.16.8"
nix = "0.27.1"
wayland-scanner = "0.31.0"
wayland-backend = "0.3.2"
cluFlock = "1.2.7"
fxtypemap = "0.2.0"
rustc-hash = "2.0.0"
portable-atomic = { version = "1.7.0", features = ["float", "std"] }
send_wrapper = "0.6.0"
slotmap = "1.0.7"
global_counter = "=0.2.2"
parking_lot = "0.12.3"
# rust errors/logging
color-eyre = { version = "0.6.3", default-features = false }
clap = { version = "4.5.13", features = ["derive"] }
console-subscriber = { version = "0.4.0", optional = true }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true }
# (de)serialization
serde = { version = "1.0.205", features = ["derive"] }
serde_repr = "0.1.19"
toml = "0.8.19"
# mathy stuffs
glam = { version = "0.28.0", features = ["mint", "serde"] }
mint = "0.5.9"
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
prisma = "0.1.1"
# linux stuffs
libc = "0.2.155"
nix = "0.29.0"
input-event-codes = "6.2.0"
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] }
directories = "5.0.1"
xkbcommon-rs = "0.1.0"
# wayland
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
wayland-scanner = { version = "0.31.4", optional = true }
[dependencies.smithay]
# 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/technobaboo/smithay.git"
# git = "https://github.com/colinmarc/smithay.git"
git = "https://github.com/smithay/smithay.git"
# path = "../smithay"
default-features = false
features = [
"desktop",
"backend_drm",
"backend_egl",
"renderer_gl",
"wayland_frontend",
]
version = "*"
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
optional = true
[dependencies.stereokit]
[dependencies.stereokit-rust]
# path = "../StereoKit-rust"
# git = "https://github.com/mvvvv/StereoKit-rust.git"
git = "https://github.com/technobaboo/StereoKit-rust.git"
features = ["no-event-loop"]
default-features = false
features = ["linux-egl"]
version = "0.16.9"
[dependencies.console-subscriber]
version = "0.1.8"
optional = true
[dependencies.tracing-tracy]
version = "0.10.4"
optional = true
[dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
branch = "dev"
[dependencies.stardust-xr-server-codegen]
path = "codegen"
# [patch.crates-io.stereokit]
# path = "../stereokit-rs"
# [patch.crates-io.stereokit-sys]
# path = "../stereokit-sys"

View File

@@ -15,3 +15,4 @@ split-iter = "0.1.0"
[dependencies.stardust-xr-schemas]
git = "https://github.com/StardustXR/core.git"
branch = "dev"

View File

@@ -8,10 +8,10 @@ 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_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)
@@ -36,27 +36,39 @@ pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::To
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)
// }
#[proc_macro]
pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(INPUT_PROTOCOL)
}
#[proc_macro]
pub fn codegen_item_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(ITEM_PROTOCOL)
}
#[proc_macro]
pub fn codegen_item_camera_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(ITEM_CAMERA_PROTOCOL)
}
#[proc_macro]
pub fn codegen_item_panel_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(ITEM_PANEL_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,
let node_id = p.node_id;
let node_id = quote! {
const INTERFACE_NODE_ID: u64 = #node_id;
};
let aspect = generate_aspect(&Aspect {
name: "interface".to_string(),
description: protocol.description.clone(),
inherits: vec![],
members: p.members,
})
});
quote!(#node_id #aspect)
})
.unwrap_or_default();
let custom_enums = protocol
@@ -83,12 +95,6 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
.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()
}
@@ -162,45 +168,6 @@ fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
}
}
// 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);
@@ -213,8 +180,10 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
.map(generate_member)
.reduce(fold_tokens)
.map(|t| {
// TODO: properly import all dependencies
quote! {
pub mod #client_mod_name {
use super::*;
#t
}
}
@@ -225,6 +194,30 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let opcodes = aspect
.members
.iter()
.map(|m| {
let aspect_name = aspect.name.to_case(Case::ScreamingSnake);
let member_name = m.name.to_case(Case::ScreamingSnake);
let name_type = if m.side == Side::Client {
"CLIENT"
} else {
"SERVER"
};
let name = Ident::new(
&format!("{aspect_name}_{member_name}_{name_type}_OPCODE"),
Span::call_site(),
);
let opcode = m.opcode;
quote!(pub(crate) const #name: u64 = #opcode;)
})
.reduce(fold_tokens)
.unwrap_or_default();
let alias_info = generate_alias_info(aspect);
let server_side_members = server_members
.map(generate_member)
.reduce(fold_tokens)
@@ -250,11 +243,58 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
#server_side_members
}
};
quote!(#client_side_members #server_side_members)
quote!(#opcodes #alias_info #client_side_members #server_side_members)
}
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
aspect
.members
.iter()
.filter(|m| m.side == side)
.filter(|m| m._type == _type)
.map(|m| m.opcode)
.map(|o| quote!(#o))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default()
}
fn generate_alias_info(aspect: &Aspect) -> TokenStream {
let aspect_alias_info_name = Ident::new(
&format!(
"{}_ASPECT_ALIAS_INFO",
aspect.name.to_case(Case::ScreamingSnake)
),
Span::call_site(),
);
let local_signals = generate_alias_opcodes(aspect, Side::Server, MemberType::Signal);
let local_methods = generate_alias_opcodes(aspect, Side::Server, MemberType::Method);
let remote_signals = generate_alias_opcodes(aspect, Side::Client, MemberType::Signal);
let inherits = aspect
.inherits
.iter()
.map(|a| {
Ident::new(
&format!("{}_ASPECT_ALIAS_INFO", a.to_case(Case::ScreamingSnake)),
Span::call_site(),
)
})
.map(|a| quote!(#a.clone()))
.fold(quote!(), |a, b| quote!(#a + #b));
quote! {
lazy_static::lazy_static! {
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
server_signals: vec![#local_signals],
server_methods: vec![#local_methods],
client_signals: vec![#remote_signals],
}
#inherits;
}
}
}
fn generate_member(member: &Member) -> TokenStream {
let name_str = &member.name;
let id = member.opcode;
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
let description = &member.description;
@@ -282,15 +322,15 @@ fn generate_member(member: &Member) -> TokenStream {
let return_type = member
.return_type
.as_ref()
.map(|r| generate_argument_type(&r, false, true))
.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
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
_node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
}
}
}
@@ -299,7 +339,7 @@ fn generate_member(member: &Member) -> TokenStream {
#[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)
_node.send_remote_signal(#id, serialized)
}
}
}
@@ -310,23 +350,7 @@ fn generate_member(member: &Member) -> TokenStream {
}
}
(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<()>;
}
@@ -334,8 +358,8 @@ fn generate_member(member: &Member) -> TokenStream {
}
}
fn generate_handler(member: &Member) -> TokenStream {
let member_name = &member.name;
let member_name_ident = Ident::new(&member_name, Span::call_site());
let opcode = member.opcode;
let member_name_ident = Ident::new(&member.name, Span::call_site());
let argument_names = member
.arguments
@@ -358,6 +382,11 @@ fn generate_handler(member: &Member) -> TokenStream {
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
})
.unwrap_or_default();
let serialize = generate_argument_serialize(
"result",
&member.return_type.clone().unwrap_or(ArgumentType::Empty),
false,
);
let argument_uses = member
.arguments
.iter()
@@ -366,16 +395,17 @@ fn generate_handler(member: &Member) -> TokenStream {
.unwrap_or_default();
match member._type {
MemberType::Signal => quote! {
node.add_local_signal(#member_name, |_node, _calling_client, _message| {
node.add_local_signal(#opcode, |_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| {
node.add_local_method(#opcode, |_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()))
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
Ok((#serialize, Vec::new()))
});
});
},
@@ -387,7 +417,13 @@ fn generate_argument_name(argument: &Argument) -> TokenStream {
fn convert_deserializeable_argument_type(argument_type: &ArgumentType) -> ArgumentType {
match argument_type {
ArgumentType::Node { .. } => ArgumentType::String,
ArgumentType::Node { .. } => ArgumentType::NodeID,
ArgumentType::Vec(v) => {
ArgumentType::Vec(Box::new(convert_deserializeable_argument_type(v.as_ref())))
}
ArgumentType::Map(v) => {
ArgumentType::Map(Box::new(convert_deserializeable_argument_type(v.as_ref())))
}
f => f.clone(),
}
}
@@ -397,12 +433,18 @@ fn generate_argument_deserialize(
optional: bool,
) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
if let ArgumentType::Node { .. } = argument_type {
return match optional {
true => quote!(#name.map(|n| _calling_client.get_node(#argument_name, n)?)),
false => quote!(_calling_client.get_node(#argument_name, #name)?),
};
}
if optional {
let mapping = generate_argument_deserialize("o", argument_type, false);
return quote!(#name.map(|o| Ok::<_, color_eyre::eyre::Report>(#mapping)).transpose()?);
}
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);
@@ -424,10 +466,10 @@ fn generate_argument_serialize(
match argument_type {
ArgumentType::Node {
_type,
return_info: _,
return_id_parameter_name: _,
} => match optional {
true => quote!(#name.map(|n| n.get_path())),
false => quote!(#name.get_path()),
true => quote!(#name.map(|n| n.get_id())),
false => quote!(#name.get_id()),
},
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
ArgumentType::Vec(v) => {
@@ -448,18 +490,21 @@ fn generate_argument_decl(argument: &Argument, owned_values: bool) -> TokenStrea
}
fn argument_type_option_name(argument_type: &ArgumentType) -> String {
match argument_type {
ArgumentType::Empty => "Empty".to_string(),
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::Vec2(_) => "Vec2".to_string(),
ArgumentType::Vec3(_) => "Vec3".to_string(),
ArgumentType::Quat => "Quat".to_string(),
ArgumentType::Mat4 => "Mat4".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::Vec(v) => format!("{}Vector", argument_type_option_name(v)),
ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(m)),
ArgumentType::NodeID => "Node ID".to_string(),
ArgumentType::Datamap => "Datamap".to_string(),
ArgumentType::ResourceID => "ResourceID".to_string(),
ArgumentType::Enum(e) => e.clone(),
@@ -474,13 +519,21 @@ fn generate_argument_type(
owned: bool,
) -> TokenStream {
let _type = match argument_type {
ArgumentType::Empty => quote!(()),
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::Vec2(t) => {
let t = generate_argument_type(t, false, true);
quote!(stardust_xr::values::Vector2<#t>)
}
ArgumentType::Vec3(t) => {
let t = generate_argument_type(t, false, true);
quote!(stardust_xr::values::Vector3<#t>)
}
ArgumentType::Quat => quote!(stardust_xr::values::Quaternion),
ArgumentType::Mat4 => quote!(stardust_xr::values::Mat4),
ArgumentType::Color => quote!(stardust_xr::values::Color),
ArgumentType::Bytes => {
if !owned {
@@ -497,7 +550,7 @@ fn generate_argument_type(
}
}
ArgumentType::Vec(t) => {
let t = generate_argument_type(&t, false, true);
let t = generate_argument_type(t, false, true);
if !owned {
quote!(&[#t])
} else {
@@ -505,14 +558,15 @@ fn generate_argument_type(
}
}
ArgumentType::Map(t) => {
let t = generate_argument_type(&t, false, true);
let t = generate_argument_type(t, false, true);
if !owned {
quote!(&rustc_hash::FxHashMap<String, #t>)
quote!(&stardust_xr::values::Map<String, #t>)
} else {
quote!(rustc_hash::FxHashMap<String, #t>)
quote!(stardust_xr::values::Map<String, #t>)
}
}
ArgumentType::NodeID => quote!(u64),
ArgumentType::Datamap => {
if !owned {
quote!(&stardust_xr::values::Datamap)
@@ -545,7 +599,7 @@ fn generate_argument_type(
}
ArgumentType::Node {
_type,
return_info: _,
return_id_parameter_name: _,
} => {
if !owned {
quote!(&std::sync::Arc<crate::nodes::Node>)

200
flake.lock generated
View File

@@ -1,24 +1,23 @@
{
"nodes": {
"fenix": {
"crane": {
"inputs": {
"nixpkgs": [
"flatland",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
]
},
"locked": {
"lastModified": 1678775037,
"narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583",
"lastModified": 1712681629,
"narHash": "sha256-bMDXn4AkTXLCpoZbII6pDGoSeSe9gI87jxPsHRXgu/E=",
"owner": "ipetkov",
"repo": "crane",
"rev": "220387ac8e99cbee0ca4c95b621c4bc782b6a235",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
@@ -27,11 +26,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1690933134,
"narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
@@ -42,14 +41,17 @@
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
"nixpkgs-lib": [
"hercules-ci-effects",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688466019,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
@@ -57,39 +59,17 @@
"type": "indirect"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688466019,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flatland": {
"inputs": {
"fenix": "fenix",
"crane": "crane",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1694190588,
"narHash": "sha256-tcvp09A54AbXkkXS/P6Ed5TUWzMEuQ9h/fnfLz7gnJc=",
"lastModified": 1713050562,
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
"owner": "StardustXR",
"repo": "flatland",
"rev": "3867067452761959a95497d6589f9336b347270c",
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
"type": "github"
},
"original": {
@@ -98,53 +78,17 @@
"type": "github"
}
},
"haskell-flake": {
"locked": {
"lastModified": 1684780604,
"narHash": "sha256-2uMZsewmRn7rRtAnnQNw1lj0uZBMh4m6Cs/7dV5YF08=",
"owner": "srid",
"repo": "haskell-flake",
"rev": "74210fa80a49f1b6f67223debdbf1494596ff9f2",
"type": "github"
},
"original": {
"owner": "srid",
"ref": "0.3.0",
"repo": "haskell-flake",
"type": "github"
}
},
"hercules-ci-agent": {
"inputs": {
"flake-parts": "flake-parts_3",
"haskell-flake": "haskell-flake",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1688568579,
"narHash": "sha256-ON0M56wtY/TIIGPkXDlJboAmuYwc73Hi8X9iJGtxOhM=",
"owner": "hercules-ci",
"repo": "hercules-ci-agent",
"rev": "367dd8cd649b57009a6502e878005a1e54ad78c5",
"type": "github"
},
"original": {
"id": "hercules-ci-agent",
"type": "indirect"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts_2",
"hercules-ci-agent": "hercules-ci-agent",
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1689397210,
"narHash": "sha256-fVxZnqxMbsDkB4GzGAs/B41K0wt/e+B/fLxmTFF/S20=",
"lastModified": 1719226092,
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "0a63bfa3f00a3775ea3a6722b247880f1ffe91ce",
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
"type": "github"
},
"original": {
@@ -155,63 +99,39 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1678703398,
"narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=",
"lastModified": 1712791164,
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.11",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1690881714,
"narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9e1960bc196baf6881340d53dccb203a951745a2",
"type": "github"
"lastModified": 1722555339,
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"dir": "lib",
"lastModified": 1688049487,
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1688322751,
"narHash": "sha256-eW62dC5f33oKZL7VWlomttbUnOTHrAbte9yNUNW8rbk=",
"lastModified": 1713714899,
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0fbe93c5a7cac99f90b60bdf5f149383daaa615f",
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
"type": "github"
},
"original": {
@@ -223,26 +143,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1689393711,
"narHash": "sha256-l3gyTPy/qWLDk1CY1EgYwlsxcGxN2aVd7MlCzQa69rk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "27fcd46fa18df36d270174246e7bd8f1787129ff",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1692734709,
"narHash": "sha256-SCFnyHCyYjwEmgUsHDDuU0TsbVMKeU1vwkR+r7uS2Rg=",
"lastModified": 1723991338,
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b85ed9dcbf187b909ef7964774f8847d554fab3b",
"rev": "8a3354191c0d7144db9756a74755672387b702ba",
"type": "github"
},
"original": {
@@ -257,24 +162,7 @@
"flake-parts": "flake-parts",
"flatland": "flatland",
"hercules-ci-effects": "hercules-ci-effects",
"nixpkgs": "nixpkgs_4"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1678695923,
"narHash": "sha256-rDhiiU8P6sf6mgj5IKgCuTRN9uYeqWr6xl4XLkKnMWg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "95497533524537b1cc7a2870ce94b0b14503be8b",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
"nixpkgs": "nixpkgs_3"
}
}
},

View File

@@ -1,7 +1,9 @@
{
nixConfig = {
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";
@@ -12,37 +14,44 @@
# it to create VM Tests
flatland.url = "github:StardustXR/flatland";
};
outputs = inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }:
outputs =
inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }:
let
name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
src = builtins.path {
name = "${name}-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
nixpkgs.lib.all (n: builtins.baseNameOf path != n) [
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
flake-parts.flakeModules.easyOverlay
];
in flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ flake-parts.flakeModules.easyOverlay ];
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 ]; };
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ inputs.self.overlays.default ];
};
overlayAttrs = config.packages;
packages = {
packages = let sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
in {
default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix { inherit name src; };
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
inherit name src sk_gpu;
};
};
checks.gnome-graphical-test = pkgs.nixosTest (import ./nix/gnome-graphical-test.nix { inherit pkgs self; });
apps.default = {
type = "app";
program = self'.packages.${name} + "/bin/stardust-xr-server";
};
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";
@@ -61,7 +70,9 @@
readSecretString stardustxrDiscord .webhook > .webhook
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 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"

27
nix/meshoptimizer.nix Normal file
View File

@@ -0,0 +1,27 @@
{ lib, stdenv, fetchFromGitHub, cmake }:
stdenv.mkDerivation rec {
pname = "meshoptimizer";
version = "0.20";
src = fetchFromGitHub {
owner = "zeux";
repo = "meshoptimizer";
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
};
nativeBuildInputs = [ cmake ];
cmakeFlags = [
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON"
];
meta = with lib; {
description = "Mesh optimization library that makes meshes smaller and faster to render";
homepage = "https://github.com/zeux/meshoptimizer";
license = licenses.mit;
platforms = platforms.all;
};
}

16
nix/sk_gpu.nix Normal file
View File

@@ -0,0 +1,16 @@
{ stdenv, fetchurl, unzip }:
let
sk_gpu_zip = fetchurl {
url =
"https://github.com/StereoKit/sk_gpu/releases/download/v2024.8.16/sk_gpu.v2024.8.16.zip";
sha256 = "sha256-Wk3PZFlWqhrsQ8xG0sQaV2xSasdg2D7TMiPvl/CgtGU=";
};
in stdenv.mkDerivation rec {
name = "sk_gpu";
src = sk_gpu_zip;
unpackPhase = ''
unzip -d $out ${sk_gpu_zip}
'';
nativeBuildInputs = [ unzip ];
}

View File

@@ -1,17 +1,25 @@
{ rustPlatform
, src
, name
, openxr-loader
, libGL
, mesa
, xorg
, fontconfig
, libxkbcommon
, libclang
, cmake
, cpm-cmake
, pkg-config
, llvmPackages
, fetchFromGitHub
, sk_gpu
, libXau
, libXdmcp
, stdenv
, lib
, openxr-loader
}:
rustPlatform.buildRustPackage rec {
@@ -20,21 +28,59 @@ rustPlatform.buildRustPackage rec {
lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true;
};
buildFeatures = [ "local_deps" ];
FORCE_LOCAL_DEPS = true;
CPM_LOCAL_PACKAGES_ONLY = true;
CPM_SOURCE_CACHE = "./build";
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
CPM_USE_LOCAL_PACKAGES = true;
CPM_DOWNLOAD_ALL = false;
meshoptimizer = fetchFromGitHub {
owner = "zeux";
repo = "meshoptimizer";
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
};
basis_universal = fetchFromGitHub {
owner = "BinomialLLC";
repo = "basis_universal";
rev = "900e40fb5d2502927360fe2f31762bdbb624455f";
sha256 = "sha256-zBRAXgG5Fi6+5uPQCI/RCGatY6O4ELuYBoKrPNn4K+8=";
};
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
DEP_SK_GPU_SOURCE = "${sk_gpu}";
postPatch = let libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
in ''
sk=$(echo $cargoDepsCopy/stereokit-rust-*/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
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.38.7.cmake
mkdir -p $sk/sk_gpu
cp -R ${sk_gpu}/* $sk/sk_gpu
chmod -R 755 $sk/sk_gpu/tools/linux_x64/*
export DEP_SK_GPU_SOURCE=$sk/sk_gpu
export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib";
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
--set-rpath "${libPath}" \
$sk/sk_gpu/tools/linux_x64/skshaderc
'';
nativeBuildInputs = [
cmake pkg-config llvmPackages.libcxxClang
];
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
buildInputs = [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
libGL
mesa
xorg.libX11.dev
xorg.libXft
xorg.libXfixes
fontconfig
libxkbcommon
libXau
libXdmcp
openxr-loader
];
LIBCLANG_PATH = "${libclang.lib}/lib";
}

View File

@@ -1,22 +1,24 @@
use super::{
client_state::{ClientState, CLIENT_STATES},
client_state::{ClientStateParsed, CLIENT_STATES},
destroy_queue,
scenegraph::Scenegraph,
};
use crate::{
core::{registry::OwnedRegistry, task},
nodes::{audio, data, drawable, fields, hmd, input, items, root::Root, spatial, Node},
nodes::{
audio, data, drawable, fields, input, items,
root::{ClientState, Root},
spatial, Node,
},
};
use color_eyre::eyre::{eyre, Result};
use global_counter::primitive::exact::CounterU32;
use lazy_static::lazy_static;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::{
messenger::{self, MessageSenderHandle},
schemas::flex::serialize,
};
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
use stardust_xr::messenger::{self, MessageSenderHandle};
use std::{fmt::Debug, fs, iter::FromIterator, path::PathBuf, sync::Arc};
use tokio::{net::UnixStream, task::JoinHandle};
use tracing::info;
@@ -32,10 +34,11 @@ lazy_static! {
disconnect_status: OnceCell::new(),
message_sender_handle: None,
id_counter: CounterU32::new(0),
scenegraph: Default::default(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
state: Arc::new(ClientState::default()),
state: OnceCell::default(),
});
}
@@ -47,7 +50,7 @@ pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
.map(|(k, v)| (k.to_string(), v.to_string())),
))
}
pub fn state(env: &FxHashMap<String, String>) -> Option<Arc<ClientState>> {
pub fn state(env: &FxHashMap<String, String>) -> Option<Arc<ClientStateParsed>> {
let token = env.get("STARDUST_STARTUP_TOKEN")?;
CLIENT_STATES.lock().get(token).cloned()
}
@@ -60,11 +63,12 @@ pub struct Client {
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
disconnect_status: OnceCell<Result<()>>,
id_counter: CounterU32,
pub message_sender_handle: Option<MessageSenderHandle>,
pub scenegraph: Arc<Scenegraph>,
pub root: OnceCell<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub state: Arc<ClientState>,
pub state: OnceCell<ClientState>,
}
impl Client {
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
@@ -84,7 +88,7 @@ impl Client {
let state = env
.as_ref()
.and_then(state)
.unwrap_or_else(|| Arc::new(ClientState::default()));
.unwrap_or_else(|| Arc::new(ClientStateParsed::default()));
let client = CLIENTS.add(Client {
pid,
@@ -95,29 +99,25 @@ impl Client {
flush_join_handle: OnceCell::new(),
disconnect_status: OnceCell::new(),
id_counter: CounterU32::new(256),
message_sender_handle: Some(messenger_tx.handle()),
scenegraph: scenegraph.clone(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
state,
state: OnceCell::default(),
});
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client)?);
hmd::make_alias(&client)?;
let _ = client.root.set(Root::create(&client, state.root)?);
spatial::create_interface(&client)?;
fields::create_interface(&client)?;
drawable::create_interface(&client)?;
audio::create_interface(&client)?;
data::create_interface(&client)?;
items::create_interface(&client)?;
input::create_interface(&client)?;
items::camera::create_interface(&client)?;
items::panel::create_interface(&client)?;
client
.root
.get()
.unwrap()
.node
.send_remote_signal("restore_state", serialize(client.state.apply_to(&client))?)?;
let _ = client.state.set(state.apply_to(&client));
let pid_printable = pid
.map(|pid| pid.to_string())
@@ -141,11 +141,8 @@ impl Client {
let client = client.clone();
async move {
loop {
match messenger_rx.dispatch(&*scenegraph).await {
Err(e) => {
client.disconnect(Err(e.into()));
}
_ => (),
if let Err(e) = messenger_rx.dispatch(&*scenegraph).await {
client.disconnect(Err(e.into()));
}
}
}
@@ -159,11 +156,8 @@ impl Client {
let client = client.clone();
async move {
loop {
match messenger_tx.flush().await {
Err(e) => {
client.disconnect(Err(e.into()));
}
_ => (),
if let Err(e) = messenger_tx.flush().await {
client.disconnect(Err(e.into()));
}
}
}
@@ -190,15 +184,19 @@ impl Client {
let cwd_proc_path = format!("/proc/{pid}/cwd");
std::fs::read_link(cwd_proc_path).ok()
}
pub async fn save_state(&self) -> Option<ClientState> {
pub async fn save_state(&self) -> Option<ClientStateParsed> {
let internal = self.root.get()?.save_state().await.ok()?;
Some(ClientState::from_deserialized(self, internal))
Some(ClientStateParsed::from_deserialized(self, internal))
}
pub fn generate_id(&self) -> u64 {
self.id_counter.inc() as u64
}
#[inline]
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
pub fn get_node(&self, name: &'static str, id: u64) -> Result<Arc<Node>> {
self.scenegraph
.get_node(path)
.get_node(id)
.ok_or_else(|| eyre!("{} not found", name))
}
@@ -215,6 +213,19 @@ impl Client {
}
}
}
impl Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("pid", &self.pid)
.field("exe", &self.exe)
.field("dispatch_join_handle", &self.dispatch_join_handle)
.field("flush_join_handle", &self.flush_join_handle)
.field("disconnect_status", &self.disconnect_status)
.field("base_resource_prefixes", &self.base_resource_prefixes)
.field("state", &self.state)
.finish()
}
}
impl Drop for Client {
fn drop(&mut self) {
info!(

View File

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

@@ -3,9 +3,11 @@ use parking_lot::Mutex;
use std::any::Any;
use tokio::sync::mpsc::{self, unbounded_channel};
type Anything = Box<dyn Any + Send + Sync>;
static MAIN_DESTROY_QUEUE: Lazy<(
mpsc::UnboundedSender<Box<dyn Any + Send + Sync>>,
Mutex<mpsc::UnboundedReceiver<Box<dyn Any + Send + Sync>>>,
mpsc::UnboundedSender<Anything>,
Mutex<mpsc::UnboundedReceiver<Anything>>,
)> = Lazy::new(|| {
let (tx, rx) = unbounded_channel();
(tx, Mutex::new(rx))

View File

@@ -1,36 +0,0 @@
use super::client::Client;
use super::task;
use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::net::UnixListener;
use tokio::task::JoinHandle;
use tracing::error;
pub struct EventLoop {
join_handle: JoinHandle<()>,
}
impl EventLoop {
pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
let socket = UnixListener::bind(socket_path)?;
let join_handle = task::new(|| "event loop", async move {
loop {
let Ok((socket, _)) = socket.accept().await else { continue };
if let Err(e) = Client::from_connection(socket) {
error!(?e, "Unable to create client from connection");
}
}
})?;
let event_loop = Arc::new(EventLoop { join_handle });
Ok(event_loop)
}
}
impl Drop for EventLoop {
fn drop(&mut self) {
self.join_handle.abort();
}
}

View File

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

View File

@@ -2,9 +2,7 @@ pub mod client;
pub mod client_state;
pub mod delta;
pub mod destroy_queue;
pub mod eventloop;
pub mod idl_utils;
pub mod node_collections;
pub mod registry;
pub mod resource;
pub mod scenegraph;

View File

@@ -1,83 +0,0 @@
use crate::nodes::Node;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::{
borrow::Borrow,
hash::Hash,
sync::{Arc, Weak},
};
// #[derive(Default)]
// pub struct LifeLinkedNodeList {
// nodes: Mutex<Vec<Weak<Node>>>,
// }
// impl LifeLinkedNodeList {
// pub fn add(&self, node: Weak<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();
// }
// }
#[derive(Default, Debug)]
pub struct LifeLinkedNodeMap<K: Hash + Eq> {
nodes: Mutex<FxHashMap<K, Weak<Node>>>,
}
#[allow(dead_code)]
impl<K: Hash + Eq> LifeLinkedNodeMap<K> {
pub fn add(&self, key: K, node: &Arc<Node>) {
self.nodes.lock().insert(key, Arc::downgrade(node));
}
pub fn get<Q>(&self, key: &Q) -> Option<Arc<Node>>
where
Q: ?Sized,
K: Borrow<Q>,
Q: Hash + Eq,
{
self.nodes.lock().get(key).and_then(|n| n.upgrade())
}
pub fn nodes(&self) -> Vec<Arc<Node>> {
self.nodes
.lock()
.values()
.filter_map(|v| v.upgrade())
.collect()
}
pub fn remove<Q>(&self, key: &Q) -> Option<Arc<Node>>
where
Q: ?Sized,
K: Borrow<Q>,
Q: Hash + Eq,
{
self.nodes.lock().remove(key).and_then(|n| n.upgrade())
}
pub fn clear(&self) {
let mut nodes = self.nodes.lock();
nodes
.values()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
nodes.clear();
}
}
impl<K: Hash + Eq> Drop for LifeLinkedNodeMap<K> {
fn drop(&mut self) {
self.clear();
}
}

View File

@@ -13,9 +13,7 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
Registry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
MutexGuard::map(self.0.lock(), |r| {
r.get_or_insert_with(|| FxHashMap::default())
})
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
}
pub fn add(&self, t: T) -> Arc<T>
where
@@ -33,12 +31,38 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
}
pub fn get_changes(old: &Registry<T>, new: &Registry<T>) -> (Vec<Arc<T>>, Vec<Arc<T>>) {
let old = old.lock();
let new = new.lock();
let mut added = Vec::new();
let mut removed = Vec::new();
for (id, entry) in new.iter() {
if let Some(entry) = entry.upgrade() {
if !old.contains_key(id) {
added.push(entry);
}
}
}
for (id, entry) in old.iter() {
if let Some(entry) = entry.upgrade() {
if !new.contains_key(id) {
removed.push(entry);
}
}
}
(added, removed)
}
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.lock()
.iter()
.filter_map(|pair| pair.1.upgrade())
.collect()
}
pub fn set(&self, other: &Registry<T>) {
self.lock().clone_from(&other.lock());
}
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
self.0
.lock()
@@ -48,6 +72,14 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
.filter_map(|pair| pair.1.upgrade())
.collect()
}
pub fn retain<F: Fn(&Arc<T>) -> bool>(&self, f: F) {
self.lock().retain(|_, v| {
let Some(v) = v.upgrade() else {
return true;
};
(f)(&v)
})
}
pub fn remove(&self, t: &T) {
self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize));
@@ -84,9 +116,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
OwnedRegistry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
MutexGuard::map(self.0.lock(), |r| {
r.get_or_insert_with(|| FxHashMap::default())
})
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
}
pub fn add(&self, t: T) -> Arc<T>
where

View File

@@ -1,13 +1,16 @@
use stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use super::client::Client;
lazy_static::lazy_static! {
static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default();
static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(':').map(PathBuf::from).collect()).unwrap_or_default();
}
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
fn has_extension(path: &Path, extensions: &[&OsStr]) -> bool {
if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension)
} else {

View File

@@ -1,10 +1,9 @@
use crate::nodes::alias::Alias;
use crate::nodes::alias::get_original;
use crate::nodes::Node;
use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::Ordering;
use rustc_hash::FxHashMap;
use serde::Serialize;
use stardust_xr::scenegraph;
@@ -19,7 +18,7 @@ use tracing::{debug, debug_span};
#[derive(Default)]
pub struct Scenegraph {
pub(super) client: OnceCell<Weak<Client>>,
nodes: Mutex<FxHashMap<String, Arc<Node>>>,
nodes: Mutex<FxHashMap<u64, Arc<Node>>>,
}
impl Scenegraph {
@@ -34,29 +33,22 @@ impl Scenegraph {
}
pub fn add_node_raw(&self, node: Arc<Node>) {
debug!(node = ?&*node, "Add node");
let path = node.get_path().to_string();
self.nodes.lock().insert(path, node);
self.nodes.lock().insert(node.get_id(), node);
}
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
let mut node = self.nodes.lock().get(path)?.clone();
while let Ok(alias) = node.get_aspect::<Alias>() {
if alias.enabled.load(Ordering::Acquire) {
node = alias.original.upgrade()?;
} else {
return None;
}
}
Some(node)
pub fn get_node(&self, node: u64) -> Option<Arc<Node>> {
let node = self.nodes.lock().get(&node)?.clone();
get_original(node, true)
}
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
debug!(path, "Remove node");
self.nodes.lock().remove(path)
pub fn remove_node(&self, node: u64) -> Option<Arc<Node>> {
debug!(node, "Remove node");
self.nodes.lock().remove(&node)
}
}
pub struct MethodResponseSender(oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>);
pub type MethodResponse = Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>;
pub struct MethodResponseSender(oneshot::Sender<MethodResponse>);
impl MethodResponseSender {
pub fn send(self, t: Result<Message, ScenegraphError>) {
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
@@ -94,16 +86,16 @@ fn map_method_return<T: Serialize>(
impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(
&self,
path: &str,
method: &str,
node: u64,
method: u64,
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(|| {
self.get_node(path)
debug_span!("Handle signal", node, method).in_scope(|| {
self.get_node(node)
.ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal(
client,
@@ -117,8 +109,8 @@ impl scenegraph::Scenegraph for Scenegraph {
}
fn execute_method(
&self,
path: &str,
method: &str,
node: u64,
method: u64,
data: &[u8],
fds: Vec<OwnedFd>,
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
@@ -127,8 +119,8 @@ impl scenegraph::Scenegraph for Scenegraph {
let _ = response.send(Err(ScenegraphError::MethodNotFound));
return;
};
debug!(path, method, "Handle method");
let Some(node) = self.get_node(path) else {
debug!(node, method, "Handle method");
let Some(node) = self.get_node(node) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};

View File

@@ -1,84 +1,86 @@
#![allow(clippy::empty_docs)]
mod core;
mod nodes;
mod objects;
mod session;
#[cfg(feature = "wayland")]
mod wayland;
use crate::core::client::CLIENTS;
use crate::core::client_state::ClientState;
use crate::core::destroy_queue;
use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, hmd, input};
use crate::objects::input::eye_pointer::EyePointer;
use crate::objects::input::mouse_pointer::MousePointer;
use crate::objects::input::sk_controller::SkController;
use crate::objects::input::sk_hand::SkHand;
use crate::objects::play_space::PlaySpace;
use crate::wayland::X_DISPLAY;
use crate::nodes::{audio, drawable, input};
use self::core::eventloop::EventLoop;
use clap::Parser;
use core::client::Client;
use core::task;
use directories::ProjectDirs;
use objects::ServerObjects;
use once_cell::sync::OnceCell;
use session::{launch_start, save_session};
use stardust_xr::server;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc;
use std::time::Duration;
use stereokit::{
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread,
TextureFormat, TextureType,
};
use stereokit::{DisplayBlend, Sk};
use stereokit_rust::material::Material;
use stereokit_rust::shader::Shader;
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
use stereokit_rust::system::{LogLevel, Renderer};
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
use stereokit_rust::ui::Ui;
use stereokit_rust::util::{Color128, Time};
use tokio::net::UnixListener;
use tokio::sync::Notify;
use tokio::task::LocalSet;
use tokio::{runtime::Handle, sync::oneshot};
use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use zbus::fdo::ObjectManager;
use zbus::Connection;
#[derive(Parser)]
#[derive(Debug, Clone, Parser)]
#[clap(author, version, about, long_about = None)]
struct CliArgs {
/// Force flatscreen mode and use the mouse pointer as a 3D pointer
#[clap(short, long, action)]
flatscreen: bool,
/// If monado insists on emulating them, set this flag...we want the raw input
#[clap(long)]
disable_controllers: bool,
/// If monado insists on emulating , set this flag...we want the raw input
#[clap(long)]
disable_hands: bool,
/// Run Stardust XR as an overlay with given priority
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
overlay_priority: Option<u32>,
/// Don't create a tip input for controller because SOME RUNTIMES will lie
#[clap(long, action)]
disable_controller: bool,
/// Debug the clients started by the server
#[clap(short = 'd', long = "debug", action)]
debug_launched_clients: bool,
/// Run a script when ready for clients to connect. If this is not set the script at $HOME/.config/stardust/startup will be ran if it exists.
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
startup_script: Option<PathBuf>,
/// Restore the session with the given ID (or `latest`), ignoring the startup script. Sessions are stored in directories at `~/.local/state/stardust/`.
#[clap(id = "SESSION_ID", long = "restore", action)]
restore: Option<String>,
}
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
static STOP_NOTIFIER: Notify = Notify::const_new();
struct EventLoopInfo {
tokio_handle: Handle,
socket_path: PathBuf,
}
fn main() {
ctrlc::set_handler(|| {
if atty::isnt(atty::Stream::Stdout) {
STOP_NOTIFIER.notify_waiters()
}
})
.unwrap();
// #[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() {
color_eyre::install().unwrap();
let registry = tracing_subscriber::registry();
#[cfg(feature = "profile_app")]
let registry = registry.with(tracing_tracy::TracyLayer::new().with_filter(LevelFilter::DEBUG));
let registry = registry.with(
tracing_tracy::TracyLayer::new(tracing_tracy::DefaultConfig::default())
.with_filter(LevelFilter::DEBUG),
);
#[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
@@ -92,22 +94,93 @@ fn main() {
.with_filter(EnvFilter::from_default_env());
registry.with(log_layer).init();
let cli_args = CliArgs::parse();
let 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");
info!(
socket_path = ?socket_path.display(),
"Stardust socket created"
);
let socket =
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
task::new(|| "client join loop", async move {
loop {
let Ok((stream, _)) = socket.accept().await else {
continue;
};
if let Err(e) = Client::from_connection(stream) {
error!(?e, "Unable to create client from connection");
}
}
})
.unwrap();
info!("Init client join loop");
let project_dirs = ProjectDirs::from("", "", "stardust");
if project_dirs.is_none() {
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
}
let cli_args = Arc::new(CliArgs::parse());
let sk = stereokit::Settings {
app_name: "Stardust XR".to_string(),
display_preference: if cli_args.flatscreen {
DisplayMode::Flatscreen
let dbus_connection = Connection::session()
.await
.expect("Could not open dbus session");
dbus_connection
.request_name("org.stardustxr.HMD")
.await
.expect("Another instance of the server is running. This is not supported currently (but is planned).");
dbus_connection
.object_server()
.at("/", ObjectManager)
.await
.expect("Couldn't add the object manager");
let sk_ready_notifier = Arc::new(Notify::new());
let stereokit_loop = tokio::task::spawn_blocking({
let sk_ready_notifier = sk_ready_notifier.clone();
let project_dirs = project_dirs.clone();
let cli_args = cli_args.clone();
let dbus_connection = dbus_connection.clone();
move || stereokit_loop(sk_ready_notifier, project_dirs, cli_args, dbus_connection)
});
sk_ready_notifier.notified().await;
let mut startup_children = project_dirs
.as_ref()
.map(|project_dirs| launch_start(&cli_args, project_dirs))
.unwrap_or_default();
tokio::select! {
_ = stereokit_loop => (),
_ = tokio::signal::ctrl_c() => unsafe {sk_quit(QuitReason::SystemClose)},
}
info!("Stopping...");
if let Some(project_dirs) = project_dirs {
save_session(&project_dirs).await;
}
for mut startup_child in startup_children.drain(..) {
let _ = startup_child.kill();
}
info!("Cleanly shut down Stardust");
}
fn stereokit_loop(
sk_ready_notifier: Arc<Notify>,
project_dirs: Option<ProjectDirs>,
args: CliArgs,
dbus_connection: Connection,
) {
let sk = SkSettings::default()
.app_name("Stardust XR")
.mode(if args.flatscreen {
AppMode::Simulator
} else {
DisplayMode::MixedReality
},
blend_preference: DisplayBlend::AnyTransparent,
depth_mode: DepthMode::D32,
log_filter: match EnvFilter::from_default_env().max_level_hint() {
AppMode::XR
})
.depth_mode(DepthMode::D32)
.log_filter(match EnvFilter::from_default_env().max_level_hint() {
Some(LevelFilter::ERROR) => LogLevel::Error,
Some(LevelFilter::WARN) => LogLevel::Warning,
Some(LevelFilter::INFO) => LogLevel::Inform,
@@ -115,224 +188,90 @@ fn main() {
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
Some(LevelFilter::OFF) => LogLevel::None,
None => LogLevel::Warning,
},
overlay_app: cli_args.overlay_priority.is_some(),
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
disable_desktop_input_window: true,
render_scaling: 2.0,
..Default::default()
}
.init()
.expect("StereoKit failed to initialize");
let _ = SK_MULTITHREAD.set(sk.multithreaded());
})
.overlay_app(args.overlay_priority.is_some())
.overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
.disable_desktop_input_window(true)
.origin(OriginMode::Local)
.init()
.expect("StereoKit failed to initialize");
info!("Init StereoKit");
sk.render_set_multisample(0);
sk.material_set_shader(
sk.material_find("default/material_pbr").unwrap(),
sk.shader_find("default/shader_pbr_clip").unwrap(),
);
Renderer::multisample(0);
Material::default().shader(Shader::pbr_clip());
Ui::enable_far_interact(false);
// Skytex/light stuff
{
if let Some((light, tex)) = project_dirs
if let Some(sky) = project_dirs
.as_ref()
.and_then(|dirs| {
let skytex_path = dirs.config_dir().join("skytex.hdr");
skytex_path
.exists()
.then(|| sk.tex_create_cubemap_file(&skytex_path, true, 100).ok())
})
.flatten()
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
.filter(|f| f.exists())
.and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok())
{
sk.render_set_skytex(&tex);
sk.render_set_skylight(light);
sky.render_as_sky();
} else {
sk.render_set_skytex(sk.tex_gen_color(
BLACK,
Renderer::skytex(Tex::gen_color(
Color128::BLACK,
1,
1,
TextureType::CUBEMAP,
TextureFormat::RGBA32,
TexType::Cubemap,
TexFormat::RGBA32,
));
}
}
let mut mouse_pointer = cli_args
.flatscreen
.then(MousePointer::new)
.transpose()
.unwrap();
let mut hands = (!cli_args.flatscreen)
.then(|| {
let left = SkHand::new(Handed::Left).ok();
let right = SkHand::new(Handed::Right).ok();
left.zip(right)
})
.flatten();
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
.then(|| {
let left = SkController::new(&sk, Handed::Left).ok();
let right = SkController::new(&sk, Handed::Right).ok();
left.zip(right)
})
.flatten();
let eye_pointer = (sk.active_display_mode() == DisplayMode::MixedReality
&& sk.device_has_eye_gaze())
.then(EyePointer::new)
.transpose()
.unwrap();
if hands.is_none() {
sk.input_hand_visible(Handed::Left, false);
sk.input_hand_visible(Handed::Right, false);
}
let play_space = sk
.world_has_bounds()
.then(|| PlaySpace::new().ok())
.flatten();
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
let event_thread = std::thread::Builder::new()
.name("event_loop".to_owned())
.spawn(move || event_loop(info_sender))
.unwrap();
let event_loop_info = info_receiver.blocking_recv().unwrap();
let _tokio_handle = event_loop_info.tokio_handle.enter();
#[cfg(feature = "wayland")]
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
#[cfg(feature = "wayland")]
wayland.make_context_current();
sk_ready_notifier.notify_waiters();
info!("Stardust ready!");
let mut startup_child = (|| {
let project_dirs = project_dirs.as_ref()?;
let startup_script_path = cli_args
.startup_script
.clone()
.and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
let mut startup_command = Command::new("bash");
startup_command.arg(startup_script_path);
startup_command.arg("&");
startup_command.stdin(Stdio::null());
startup_command.stdout(Stdio::null());
startup_command.stderr(Stdio::null());
startup_command.env(
"FLAT_WAYLAND_DISPLAY",
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
);
startup_command.env(
"STARDUST_INSTANCE",
event_loop_info
.socket_path
.file_name()
.expect("Stardust socket path not found"),
);
#[cfg(feature = "wayland")]
{
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 objects = ServerObjects::new(
dbus_connection.clone(),
&sk,
args.disable_controllers,
args.disable_hands,
);
let mut last_frame_delta = Duration::ZERO;
let mut sleep_duration = Duration::ZERO;
debug_span!("StereoKit").in_scope(|| {
sk.run(
|sk| {
let _span = debug_span!("StereoKit step");
let _span = _span.enter();
while let Some(token) = sk.step() {
let _span = debug_span!("StereoKit step");
let _span = _span.enter();
hmd::frame(sk);
camera::update(sk);
#[cfg(feature = "wayland")]
wayland.frame_event(sk);
destroy_queue::clear();
camera::update(token);
#[cfg(feature = "wayland")]
wayland.frame_event();
destroy_queue::clear();
if let Some(mouse_pointer) = &mut mouse_pointer {
mouse_pointer.update(sk);
}
if let Some((left_hand, right_hand)) = &mut hands {
left_hand.update(!cli_args.disable_controller, sk);
right_hand.update(!cli_args.disable_controller, sk);
}
if let Some((left_controller, right_controller)) = &mut controllers {
left_controller.update(sk);
right_controller.update(sk);
}
if let Some(eye_pointer) = &eye_pointer {
eye_pointer.update(sk);
}
if let Some(play_space) = &play_space {
play_space.update(sk);
}
input::process_input();
nodes::root::Root::send_frame_events(sk.time_elapsed_unscaled());
adaptive_sleep(
sk,
&mut last_frame_delta,
&mut sleep_duration,
Duration::from_micros(250),
);
objects.update(&sk, token);
input::process_input();
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
adaptive_sleep(
&mut last_frame_delta,
&mut sleep_duration,
Duration::from_micros(250),
);
#[cfg(feature = "wayland")]
wayland.update(sk);
drawable::draw(sk);
audio::update(sk);
#[cfg(feature = "wayland")]
wayland.make_context_current();
},
|_sk| {
info!("Cleanly shut down StereoKit");
if let Some(mut startup_child) = startup_child.take() {
let _ = startup_child.kill();
}
},
)
});
#[cfg(feature = "wayland")]
wayland.update();
drawable::draw(token);
audio::update();
}
info!("Cleanly shut down StereoKit");
#[cfg(feature = "wayland")]
drop(wayland);
STOP_NOTIFIER.notify_waiters();
event_thread
.join()
.expect("Failed to cleanly shut down event loop")
.unwrap();
info!("Cleanly shut down Stardust");
}
fn adaptive_sleep(
sk: &impl StereoKitMultiThread,
last_frame_delta: &mut Duration,
sleep_duration: &mut Duration,
sleep_duration_increase: Duration,
) {
let frame_delta = Duration::from_secs_f64(sk.time_elapsed_unscaled());
let frame_delta = Duration::from_secs_f64(Time::get_step_unscaled());
if *last_frame_delta < frame_delta {
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_frame_delta) {
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
@@ -348,47 +287,3 @@ fn adaptive_sleep(
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
});
}
#[tokio::main]
// #[tokio::main(flavor = "current_thread")]
async fn event_loop(info_sender: oneshot::Sender<EventLoopInfo>) -> color_eyre::eyre::Result<()> {
let 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");
let _event_loop = EventLoop::new(socket_path.clone()).expect("Couldn't create server socket");
info!("Init event loop");
info!(
socket_path = ?socket_path.display(),
"Stardust socket created"
);
let _ = info_sender.send(EventLoopInfo {
tokio_handle: Handle::current(),
socket_path,
});
STOP_NOTIFIER.notified().await;
println!("Stopping...");
save_clients().await;
info!("Cleanly shut down event loop");
unsafe {
stereokit::sys::sk_quit();
}
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,52 +1,141 @@
use super::{Aspect, Node};
use crate::core::client::Client;
use color_eyre::eyre::{ensure, Result};
use portable_atomic::AtomicBool;
use std::sync::{Arc, Weak};
use crate::core::{client::Client, registry::Registry};
use color_eyre::eyre::Result;
use std::{
ops::Add,
sync::{Arc, Weak},
};
#[derive(Debug, Default, Clone)]
pub struct AliasInfo {
pub(super) server_signals: Vec<&'static str>,
pub(super) server_methods: Vec<&'static str>,
pub(super) client_signals: Vec<&'static str>,
pub(super) server_signals: Vec<u64>,
pub(super) server_methods: Vec<u64>,
pub(super) client_signals: Vec<u64>,
}
impl Add for AliasInfo {
type Output = AliasInfo;
fn add(mut self, mut rhs: Self) -> Self::Output {
self.server_signals.append(&mut rhs.server_signals);
self.server_methods.append(&mut rhs.server_methods);
self.client_signals.append(&mut rhs.client_signals);
self
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct Alias {
pub enabled: Arc<AtomicBool>,
pub(super) node: Weak<Node>,
pub original: Weak<Node>,
pub info: AliasInfo,
pub(super) original: Weak<Node>,
pub(super) info: AliasInfo,
}
impl Alias {
pub fn create(
client: &Arc<Client>,
parent: &str,
name: &str,
original: &Arc<Node>,
client: &Arc<Client>,
info: AliasInfo,
list: Option<&AliasList>,
) -> Result<Arc<Node>> {
ensure!(
client
.scenegraph
.get_node(&(parent.to_string() + "/" + name))
.is_none(),
"Node already exists"
);
let node = Node::generate(client, true).add_to_scenegraph()?;
Self::add_to(&node, original, info)?;
if let Some(list) = list {
list.add(&node);
}
Ok(node)
}
pub fn create_with_id(
original: &Arc<Node>,
client: &Arc<Client>,
new_id: u64,
info: AliasInfo,
list: Option<&AliasList>,
) -> Result<Arc<Node>> {
let node = Node::from_id(client, new_id, true).add_to_scenegraph()?;
Self::add_to(&node, original, info)?;
if let Some(list) = list {
list.add(&node);
}
Ok(node)
}
let node = Node::create_parent_name(client, parent, name, true).add_to_scenegraph()?;
fn add_to(new_node: &Arc<Node>, original: &Arc<Node>, info: AliasInfo) -> Result<()> {
let alias = Alias {
enabled: Arc::new(AtomicBool::new(true)),
node: Arc::downgrade(&node),
node: Arc::downgrade(new_node),
original: Arc::downgrade(original),
info,
};
let alias = original.aliases.add(alias);
node.add_aspect_raw(alias);
Ok(node)
new_node.add_aspect_raw(alias);
Ok(())
}
}
impl Aspect for Alias {
const NAME: &'static str = "Alias";
}
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
let Ok(alias) = node.get_aspect::<Alias>() else {
return Some(node);
};
if stop_on_disabled && !node.enabled() {
return None;
}
get_original(alias.original.upgrade()?, stop_on_disabled)
}
pub fn links_to(alias: Arc<Node>, original: Weak<Node>) -> bool {
let Ok(alias) = alias.get_aspect::<Alias>() else {
return false;
};
if alias.original.ptr_eq(&original) {
return true;
}
let Some(original_strong) = alias.original.upgrade() else {
return false;
};
links_to(original_strong, original)
}
#[derive(Debug, Default, Clone)]
pub struct AliasList(Registry<Node>);
impl AliasList {
fn add(&self, node: &Arc<Node>) {
self.0.add_raw(node);
}
pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> {
self.0
.get_valid_contents()
.into_iter()
.find(move |node| links_to(node.clone(), original.clone()))
}
pub fn get_from_aspect<A: Aspect>(&self, aspect: &A) -> Option<Arc<Node>> {
self.0.get_valid_contents().into_iter().find(|node| {
let Some(node) = get_original(node.clone(), false) else {
return false;
};
let Ok(aspect2) = node.get_aspect::<A>() else {
return false;
};
Arc::as_ptr(&aspect2) == (aspect as *const A)
})
}
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
self.0.get_valid_contents()
}
pub fn remove_aspect<A: Aspect>(&self, aspect: &A) {
self.0.retain(|node| {
let Some(original) = get_original(node.clone(), false) else {
return false;
};
let Ok(aspect2) = original.get_aspect::<A>() else {
return false;
};
Arc::as_ptr(&aspect2) != (aspect as *const A)
})
}
}
impl Drop for AliasList {
fn drop(&mut self) {
for node in self.0.take_valid_contents() {
node.destroy();
}
}
}

View File

@@ -4,18 +4,18 @@ use crate::core::destroy_queue;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::create_interface;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::{Spatial, Transform};
use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use stardust_xr::values::ResourceID;
use std::ops::DerefMut;
use std::sync::Arc;
use std::{ffi::OsStr, path::PathBuf};
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
@@ -25,8 +25,8 @@ pub struct Sound {
volume: f32,
pending_audio_path: PathBuf,
sk_sound: OnceCell<SendWrapper<SkSound>>,
instance: Mutex<Option<SoundInstance>>,
sk_sound: OnceCell<SkSound>,
instance: Mutex<Option<SoundInst>>,
stop: Mutex<Option<()>>,
play: Mutex<Option<()>>,
}
@@ -53,24 +53,21 @@ impl Sound {
Ok(sound_arc)
}
fn update(&self, sk: &impl StereoKitDraw) {
let sound = self.sk_sound.get_or_init(|| {
SendWrapper::new(sk.sound_create(self.pending_audio_path.clone()).unwrap())
});
fn update(&self) {
let sound = self
.sk_sound
.get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
if self.stop.lock().take().is_some() {
if let Some(instance) = self.instance.lock().take() {
sk.sound_inst_stop(instance);
instance.stop();
}
}
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
self.instance.lock().replace(sk.sound_play(
sound.as_ref(),
vec3(0.0, 0.0, 0.0),
self.volume,
));
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
self.instance.lock().replace(instance);
}
if let Some(instance) = self.instance.lock().deref_mut() {
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
instance.position(self.space.global_transform().w_axis.xyz());
}
}
}
@@ -98,26 +95,25 @@ impl Drop for Sound {
}
}
pub fn update(sk: &impl StereoKitDraw) {
pub fn update() {
for sound in SOUND_REGISTRY.get_valid_contents() {
sound.update(sk)
sound.update()
}
}
create_interface!(AudioInterface, AudioInterfaceAspect, "/audio");
create_interface!(AudioInterface);
struct AudioInterface;
impl AudioInterfaceAspect for AudioInterface {
impl InterfaceAspect for AudioInterface {
#[doc = "Create a sound node. WAV and MP3 are supported."]
fn create_sound(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
parent: Arc<Node>,
transform: Transform,
resource: ResourceID,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_SOUND_PARENT_PATH, &name, true);
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;

View File

@@ -1,26 +1,27 @@
use super::alias::AliasInfo;
use super::alias::AliasList;
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Node};
use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::create_interface;
use crate::nodes::fields::FIELD_ALIAS_INFO;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{bail, ensure, eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use stardust_xr::schemas::flex::flexbuffers;
use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak};
lazy_static! {
pub static ref KEYMAPS: Mutex<FxHashMap<String, String>> = Mutex::new(FxHashMap::default());
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
}
// TODO: probably just use d-bus for this stuff (custom protocol for exporting spatials as refs) because the mask stuff is just too confusing
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
@@ -37,7 +38,7 @@ pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bo
let greater_key = get_mask(mask_map_greater)?.index(key)?;
// 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
&& lesser_key.as_vector().is_empty()
&& greater_key.flexbuffer_type().is_vector()
{
continue;
@@ -58,14 +59,16 @@ stardust_xr_server_codegen::codegen_data_protocol!();
pub struct PulseSender {
node: Weak<Node>,
pub mask: Datamap,
aliases: LifeLinkedNodeMap<String>,
aliases: AliasList,
field_aliases: AliasList,
}
impl PulseSender {
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
let sender = PulseSender {
node: Arc::downgrade(node),
mask,
aliases: LifeLinkedNodeMap::default(),
aliases: AliasList::default(),
field_aliases: AliasList::default(),
};
// <PulseSender as PulseSenderAspect>::add_node_members(node);
@@ -91,44 +94,43 @@ impl PulseSender {
};
// Receiver itself
let Ok(rx_alias) = Alias::create(
&tx_client,
tx_node.get_path(),
receiver.uid.as_str(),
&rx_node,
AliasInfo {
server_methods: vec!["send_data"],
..Default::default()
},
&tx_client,
PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
Some(&self.aliases),
) else {
return;
};
self.aliases.add(receiver.uid.clone(), &rx_alias);
// Receiver's field
let Ok(rx_field_alias) = Alias::create(
&rx_node
.get_aspect::<PulseReceiver>()
.unwrap()
.field
.spatial
.node()
.unwrap(),
&tx_client,
rx_alias.get_path(),
"field",
&rx_node.get_aspect::<PulseReceiver>().unwrap().field_node,
FIELD_ALIAS_INFO.clone(),
Some(&self.aliases),
) 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);
let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
}
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str();
self.aliases.remove(uid);
self.aliases.remove(&(uid.to_string() + "-field"));
let Some(node) = receiver.node.upgrade() else {
return;
};
self.aliases.remove_aspect(receiver);
self.field_aliases.remove_aspect(receiver.field.as_ref());
let Some(tx_node) = self.node.upgrade() else {
return;
};
let _ = pulse_sender_client::drop_receiver(&tx_node, uid);
let _ = pulse_sender_client::drop_receiver(&tx_node, node.get_id());
}
}
impl Aspect for PulseSender {
@@ -142,21 +144,19 @@ impl Drop for PulseSender {
}
pub struct PulseReceiver {
uid: String,
pub node: Weak<Node>,
pub field_node: Arc<Node>,
pub field: Arc<Field>,
pub mask: Datamap,
}
impl PulseReceiver {
pub fn add_to(
node: &Arc<Node>,
field_node: Arc<Node>,
field: Arc<Field>,
mask: Datamap,
) -> Result<Arc<PulseReceiver>> {
let receiver = PulseReceiver {
uid: nanoid!(),
node: Arc::downgrade(node),
field_node,
field,
mask,
};
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
@@ -186,7 +186,7 @@ impl PulseReceiverAspect for PulseReceiver {
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
this_receiver.mask
);
pulse_receiver_client::data(&node, &sender.uid, &data)?;
pulse_receiver_client::data(&node, &sender, &data)?;
Ok(())
}
}
@@ -199,24 +199,19 @@ impl Drop for PulseReceiver {
}
}
create_interface!(DataInterface, DataInterfaceAspect, "/data");
create_interface!(DataInterface);
struct DataInterface;
impl DataInterfaceAspect for DataInterface {
impl InterfaceAspect for DataInterface {
fn create_pulse_sender(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
parent: Arc<Node>,
transform: Transform,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_PULSE_SENDER_PARENT_PATH,
&name,
true,
);
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false);
@@ -229,22 +224,17 @@ impl DataInterfaceAspect for DataInterface {
fn create_pulse_receiver(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_PULSE_RECEIVER_PARENT_PATH,
&name,
true,
);
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let _ = field.get_aspect::<Field>()?;
let field = field.get_aspect::<Field>()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
@@ -256,7 +246,7 @@ impl DataInterfaceAspect for DataInterface {
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap: String,
) -> Result<String> {
) -> Result<u64> {
let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps
.iter()
@@ -264,22 +254,20 @@ impl DataInterfaceAspect for DataInterface {
.map(|(k, _v)| k)
.last()
{
return Ok(found_keymap_id.clone());
return Ok(found_keymap_id.data().as_ffi());
}
let generated_id = nanoid!();
keymaps.insert(generated_id.clone(), keymap);
Ok(generated_id)
let key = keymaps.insert(keymap);
Ok(key.data().as_ffi())
}
async fn get_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap_id: String,
keymap_id: u64,
) -> Result<String> {
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(&keymap_id) else {
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
bail!("Could not find keymap. Try registering it")
};

View File

@@ -4,17 +4,17 @@ use crate::{
nodes::{spatial::Spatial, Aspect, Node},
};
use color_eyre::eyre::Result;
use glam::Vec3A;
use glam::Vec3;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use prisma::Lerp;
use std::{collections::VecDeque, sync::Arc};
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
use stereokit_rust::{
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
};
static LINES_REGISTRY: Registry<Lines> = Registry::new();
pub struct Lines {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
data: Mutex<Vec<Line>>,
}
@@ -29,7 +29,7 @@ impl Lines {
if let Ok(lines) = node.get_aspect::<Lines>() {
for line in &*lines.data.lock() {
for point in &line.points {
bounds = bounds_grow_to_fit_pt(bounds, point.point);
bounds.grown_point(Vec3::from(point.point));
}
}
}
@@ -37,7 +37,6 @@ impl Lines {
});
let lines = LINES_REGISTRY.add(Lines {
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(lines),
});
@@ -47,7 +46,7 @@ impl Lines {
Ok(lines)
}
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
fn draw(&self, token: &MainThreadToken) {
let transform_mat = self.space.global_transform();
let data = self.data.lock().clone();
for line in &data {
@@ -55,15 +54,9 @@ impl Lines {
.points
.iter()
.map(|p| SkLinePoint {
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
thickness: p.thickness,
color: stereokit::sys::color128::from([
p.color.c.r,
p.color.c.g,
p.color.c.b,
p.color.a,
])
.into(),
color: Color128::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
})
.collect();
if line.cyclic && !points.is_empty() {
@@ -78,17 +71,15 @@ impl Lines {
};
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(
Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5),
)
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: color.into(),
};
points.push_front(connect_point.clone());
points.push_front(connect_point);
points.push_back(connect_point);
}
draw_ctx.line_add_listv(points.make_contiguous());
stereokit_rust::system::Lines::add_list(token, points.make_contiguous());
}
}
}
@@ -108,10 +99,12 @@ impl Drop for Lines {
}
}
pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
pub fn draw_all(token: &MainThreadToken) {
for lines in LINES_REGISTRY.get_valid_contents() {
if lines.enabled.load(Ordering::Relaxed) {
lines.draw(draw_ctx);
if let Some(node) = lines.space.node() {
if node.enabled() {
lines.draw(token);
}
}
}
}

View File

@@ -1,5 +1,7 @@
pub mod lines;
pub mod model;
#[cfg(feature = "wayland")]
pub mod shader_manipulation;
pub mod shaders;
pub mod text;
@@ -8,6 +10,7 @@ use super::{
spatial::{Spatial, Transform},
Node,
};
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::{
core::{client::Client, resource::get_resource_file},
create_interface,
@@ -16,22 +19,22 @@ use color_eyre::eyre::{self, Result};
use parking_lot::Mutex;
use stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::StereoKitDraw;
use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
// #[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) {
lines::draw_all(sk);
model::draw_all(sk);
text::draw_all(sk);
pub fn draw(token: &MainThreadToken) {
lines::draw_all(token);
model::draw_all(token);
text::draw_all(token);
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
sk.render_set_skytex(&skytex);
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
Renderer::skytex(skytex.tex);
}
}
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
sk.render_set_skylight(skylight);
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
Renderer::skylight(skylight.sh);
}
}
}
@@ -40,10 +43,10 @@ static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
stardust_xr_server_codegen::codegen_drawable_protocol!();
create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable");
create_interface!(DrawableInterface);
pub struct DrawableInterface;
impl DrawableInterfaceAspect for DrawableInterface {
impl InterfaceAspect for DrawableInterface {
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")])
.ok_or(eyre::eyre!("Could not find resource"))?;
@@ -65,13 +68,12 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn create_lines(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
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 node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
@@ -84,13 +86,12 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn load_model(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
parent: Arc<Node>,
transform: Transform,
model: ResourceID,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::LOAD_MODEL_PARENT_PATH, &name, true);
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
@@ -102,14 +103,13 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn create_text(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
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 node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);

View File

@@ -1,73 +1,124 @@
use super::{MaterialParameter, ModelAspect, ModelPartAspect, Node};
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::Spatial;
use crate::nodes::Aspect;
use crate::SK_MULTITHREAD;
use color_eyre::eyre::{eyre, Result};
use once_cell::sync::OnceCell;
use crate::nodes::{Aspect, Node};
use color_eyre::eyre::{bail, eyre, Result};
use glam::{Mat4, Vec2, Vec3};
use once_cell::sync::{Lazy, OnceCell};
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use stardust_xr::values::ResourceID;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Weak};
use stereokit::named_colors::WHITE;
use stereokit::{
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
StereoKitMultiThread, Transparency,
};
use stereokit_rust::material::Transparency;
use stereokit_rust::maths::Bounds;
use stereokit_rust::sk::MainThreadToken;
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
pub struct MaterialWrapper(pub Material);
impl Hash for MaterialWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.get_shader().0.as_ptr().hash(state);
for param in self.0.get_all_param_info() {
param.to_string().hash(state)
}
self.0.get_chain().map(MaterialWrapper).hash(state)
}
}
impl PartialEq for MaterialWrapper {
fn eq(&self, other: &Self) -> bool {
if self.0.get_shader().0.as_ptr() != other.0.get_shader().0.as_ptr() {
return false;
}
if self.0.get_all_param_info().count() != other.0.get_all_param_info().count() {
return false;
}
for self_param in self.0.get_all_param_info() {
let Some(other_param) = other
.0
.get_all_param_info()
.get_data(self_param.get_name(), self_param.get_type())
else {
return false;
};
if self_param.to_string() != other_param.to_string() {
return false;
}
}
self.0.get_chain().map(MaterialWrapper) == other.0.get_chain().map(MaterialWrapper)
}
}
impl Eq for MaterialWrapper {}
unsafe impl Send for MaterialWrapper {}
unsafe impl Sync for MaterialWrapper {}
#[derive(Default)]
struct MaterialRegistry(Mutex<FxHashMap<u64, String>>);
impl MaterialRegistry {
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
let mut lock = self.0.lock();
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
material.hash(&mut hasher);
hasher.finish()
};
if let Some(id) = lock.get(&hash) {
if let Ok(existing) = Material::find(id) {
return Arc::new(MaterialWrapper(existing));
}
}
lock.insert(hash, material.0.get_id().to_string());
material
}
}
static MATERIAL_REGISTRY: Lazy<MaterialRegistry> = Lazy::new(MaterialRegistry::default);
static MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
impl MaterialParameter {
fn apply_to_material(
&self,
client: &Client,
sk: &impl StereoKitMultiThread,
material: &Material,
parameter_name: &str,
) {
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
let mut params = material.get_all_param_info();
match self {
MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val);
params.set_bool(parameter_name, *val);
}
MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *val);
params.set_int(parameter_name, &[*val]);
}
MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val);
params.set_uint(parameter_name, &[*val]);
}
MaterialParameter::Float(val) => {
sk.material_set_float(material, parameter_name, *val);
params.set_float(parameter_name, *val);
}
MaterialParameter::Vec2(val) => {
sk.material_set_vector2(material, parameter_name, *val);
params.set_vec2(parameter_name, Vec2::from(*val));
}
MaterialParameter::Vec3(val) => {
sk.material_set_vector3(material, parameter_name, *val);
params.set_vec3(parameter_name, Vec3::from(*val));
}
MaterialParameter::Color(val) => {
sk.material_set_color(
material,
params.set_color(
parameter_name,
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
);
}
MaterialParameter::Texture(resource) => {
let Some(texture_path) =
get_resource_file(&resource, &client, &[OsStr::new("png"), OsStr::new("jpg")])
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
else {
return;
};
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
sk.material_set_texture(material, parameter_name, &tex);
if let Ok(tex) = Tex::from_file(texture_path, true, None) {
params.set_texture(parameter_name, &tex);
}
}
}
@@ -76,129 +127,113 @@ impl MaterialParameter {
pub struct ModelPart {
id: i32,
path: PathBuf,
path: String,
space: Arc<Spatial>,
model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<Material>>>,
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
aliases: AliasList,
}
impl ModelPart {
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
fn create_for_model(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 mut mat = Material::copy(&Material::unlit());
mat.transparency(Transparency::None);
mat.color_tint(Color128::BLACK_TRANSPARENT);
Arc::new(MaterialWrapper(mat))
});
let first_root_part = sk.model_node_get_root(sk_model);
let mut current_option_part = Some(first_root_part);
while let Some(current_part) = &mut current_option_part {
ModelPart::create(sk, model, sk_model, *current_part);
if let Some(child) = sk.model_node_child(sk_model, *current_part) {
*current_part = child;
} else if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
*current_part = sibling;
} else {
while let Some(current_part) = &mut current_option_part {
if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
*current_part = sibling;
break;
}
current_option_part = sk.model_node_parent(sk_model, *current_part);
}
}
let nodes = sk_model.get_nodes();
for part in nodes.all() {
ModelPart::create(model, &part);
}
}
fn create(
sk: &impl StereoKitMultiThread,
model: &Arc<Model>,
sk_model: &SKModel,
id: i32,
) -> Option<Arc<Self>> {
let parent_node = sk
.model_node_parent(sk_model, id)
.and_then(|id| model.parts.get(&id));
let parent_part = parent_node
.as_ref()
.and_then(|node| node.get_aspect::<ModelPart>().ok());
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
let mut parts = model.parts.lock();
let parent_part = part
.get_parent()
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
let stardust_model_part = model.space.node()?;
let client = stardust_model_part.get_client()?;
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)?);
let node = client.scenegraph.add_node(Node::create_parent_name(
&client,
stardust_model_part.get_path(),
part_path.to_str()?,
false,
));
let spatial_parent = parent_node
.and_then(|n| n.get_aspect::<Spatial>().ok())
let mut part_path = parent_part
.map(|n| n.path.clone() + "/")
.unwrap_or_default();
part_path += part.get_name().unwrap();
let node = client.scenegraph.add_node(Node::generate(&client, false));
let spatial_parent = parent_part
.map(|n| n.space.clone())
.unwrap_or_else(|| model.space.clone());
let local_transform = unsafe { part.get_local_transform().m };
let space = Spatial::add_to(
&node,
Some(spatial_parent),
sk.model_node_get_transform_local(sk_model, id),
Mat4::from_cols_array(&local_transform),
false,
);
let _ = node
.get_aspect::<Spatial>()
.unwrap()
.bounding_box_calc
.set(|node| {
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
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 _ = space.bounding_box_calc.set(|node| {
let Ok(model_part) = node.get_aspect::<ModelPart>() 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 model_nodes = sk_model.get_nodes();
let Some(model_node) = model_nodes.get_index(model_part.id) else {
return Bounds::default();
};
let Some(sk_mesh) = model_node.get_mesh() else {
return Bounds::default();
};
sk_mesh.get_bounds()
});
let model_part = Arc::new(ModelPart {
id,
id: *part.get_id(),
path: part_path,
space,
model: Arc::downgrade(model),
pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None),
aliases: AliasList::default(),
});
<ModelPart as ModelPartAspect>::add_node_members(&node);
node.add_aspect_raw(model_part.clone());
model.parts.add(id, &node);
parts.push(model_part.clone());
Some(model_part)
}
pub fn replace_material(&self, replacement: Arc<Material>) {
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
self.pending_material_replacement
.lock()
.replace(replacement);
.replace(shared_material);
}
/// only to be run on the main thread
pub fn replace_material_now(&self, replacement: &Material) {
let Some(model) = self.model.upgrade() else {
return;
};
let Some(sk_model) = model.sk_model.get() else {
return;
};
let nodes = sk_model.get_nodes();
let Some(mut part) = nodes.get_index(self.id) else {
return;
};
let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
part.material(&shared_material.0);
}
fn update(&self, sk: &impl StereoKitDraw) {
fn update(&self) {
let Some(model) = self.model.upgrade() else {
return;
};
@@ -208,28 +243,39 @@ impl ModelPart {
let Some(node) = model.space.node() else {
return;
};
let nodes = sk_model.get_nodes();
let Some(mut part) = nodes.get_index(self.id) else {
return;
};
part.model_transform(Spatial::space_to_space_matrix(
Some(&self.space),
Some(&model.space),
));
let Some(client) = node.get_client() else {
return;
};
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());
part.material(&material_replacement.0);
}
let mut material_parameters = self.pending_material_parameters.lock();
for (parameter_name, parameter_value) in material_parameters.drain() {
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {
continue;
};
let new_material = sk.material_copy(material);
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
sk.model_node_set_material(sk_model, self.id, &new_material);
}
'mat_params: {
let mut material_parameters = self.pending_material_parameters.lock();
if !material_parameters.is_empty() {
let Some(material) = part.get_material() else {
break 'mat_params;
};
let new_material = material.copy();
for (parameter_name, parameter_value) in material_parameters.drain() {
parameter_value.apply_to_material(&client, &new_material, &parameter_name);
}
sk.model_node_set_transform_model(
sk_model,
self.id,
Spatial::space_to_space_matrix(Some(&self.space), Some(&model.space)),
);
let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
part.material(&shared_material.0);
}
}
}
}
impl Aspect for ModelPart {
@@ -261,16 +307,11 @@ impl ModelPartAspect for ModelPart {
}
pub struct Model {
self_ref: Weak<Model>,
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
_resource_id: ResourceID,
sk_model: OnceCell<SKModel>,
parts: LifeLinkedNodeMap<i32>,
parts: Mutex<Vec<Arc<ModelPart>>>,
}
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
let pending_model_path = get_resource_file(
@@ -280,61 +321,81 @@ impl Model {
)
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model {
self_ref: self_ref.clone(),
enabled: node.enabled.clone(),
let model = Arc::new(Model {
space: node.get_aspect::<Spatial>().unwrap().clone(),
_resource_id: resource_id,
sk_model: OnceCell::new(),
parts: LifeLinkedNodeMap::default(),
parts: Mutex::new(Vec::default()),
});
<Model as ModelAspect>::add_node_members(node);
MODEL_REGISTRY.add_raw(&model);
let sk = SK_MULTITHREAD.get().unwrap();
let sk_model = sk.model_copy(
sk.model_create_file(pending_model_path.to_str().unwrap(), None::<Shader>)?,
);
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
let sk_model = SKModel::copy(SKModel::from_file(
pending_model_path.to_str().unwrap(),
None,
)?);
ModelPart::create_for_model(&model, &sk_model);
let _ = model.sk_model.set(sk_model);
node.add_aspect_raw(model.clone());
Ok(model)
}
fn draw(&self, sk: &impl StereoKitDraw) {
fn draw(&self, token: &MainThreadToken) {
let Some(sk_model) = self.sk_model.get() else {
return;
};
for model_node_node in self.parts.nodes() {
if let Ok(model_node) = model_node_node.get_aspect::<ModelPart>() {
model_node.update(sk);
};
let parts = self.parts.lock();
for model_node in &*parts {
model_node.update();
}
drop(parts);
sk.model_draw(
sk_model,
self.space.global_transform(),
WHITE,
RenderLayer::LAYER0,
);
if let Some(node) = self.space.node() {
if node.enabled() {
sk_model.draw(token, self.space.global_transform(), None, None);
}
}
}
}
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
impl Aspect for Model {
const NAME: &'static str = "Model";
}
impl ModelAspect for Model {}
impl ModelAspect for Model {
#[doc = "Bind a model part to the node with the ID input."]
fn bind_model_part(
node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
part_path: String,
) -> color_eyre::eyre::Result<()> {
let model = node.get_aspect::<Model>()?;
let parts = model.parts.lock();
let Some(part) = parts.iter().find(|p| p.path == part_path) else {
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",)
};
Alias::create_with_id(
&part.space.node().unwrap(),
&calling_client,
id,
MODEL_PART_ASPECT_ALIAS_INFO.clone(),
Some(&part.aliases),
)?;
Ok(())
}
}
impl Drop for Model {
fn drop(&mut self) {
if let Some(sk_model) = self.sk_model.take() {
destroy_queue::add(sk_model);
}
MODEL_REGISTRY.remove(self);
}
}
pub fn draw_all(sk: &impl StereoKitDraw) {
pub fn draw_all(token: &MainThreadToken) {
for model in MODEL_REGISTRY.get_valid_contents() {
if model.enabled.load(Ordering::Relaxed) {
model.draw(sk);
}
model.draw(token);
}
}

View File

@@ -3,16 +3,9 @@ use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError,
};
use std::mem::transmute;
use stereokit::Shader;
use stereokit_rust::shader::{Shader, _ShaderT};
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 {
asset_type: i32,
asset_state: i32,
@@ -133,7 +126,7 @@ pub unsafe fn shader_inject(
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
let gl_prog = link_program(c, gl_vert, gl_frag)?;
let shader: *mut FfiShader = transmute(sk_shader.0.as_mut());
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
if let Some(shader) = shader.as_mut() {
shader.shader.vertex = gl_vert;
shader.shader.pixel = gl_frag;

View File

@@ -0,0 +1,5 @@
// Simula shader with fancy lanzcos sampling
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_gamma.hlsl.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");

View File

@@ -6,10 +6,12 @@ use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Mat4, Vec2};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{
named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle as SkTextStyle,
use stereokit_rust::{
font::Font,
sk::MainThreadToken,
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
util::{Color128, Color32},
};
use super::{TextAspect, TextStyle};
@@ -17,22 +19,20 @@ use super::{TextAspect, TextStyle};
static TEXT_REGISTRY: Registry<Text> = Registry::new();
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
let x_align = match x_align {
super::XAlign::Left => TextAlign::XLeft,
super::XAlign::Center => TextAlign::XCenter,
super::XAlign::Right => TextAlign::XRight,
} as u32;
let y_align = match y_align {
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) }
match (x_align, y_align) {
(super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
(super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
(super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
(super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
(super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
(super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter,
(super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight,
(super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight,
(super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight,
}
}
pub struct Text {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceCell<SkTextStyle>,
@@ -44,10 +44,9 @@ impl Text {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>().unwrap().clone(),
font_path: style.font.as_ref().and_then(|res| {
get_resource_file(&res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
}),
style: OnceCell::new(),
@@ -60,16 +59,16 @@ impl Text {
Ok(text)
}
fn draw(&self, sk: &impl StereoKitDraw) {
fn draw(&self, token: &MainThreadToken) {
let style =
self.style
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
let font = self
.font_path
.as_deref()
.and_then(|path| sk.font_create(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) })
.and_then(|path| Font::from_file(path).ok())
.unwrap_or_default();
Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
});
if let Ok(style) = style {
@@ -82,7 +81,8 @@ impl Text {
data.character_height,
));
if let Some(bounds) = &data.bounds {
sk.text_add_in(
stereokit_rust::system::Text::add_in(
token,
&*text,
transform,
Vec2::from(bounds.bounds) / data.character_height,
@@ -93,21 +93,40 @@ impl Text {
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),
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
} else {
sk.text_add_at(
stereokit_rust::system::Text::add_at(
token,
&*text,
transform,
*style,
TextAlign::Center,
convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
vec3(0.0, 0.0, 0.0),
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
}
}
@@ -142,10 +161,12 @@ impl Drop for Text {
}
}
pub fn draw_all(sk: &impl StereoKitDraw) {
pub fn draw_all(token: &MainThreadToken) {
for text in TEXT_REGISTRY.get_valid_contents() {
if text.enabled.load(Ordering::Relaxed) {
text.draw(sk);
if let Some(node) = text.space.node() {
if node.enabled() {
text.draw(token);
}
}
}
}

287
src/nodes/fields.rs Normal file
View File

@@ -0,0 +1,287 @@
use super::alias::{Alias, AliasInfo};
use super::spatial::{
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
};
use super::{Aspect, Node};
use crate::core::client::Client;
use crate::create_interface;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{OptionExt, Result};
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::values::Vector3;
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 {
server_methods: vec![
SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE,
FIELD_REF_DISTANCE_SERVER_OPCODE,
FIELD_REF_NORMAL_SERVER_OPCODE,
FIELD_REF_CLOSEST_POINT_SERVER_OPCODE,
FIELD_REF_RAY_MARCH_SERVER_OPCODE,
],
..Default::default()
});
stardust_xr_server_codegen::codegen_field_protocol!();
lazy_static::lazy_static! {
pub static ref EXPORTED_FIELDS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
}
pub trait FieldTrait: Send + Sync + 'static {
fn spatial_ref(&self) -> &Spatial;
fn local_distance(&self, p: Vec3A) -> f32;
fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
let d = self.local_distance(p);
let e = vec2(r, 0_f32);
let n = vec3a(d, d, d)
- vec3a(
self.local_distance(vec3a(e.x, e.y, e.y)),
self.local_distance(vec3a(e.y, e.x, e.y)),
self.local_distance(vec3a(e.y, e.y, e.x)),
);
n.normalize()
}
fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
p - (self.local_normal(p, r) * self.local_distance(p))
}
fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
self.local_distance(local_p)
}
fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
reference_to_local_space
.inverse()
.transform_vector3a(self.local_normal(local_p, r))
}
fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
reference_to_local_space
.inverse()
.transform_point3a(self.local_closest_point(local_p, r))
}
fn ray_march(&self, ray: Ray) -> RayMarchResult {
let mut result = RayMarchResult {
ray_origin: ray.origin.into(),
ray_direction: ray.direction.into(),
min_distance: f32::MAX,
deepest_point_distance: 0_f32,
ray_length: 0_f32,
ray_steps: 0,
};
let ray_to_field_matrix =
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 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 {
let distance = self.local_distance(ray_point);
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
result.ray_length += march_distance;
ray_point += ray_direction * march_distance;
if result.min_distance > distance {
result.deepest_point_distance = result.ray_length;
result.min_distance = distance;
}
result.ray_steps += 1;
}
result
}
}
pub struct Ray {
pub origin: Vec3,
pub direction: Vec3,
pub space: Arc<Spatial>,
}
// const MIN_RAY_STEPS: u32 = 0;
const MAX_RAY_STEPS: u32 = 1000;
const MIN_RAY_MARCH: f32 = 0.001_f32;
const MAX_RAY_MARCH: f32 = f32::MAX;
// const MIN_RAY_LENGTH: f32 = 0_f32;
const MAX_RAY_LENGTH: f32 = 1000_f32;
pub struct Field {
pub spatial: Arc<Spatial>,
pub shape: Mutex<Shape>,
}
impl Field {
pub fn add_to(node: &Arc<Node>, shape: Shape) -> Result<Arc<Field>> {
let spatial = node.get_aspect::<Spatial>()?;
let field = Field {
spatial,
shape: Mutex::new(shape),
};
let field = node.add_aspect(field);
<Field as FieldRefAspect>::add_node_members(node);
<Field as FieldAspect>::add_node_members(node);
Ok(field)
}
}
impl Aspect for Field {
const NAME: &'static str = "Field";
}
impl FieldRefAspect for Field {
async fn distance(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: Vector3<f32>,
) -> Result<f32> {
let reference_space = space.get_aspect::<Spatial>()?;
let field = node.get_aspect::<Field>()?;
Ok(field.distance(&reference_space, point.into()))
}
async fn normal(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let field = node.get_aspect::<Field>()?;
Ok(field.normal(&reference_space, point.into(), 0.0001).into())
}
async fn closest_point(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let field = node.get_aspect::<Field>()?;
Ok(field
.closest_point(&reference_space, point.into(), 0.0001)
.into())
}
async fn ray_march(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
ray_origin: Vector3<f32>,
ray_direction: Vector3<f32>,
) -> Result<RayMarchResult> {
let space = space.get_aspect::<Spatial>()?;
let field = node.get_aspect::<Field>()?;
Ok(field.ray_march(Ray {
origin: ray_origin.into(),
direction: ray_direction.into(),
space,
}))
}
}
impl FieldAspect for Field {
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
let field = node.get_aspect::<Field>()?;
*field.shape.lock() = shape;
Ok(())
}
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
let id = rand::random();
EXPORTED_FIELDS.lock().insert(id, node);
Ok(id)
}
}
impl FieldTrait for Field {
fn spatial_ref(&self) -> &Spatial {
&self.spatial
}
fn local_distance(&self, p: Vec3A) -> f32 {
match self.shape.lock().clone() {
Shape::Box(size) => {
let q = vec3(
p.x.abs() - (size.x * 0.5_f32),
p.y.abs() - (size.y * 0.5_f32),
p.z.abs() - (size.z * 0.5_f32),
);
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
}
Shape::Cylinder(CylinderShape { length, radius }) => {
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
}
Shape::Sphere(radius) => p.length() - radius,
Shape::Torus(TorusShape { radius_a, radius_b }) => {
let q = vec2(p.xz().length() - radius_a, p.y);
q.length() - radius_b
}
}
}
}
create_interface!(FieldInterface);
pub struct FieldInterface;
impl InterfaceAspect for FieldInterface {
async fn import_field_ref(
_node: Arc<Node>,
calling_client: Arc<Client>,
uid: u64,
) -> Result<Arc<Node>> {
EXPORTED_FIELDS
.lock()
.get(&uid)
.map(|s| {
Alias::create(
s,
&calling_client,
FIELD_REF_ASPECT_ALIAS_INFO.clone(),
None,
)
.unwrap()
})
.ok_or_eyre("Couldn't find spatial with that ID")
}
fn create_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
shape: Shape,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Field::add_to(&node, shape)?;
Ok(())
}
}

View File

@@ -1,60 +0,0 @@
use super::{BoxFieldAspect, FieldTrait, Node};
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use crate::{core::client::Client, nodes::fields::Field};
use color_eyre::eyre::Result;
use glam::{vec3, vec3a, Vec3, Vec3A};
use mint::Vector3;
use parking_lot::Mutex;
use std::sync::Arc;
pub struct BoxField {
space: Arc<Spatial>,
size: Mutex<Vec3>,
}
impl BoxField {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) {
let box_field = BoxField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
size: Mutex::new(size.into()),
};
<BoxField as FieldAspect>::add_node_members(node);
<BoxField as BoxFieldAspect>::add_node_members(node);
node.add_aspect(Field::Box(box_field));
}
pub fn set_size(&self, size: Vector3<f32>) {
*self.size.lock() = size.into();
}
}
impl FieldTrait for BoxField {
fn local_distance(&self, p: Vec3A) -> f32 {
let size = self.size.lock();
let q = vec3(
p.x.abs() - (size.x * 0.5_f32),
p.y.abs() - (size.y * 0.5_f32),
p.z.abs() - (size.z * 0.5_f32),
);
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
}
fn spatial_ref(&self) -> &Spatial {
self.space.as_ref()
}
}
impl BoxFieldAspect for BoxField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
size: mint::Vector3<f32>,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Box(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(size.into());
Ok(())
}
}

View File

@@ -1,61 +0,0 @@
use super::{CylinderFieldAspect, Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct CylinderField {
space: Arc<Spatial>,
length: AtomicF32,
radius: AtomicF32,
}
impl CylinderField {
pub fn add_to(node: &Arc<Node>, length: f32, radius: f32) {
let cylinder_field = CylinderField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
length: AtomicF32::new(length.abs()),
radius: AtomicF32::new(radius.abs()),
};
<CylinderField as FieldAspect>::add_node_members(node);
<CylinderField as CylinderFieldAspect>::add_node_members(node);
node.add_aspect(Field::Cylinder(cylinder_field));
}
pub fn set_size(&self, length: f32, radius: f32) {
self.length.store(length.abs(), Ordering::Relaxed);
self.radius.store(radius.abs(), Ordering::Relaxed);
}
}
impl FieldTrait for CylinderField {
fn local_distance(&self, p: Vec3A) -> f32 {
let radius = self.radius.load(Ordering::Relaxed);
let length = self.length.load(Ordering::Relaxed);
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
}
fn spatial_ref(&self) -> &Spatial {
self.space.as_ref()
}
}
impl CylinderFieldAspect for CylinderField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
length: 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(())
}
}

View File

@@ -1,369 +0,0 @@
pub mod r#box;
mod cylinder;
mod sphere;
mod torus;
use self::cylinder::CylinderField;
use self::r#box::BoxField;
use self::sphere::SphereField;
use self::torus::TorusField;
use super::alias::AliasInfo;
use super::spatial::Spatial;
use super::{Aspect, Node};
use crate::core::client::Client;
use crate::create_interface;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::Result;
use glam::{vec2, vec3a, Mat4, Vec3, Vec3A};
use mint::Vector3;
use once_cell::sync::Lazy;
use std::ops::Deref;
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 {
server_methods: vec!["distance", "normal", "closest_point", "ray_march"],
..Default::default()
});
stardust_xr_server_codegen::codegen_field_protocol!();
pub trait FieldTrait: Send + Sync + 'static {
fn spatial_ref(&self) -> &Spatial;
fn local_distance(&self, p: Vec3A) -> f32;
fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
let d = self.local_distance(p);
let e = vec2(r, 0_f32);
let n = vec3a(d, d, d)
- vec3a(
self.local_distance(vec3a(e.x, e.y, e.y)),
self.local_distance(vec3a(e.y, e.x, e.y)),
self.local_distance(vec3a(e.y, e.y, e.x)),
);
n.normalize()
}
fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
p - (self.local_normal(p, r) * self.local_distance(p))
}
fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
self.local_distance(local_p)
}
fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
reference_to_local_space
.inverse()
.transform_vector3a(self.local_normal(local_p, r))
}
fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
let reference_to_local_space =
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
let local_p = reference_to_local_space.transform_point3a(p);
reference_to_local_space
.inverse()
.transform_point3a(self.local_closest_point(local_p, r))
}
fn ray_march(&self, ray: Ray) -> RayMarchResult {
let mut result = RayMarchResult {
ray_origin: ray.origin.into(),
ray_direction: ray.direction.into(),
min_distance: f32::MAX,
deepest_point_distance: 0_f32,
ray_length: 0_f32,
ray_steps: 0,
};
let ray_to_field_matrix =
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 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 {
let distance = self.local_distance(ray_point);
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
result.ray_length += march_distance;
ray_point += ray_direction * march_distance;
if result.min_distance > distance {
result.deepest_point_distance = result.ray_length;
result.min_distance = distance;
}
result.ray_steps += 1;
}
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 origin: Vec3,
pub direction: Vec3,
pub space: Arc<Spatial>,
}
// const MIN_RAY_STEPS: u32 = 0;
const MAX_RAY_STEPS: u32 = 1000;
const MIN_RAY_MARCH: f32 = 0.001_f32;
const MAX_RAY_MARCH: f32 = f32::MAX;
// const MIN_RAY_LENGTH: f32 = 0_f32;
const MAX_RAY_LENGTH: f32 = 1000_f32;
pub enum Field {
Box(BoxField),
Cylinder(CylinderField),
Sphere(SphereField),
Torus(TorusField),
}
impl Aspect for Field {
const NAME: &'static str = "Field";
}
impl Deref for Field {
type Target = dyn FieldTrait;
fn deref(&self) -> &Self::Target {
match self {
Field::Box(field) => field,
Field::Cylinder(field) => field,
Field::Sphere(field) => field,
Field::Torus(field) => 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),
// }
// }
// }
create_interface!(FieldInterface, FieldInterfaceAspect, "/field");
pub struct FieldInterface;
impl FieldInterfaceAspect for FieldInterface {
fn create_box_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
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>> {
client.get_node("Field", path)?.get_aspect::<Field>()
}

View File

@@ -1,55 +0,0 @@
use super::{Field, FieldTrait, Node, SphereFieldAspect};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::Vec3A;
use portable_atomic::AtomicF32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct SphereField {
space: Arc<Spatial>,
radius: AtomicF32,
}
impl SphereField {
pub fn add_to(node: &Arc<Node>, radius: f32) {
let sphere_field = SphereField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
radius: AtomicF32::new(radius),
};
<SphereField as FieldAspect>::add_node_members(node);
<SphereField as SphereFieldAspect>::add_node_members(node);
node.add_aspect(Field::Sphere(sphere_field));
}
pub fn set_radius(&self, radius: f32) {
self.radius.store(radius, Ordering::Relaxed);
}
}
impl FieldTrait for SphereField {
fn local_distance(&self, p: Vec3A) -> f32 {
p.length() - self.radius.load(Ordering::Relaxed)
}
fn local_normal(&self, p: Vec3A, _r: f32) -> Vec3A {
-p.normalize()
}
fn local_closest_point(&self, p: Vec3A, _r: f32) -> Vec3A {
p.normalize() * self.radius.load(Ordering::Relaxed)
}
fn spatial_ref(&self) -> &Spatial {
self.space.as_ref()
}
}
impl SphereFieldAspect for SphereField {
fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, radius: f32) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Sphere(this_field) = &*this_field else {
return Ok(());
};
this_field.set_radius(radius);
Ok(())
}
}

View File

@@ -1,60 +0,0 @@
use super::{Field, FieldTrait, Node, TorusFieldAspect};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct TorusField {
space: Arc<Spatial>,
radius_a: AtomicF32,
radius_b: AtomicF32,
}
impl TorusField {
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) {
let torus_field = TorusField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
radius_a: AtomicF32::new(radius_a.abs()),
radius_b: AtomicF32::new(radius_b.abs()),
};
<TorusField as FieldAspect>::add_node_members(node);
<TorusField as TorusFieldAspect>::add_node_members(node);
node.add_aspect(Field::Torus(torus_field));
}
pub fn set_size(&self, radius_a: f32, radius_b: f32) {
self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
}
}
impl FieldTrait for TorusField {
fn local_distance(&self, p: Vec3A) -> f32 {
let radius_a = self.radius_a.load(Ordering::Relaxed);
let radius_b = self.radius_b.load(Ordering::Relaxed);
let q = vec2(p.xz().length() - radius_a, p.y);
q.length() - radius_b
}
fn spatial_ref(&self) -> &Spatial {
self.space.as_ref()
}
}
impl TorusFieldAspect for TorusField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
radius_a: 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(())
}
}

View File

@@ -1,43 +0,0 @@
use super::{alias::Alias, spatial::Spatial, Node};
use crate::{
core::client::{Client, INTERNAL_CLIENT},
nodes::alias::AliasInfo,
};
use color_eyre::eyre::Result;
use glam::{vec3, Mat4};
use std::sync::Arc;
use stereokit::StereoKitMultiThread;
lazy_static::lazy_static! {
static ref HMD: Arc<Node> = create();
}
fn create() -> Arc<Node> {
let node = Arc::new(Node::create_parent_name(&INTERNAL_CLIENT, "", "hmd", false));
Spatial::add_to(&node, None, Mat4::IDENTITY, false);
node
}
pub fn frame(sk: &impl StereoKitMultiThread) {
let spatial = HMD.get_aspect::<Spatial>().unwrap();
let hmd_pose = sk.input_head();
*spatial.transform.lock() = Mat4::from_scale_rotation_translation(
vec3(1.0, 1.0, 1.0),
hmd_pose.orientation.into(),
hmd_pose.position.into(),
);
}
pub fn make_alias(client: &Arc<Client>) -> Result<Arc<Node>> {
Alias::create(
client,
"",
"hmd",
&HMD,
AliasInfo {
server_signals: vec!["get_bounds", "get_transform"],
..Default::default()
},
)
}

View File

@@ -1,51 +1,89 @@
use crate::nodes::fields::Field;
use super::{Finger, Hand, InputDataTrait, InputHandler, InputMethod, Joint, Thumb};
use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::spatial::Spatial;
use glam::{vec3a, Mat4};
use stardust_xr::schemas::flat::{Hand as FlatHand, InputDataType, Joint};
use glam::{vec3a, Mat4, Quat};
use std::sync::Arc;
use super::{DistanceLink, InputSpecialization};
#[derive(Debug, Default)]
pub struct Hand {
pub base: FlatHand,
}
impl InputSpecialization for Hand {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
self.true_distance(space, field).abs()
impl Default for Joint {
fn default() -> Self {
Joint {
position: [0.0; 3].into(),
rotation: Quat::IDENTITY.into(),
radius: 0.0,
distance: 0.0,
}
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
}
#[allow(clippy::derivable_impls)]
impl Default for Finger {
fn default() -> Self {
Finger {
tip: Default::default(),
distal: Default::default(),
intermediate: Default::default(),
proximal: Default::default(),
metacarpal: Default::default(),
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for Thumb {
fn default() -> Self {
Thumb {
tip: Default::default(),
distal: Default::default(),
proximal: Default::default(),
metacarpal: Default::default(),
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for Hand {
fn default() -> Self {
Hand {
right: Default::default(),
thumb: Default::default(),
index: Default::default(),
middle: Default::default(),
ring: Default::default(),
little: Default::default(),
palm: Default::default(),
wrist: Default::default(),
elbow: Default::default(),
}
}
}
impl InputDataTrait for Hand {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let mut min_distance = f32::MAX;
for tip in [
&self.base.thumb.tip.position,
&self.base.index.tip.position,
&self.base.middle.tip.position,
&self.base.ring.tip.position,
&self.base.little.tip.position,
&self.thumb.tip.position,
&self.index.tip.position,
&self.middle.tip.position,
&self.ring.tip.position,
&self.little.tip.position,
] {
min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z)));
}
min_distance
}
fn serialize(
&self,
distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType {
let mut hand = self.base;
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
let local_to_handler_matrix =
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
let mut joints: Vec<&mut Joint> = Vec::new();
joints.extend([&mut hand.palm, &mut hand.wrist]);
if let Some(elbow) = &mut hand.elbow {
joints.extend([&mut self.palm, &mut self.wrist]);
if let Some(elbow) = &mut self.elbow {
joints.push(elbow);
}
for finger in [
&mut hand.index,
&mut hand.middle,
&mut hand.ring,
&mut hand.little,
&mut self.index,
&mut self.middle,
&mut self.ring,
&mut self.little,
] {
joints.extend([
&mut finger.tip,
@@ -56,10 +94,10 @@ impl InputSpecialization for Hand {
]);
}
joints.extend([
&mut hand.thumb.tip,
&mut hand.thumb.distal,
&mut hand.thumb.proximal,
&mut hand.thumb.metacarpal,
&mut self.thumb.tip,
&mut self.thumb.distal,
&mut self.thumb.proximal,
&mut self.thumb.metacarpal,
]);
for joint in joints {
@@ -68,12 +106,7 @@ impl InputSpecialization for Hand {
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into();
joint.rotation = rotation.into();
joint.distance = distance_link
.handler
.field
.distance(&distance_link.handler.spatial, position.into());
joint.distance = handler.field.distance(&handler.spatial, position.into());
}
InputDataType::Hand(Box::new(hand))
}
}

View File

@@ -0,0 +1,42 @@
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY};
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Aspect, Node};
use color_eyre::eyre::Result;
use std::sync::Arc;
pub struct InputHandler {
pub spatial: Arc<Spatial>,
pub field: Arc<Field>,
pub(super) method_aliases: AliasList,
}
impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
let handler = InputHandler {
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
field: field.clone(),
method_aliases: AliasList::default(),
};
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
}
let handler = INPUT_HANDLER_REGISTRY.add(handler);
node.add_aspect_raw(handler);
Ok(())
}
}
impl Aspect for InputHandler {
const NAME: &'static str = "InputHandler";
}
impl InputHandlerAspect for InputHandler {}
impl PartialEq for InputHandler {
fn eq(&self, other: &Self) -> bool {
self.spatial == other.spatial
}
}
impl Drop for InputHandler {
fn drop(&mut self) {
INPUT_HANDLER_REGISTRY.remove(self);
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.handle_drop_handler(self);
}
}
}

233
src/nodes/input/method.rs Normal file
View File

@@ -0,0 +1,233 @@
use super::{
input_method_client, InputData, InputDataTrait, InputDataType, InputHandler, InputMethodAspect,
InputMethodRefAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO,
INPUT_METHOD_REGISTRY,
};
use crate::{
core::{client::Client, registry::Registry},
nodes::{
alias::{Alias, AliasList},
fields::{Field, FIELD_ALIAS_INFO},
spatial::Spatial,
Aspect, Node,
},
};
use color_eyre::eyre::Result;
use parking_lot::Mutex;
use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak};
pub struct InputMethod {
pub spatial: Arc<Spatial>,
pub data: Mutex<InputDataType>,
pub datamap: Mutex<Datamap>,
handler_aliases: AliasList,
handler_field_aliases: AliasList,
pub(super) handler_order: Mutex<Vec<Weak<InputHandler>>>,
pub internal_capture_requests: Registry<InputHandler>,
pub captures: Registry<InputHandler>,
}
impl InputMethod {
pub fn add_to(
node: &Arc<Node>,
data: InputDataType,
datamap: Datamap,
) -> Result<Arc<InputMethod>> {
let method = InputMethod {
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
data: Mutex::new(data),
datamap: Mutex::new(datamap),
handler_aliases: AliasList::default(),
handler_field_aliases: AliasList::default(),
handler_order: Mutex::new(Vec::new()),
internal_capture_requests: Registry::new(),
captures: Registry::new(),
};
<InputMethod as InputMethodRefAspect>::add_node_members(node);
<InputMethod as InputMethodAspect>::add_node_members(node);
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
}
let method = INPUT_METHOD_REGISTRY.add(method);
node.add_aspect_raw(method.clone());
Ok(method)
}
pub fn distance(&self, to: &Field) -> f32 {
self.data.lock().distance(&self.spatial, to)
}
pub fn set_handler_order<'a>(&self, handlers: impl Iterator<Item = &'a Arc<InputHandler>>) {
*self.handler_order.lock() = handlers.map(Arc::downgrade).collect();
}
pub(super) fn make_alias(&self, handler: &InputHandler) {
let Some(method_node) = self.spatial.node() else {
return;
};
let Some(handler_node) = handler.spatial.node() else {
return;
};
let Some(client) = handler_node.get_client() else {
return;
};
let Ok(method_alias) = Alias::create(
&method_node,
&client,
INPUT_METHOD_REF_ASPECT_ALIAS_INFO.clone(),
Some(&handler.method_aliases),
) else {
return;
};
method_alias.set_enabled(false);
}
pub(super) fn handle_new_handler(&self, handler: &InputHandler) {
self.make_alias(handler);
let Some(method_node) = self.spatial.node() else {
return;
};
let Some(method_client) = method_node.get_client() else {
return;
};
let Some(handler_node) = handler.spatial.node() else {
return;
};
// Receiver itself
let Ok(handler_alias) = Alias::create(
&handler_node,
&method_client,
INPUT_METHOD_REF_ASPECT_ALIAS_INFO.clone(),
Some(&self.handler_aliases),
) else {
return;
};
let Some(handler_field_node) = handler.field.spatial.node() else {
return;
};
// Handler's field
let Ok(rx_field_alias) = Alias::create(
&handler_field_node,
&method_client,
FIELD_ALIAS_INFO.clone(),
Some(&self.handler_field_aliases),
) else {
return;
};
let _ = input_method_client::create_handler(&method_node, &handler_alias, &rx_field_alias);
}
pub(super) fn handle_drop_handler(&self, handler: &InputHandler) {
let Some(tx_node) = self.spatial.node() else {
return;
};
let Some(handler_alias) = self.handler_aliases.get_from_aspect(handler) else {
return;
};
let _ = input_method_client::destroy_handler(&tx_node, handler_alias.id);
self.handler_aliases.remove_aspect(handler);
self.handler_field_aliases
.remove_aspect(handler.field.as_ref());
}
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
let mut input = self.data.lock().clone();
input.transform(self, handler);
InputData {
id: alias_id,
input,
distance: self.distance(&handler.field),
datamap: self.datamap.lock().clone(),
order: self
.handler_order
.lock()
.iter()
.enumerate()
.find(|(_, h)| h.ptr_eq(&Arc::downgrade(handler)))
.unwrap()
.0 as u32,
captured: self.captures.get_valid_contents().contains(handler),
}
}
}
impl Aspect for InputMethod {
const NAME: &'static str = "InputMethod";
}
impl InputMethodRefAspect for InputMethod {
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
fn request_capture(
node: Arc<Node>,
_calling_client: Arc<Client>,
handler: Arc<Node>,
) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
let input_handler = handler.get_aspect::<InputHandler>()?;
input_method
.internal_capture_requests
.add_raw(&input_handler);
Ok(())
}
}
impl InputMethodAspect for InputMethod {
#[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."]
fn set_input(
node: Arc<Node>,
_calling_client: Arc<Client>,
input: InputDataType,
) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
*input_method.data.lock() = input;
Ok(())
}
#[doc = "Set the datmap of this input method"]
fn set_datamap(node: Arc<Node>, _calling_client: Arc<Client>, datamap: Datamap) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
*input_method.datamap.lock() = datamap;
Ok(())
}
#[doc = "Manually set the order of handlers to propagate input to, or else let the server decide."]
fn set_handler_order(
node: Arc<Node>,
_calling_client: Arc<Client>,
handlers: Vec<Arc<Node>>,
) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
let handlers = handlers
.into_iter()
.filter_map(|p| p.get_aspect::<InputHandler>().ok())
.map(|i| Arc::downgrade(&i))
.collect::<Vec<_>>();
*input_method.handler_order.lock() = handlers;
Ok(())
}
#[doc = "Set which handlers are captured."]
fn set_captures(
node: Arc<Node>,
_calling_client: Arc<Client>,
handlers: Vec<Arc<Node>>,
) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
input_method.captures.clear();
for handler in handlers {
let Ok(handler) = handler.get_aspect::<InputHandler>() else {
continue;
};
input_method.captures.add_raw(&handler);
}
Ok(())
}
}
impl Drop for InputMethod {
fn drop(&mut self) {
INPUT_METHOD_REGISTRY.remove(self);
}
}

View File

@@ -1,456 +1,152 @@
pub mod hand;
pub mod pointer;
pub mod tip;
#![allow(clippy::needless_question_mark)]
use self::hand::Hand;
use self::pointer::Pointer;
use self::tip::Tip;
mod hand;
mod handler;
mod method;
mod pointer;
mod tip;
use super::{
alias::{Alias, AliasInfo},
fields::{find_field, Field, FIELD_ALIAS_INFO},
spatial::{parse_transform, Spatial},
Aspect, Message, Node,
};
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
pub use handler::*;
pub use method::*;
use super::fields::Field;
use super::spatial::Spatial;
use crate::create_interface;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use crate::{core::client::Client, nodes::Node};
use crate::{core::registry::Registry, nodes::spatial::Transform};
use color_eyre::eyre::Result;
use glam::Mat4;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::AtomicBool;
use serde::Deserialize;
use stardust_xr::schemas::{flat::InputDataType, flex::serialize};
use stardust_xr::{
schemas::{flat::InputData, flex::deserialize},
values::Datamap,
};
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak};
use tracing::{debug_span, instrument};
use stardust_xr::values::Datamap;
use std::sync::Arc;
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
pub trait InputSpecialization: Send + Sync {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn serialize(
&self,
distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType;
stardust_xr_server_codegen::codegen_input_protocol!();
pub trait InputDataTrait {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
}
pub enum InputType {
Pointer(Pointer),
Hand(Box<Hand>),
Tip(Tip),
}
impl Deref for InputType {
type Target = dyn InputSpecialization;
fn deref(&self) -> &Self::Target {
impl InputDataTrait for InputDataType {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
match self {
InputType::Pointer(p) => p,
InputType::Hand(h) => h.as_ref(),
InputType::Tip(t) => t,
InputDataType::Pointer(i) => i.transform(method, handler),
InputDataType::Hand(i) => i.transform(method, handler),
InputDataType::Tip(i) => i.transform(method, handler),
}
}
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
match self {
InputDataType::Pointer(i) => i.distance(space, field),
InputDataType::Hand(i) => i.distance(space, field),
InputDataType::Tip(i) => i.distance(space, field),
}
}
}
pub struct InputMethod {
node: Weak<Node>,
uid: String,
pub enabled: Mutex<bool>,
pub spatial: Arc<Spatial>,
pub specialization: Mutex<InputType>,
captures: Registry<InputHandler>,
pub datamap: Mutex<Option<Datamap>>,
handler_aliases: LifeLinkedNodeMap<String>,
handler_order: OnceCell<Mutex<Vec<Weak<InputHandler>>>>,
}
impl InputMethod {
#[allow(dead_code)]
pub fn add_to(
node: &Arc<Node>,
specialization: InputType,
datamap: Option<Datamap>,
) -> Result<Arc<InputMethod>> {
node.add_local_signal("capture", InputMethod::capture_flex);
node.add_local_signal("set_datamap", InputMethod::set_datamap_flex);
node.add_local_signal("set_handlers", InputMethod::set_handlers_flex);
let method = InputMethod {
node: Arc::downgrade(node),
uid: node.uid.clone(),
enabled: Mutex::new(true),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
specialization: Mutex::new(specialization),
captures: Registry::new(),
datamap: Mutex::new(datamap),
handler_aliases: LifeLinkedNodeMap::default(),
handler_order: OnceCell::new(),
};
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
method.make_alias(&handler);
}
let method = INPUT_METHOD_REGISTRY.add(method);
node.add_aspect_raw(method.clone());
Ok(method)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect::<Self>()
}
fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(&node)?;
let handler = InputHandler::find(&calling_client, deserialize(message.as_ref())?)?;
method.captures.add_raw(&handler);
node.send_remote_signal("capture", message)
}
fn set_datamap_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let method = InputMethod::get(&node)?;
method
.datamap
.lock()
.replace(Datamap::from_raw(message.data)?);
Ok(())
}
fn set_handlers_flex(
node: Arc<Node>,
create_interface!(InputInterface);
pub struct InputInterface;
impl InterfaceAspect for InputInterface {
#[doc = "Create an input method node"]
fn create_input_method(
_node: Arc<Node>,
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
.into_iter()
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
.map(|h| Arc::downgrade(&h))
.collect();
*method
.handler_order
.get_or_init(|| Mutex::new(Vec::new()))
.lock() = handlers;
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 {
self.specialization
.lock()
.compare_distance(&self.spatial, to)
}
fn true_distance(&self, to: &Field) -> f32 {
self.specialization.lock().true_distance(&self.spatial, to)
}
fn handle_new_handler(&self, handler: &InputHandler) {
let Some(method_node) = self.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
let Ok(handler_alias) = Alias::create(
&method_client,
method_node.get_path(),
handler.uid.as_str(),
&handler_node,
AliasInfo {
server_methods: vec!["get_transform"],
..Default::default()
},
) else {
return;
};
self.handler_aliases
.add(handler.uid.clone(), &handler_alias);
if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() {
// Handler's field
let Ok(rx_field_alias) = Alias::create(
&method_client,
handler_alias.get_path(),
"field",
&handler_field_node,
FIELD_ALIAS_INFO.clone(),
) else {
return;
};
self.handler_aliases
.add(handler.uid.clone() + "-field", &rx_field_alias);
}
let Ok(data) = serialize(&handler.uid) else {
return;
};
let _ = method_node.send_remote_signal("handler_created", data);
}
fn handle_drop_handler(&self, handler: &InputHandler) {
let uid = handler.uid.as_str();
self.handler_aliases.remove(uid);
self.handler_aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {
return;
};
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 {
fn drop(&mut self) {
INPUT_METHOD_REGISTRY.remove(self);
}
}
pub struct DistanceLink {
distance: f32,
method: Arc<InputMethod>,
handler: Arc<InputHandler>,
}
impl DistanceLink {
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Self {
DistanceLink {
distance: method.compare_distance(&handler.field),
method,
handler,
}
}
fn send_input(&self, order: u32, captured: bool, datamap: Datamap) {
self.handler.send_input(order, captured, self, datamap);
}
#[instrument(level = "debug", skip(self))]
fn serialize(&self, order: u32, captured: bool, datamap: Datamap) -> Vec<u8> {
let input = self.method.specialization.lock().serialize(
self,
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
);
let root = InputData {
uid: self.method.uid.clone(),
input,
distance: self.method.true_distance(&self.handler.field),
datamap,
order,
captured,
};
root.serialize()
}
}
pub struct InputHandler {
enabled: Arc<AtomicBool>,
uid: String,
node: Weak<Node>,
spatial: Arc<Spatial>,
field: Arc<Field>,
method_aliases: LifeLinkedNodeMap<usize>,
}
impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
let handler = InputHandler {
enabled: node.enabled.clone(),
uid: node.uid.clone(),
node: Arc::downgrade(node),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
field: field.clone(),
method_aliases: LifeLinkedNodeMap::default(),
};
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.make_alias(&handler);
method.handle_new_handler(&handler);
}
let handler = INPUT_HANDLER_REGISTRY.add(handler);
node.add_aspect_raw(handler);
Ok(())
}
fn find(client: &Client, path: &str) -> Result<Arc<Self>> {
client.get_node("Input Handler", path)?.get_aspect::<Self>()
}
#[instrument(level = "debug", skip(self, distance_link))]
fn send_input(
&self,
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 {
fn eq(&self, other: &Self) -> bool {
self.spatial == other.spatial
}
}
impl Drop for InputHandler {
fn drop(&mut self) {
INPUT_HANDLER_REGISTRY.remove(self);
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.handle_drop_handler(self);
}
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
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_method_pointer", pointer::create_pointer_flex);
node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_input_handler_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateInputHandlerInfo<'a> {
name: &'a str,
parent_path: &'a str,
id: u64,
parent: Arc<Node>,
transform: Transform,
field_path: &'a str,
}
let info: CreateInputHandlerInfo = deserialize(message.as_ref())?;
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, true);
let field = find_field(&calling_client, info.field_path)?;
initial_data: InputDataType,
datamap: Datamap,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = Node::create_parent_name(&calling_client, "/input/handler", info.name, true)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputHandler::add_to(&node, &field)?;
Ok(())
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputMethod::add_to(&node, initial_data, datamap)?;
Ok(())
}
#[doc = "Create an input handler node"]
fn create_input_handler(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let field = field.get_aspect::<Field>()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputHandler::add_to(&node, &field)?;
Ok(())
}
}
#[tracing::instrument(level = "debug")]
pub fn process_input() {
// Iterate over all valid input methods
let methods = debug_span!("Get valid methods").in_scope(|| {
INPUT_METHOD_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|method| *method.enabled.lock())
.filter(|method| method.datamap.lock().is_some())
});
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
const LIMIT: usize = 50;
for method in methods {
for alias in method.node.upgrade().unwrap().aliases.get_valid_contents() {
alias.enabled.store(false, Ordering::Release);
let methods = INPUT_METHOD_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|method| {
let Some(node) = method.spatial.node() else {
return false;
};
node.enabled()
});
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
for method_alias in handler.method_aliases.get_aliases() {
method_alias.set_enabled(false);
}
debug_span!("Process input method").in_scope(|| {
// Get all valid input handlers and convert them to DistanceLink objects
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links")
.in_scope(|| {
if let Some(handler_order) = method.handler_order.get() {
let handler_order = handler_order.lock();
handler_order
.iter()
.filter_map(|h| h.upgrade())
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.map(|handler| DistanceLink::from(method.clone(), handler))
.collect()
} else {
let mut distance_links: Vec<_> = handlers
.iter()
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.map(|handler| {
debug_span!("Create distance link").in_scope(|| {
DistanceLink::from(method.clone(), handler.clone())
})
})
.collect();
// Sort the distance links by their distance in ascending order
debug_span!("Sort distance links").in_scope(|| {
distance_links.sort_unstable_by(|a, b| {
a.distance.abs().partial_cmp(&b.distance.abs()).unwrap()
});
});
distance_links.truncate(LIMIT);
distance_links
}
});
let captures = method.captures.take_valid_contents();
// Iterate over the distance links and send input to them
for (i, distance_link) in distance_links.into_iter().enumerate() {
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,
// break out of the loop to avoid sending input to the remaining distance links
if captured {
break;
}
let Some(handler_node) = handler.spatial.node() else {
continue;
};
if !handler_node.enabled() {
continue;
}
if let Some(handler_field_node) = handler.field.spatial.node() {
if !handler_field_node.enabled() {
continue;
}
});
};
let (methods, datas) = methods
.clone()
// filter out methods without the handler in their handler order
.filter(|a| {
a.handler_order
.lock()
.iter()
.any(|h| h.ptr_eq(&Arc::downgrade(&handler)))
})
// filter out methods without the proper alias
.filter_map(|m| {
Some((
handler
.method_aliases
.get_from_original_node(m.spatial.node.clone())?,
m,
))
})
// make sure the input method alias is enabled
.inspect(|(a, _)| {
a.set_enabled(true);
})
// serialize the data
.map(|(a, m)| (a.clone(), m.serialize(a.get_id(), &handler)))
.unzip::<_, _, Vec<_>, Vec<_>>();
let _ = input_handler_client::input(&handler_node, &methods, &datas);
}
for method in methods {
method.internal_capture_requests.clear();
}
}

View File

@@ -1,96 +1,52 @@
use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client;
use crate::nodes::fields::{Field, Ray, RayMarchResult};
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::{Message, Node};
use glam::{vec3, Mat4};
use serde::Deserialize;
use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Datamap;
use super::{InputDataTrait, InputHandler, InputMethod, Pointer};
use crate::nodes::{
fields::{Field, FieldTrait, Ray, RayMarchResult},
spatial::Spatial,
};
use glam::{vec3, Mat4, Quat};
use std::sync::{Arc, Weak};
use std::sync::Arc;
#[derive(Default)]
pub struct Pointer;
// impl Default for Pointer {
// fn default() -> Self {
// Pointer {
// grab: Default::default(),
// select: Default::default(),
// }
// }
// }
impl Default for Pointer {
fn default() -> Self {
Pointer {
origin: [0.0; 3].into(),
orientation: Quat::IDENTITY.into(),
deepest_point: [0.0; 3].into(),
}
}
}
impl Pointer {
fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
fn ray_march(&self, method_space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: space.clone(),
space: Spatial::new(
Weak::new(),
Some(method_space.clone()),
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into()),
),
})
}
}
impl InputSpecialization for Pointer {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
if ray_info.min_distance > 0.0 {
ray_info.deepest_point_distance + 1000.0
} else {
ray_info
.deepest_point_distance
.hypot(0.001 / ray_info.min_distance)
}
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
impl InputDataTrait for Pointer {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
ray_info.min_distance
}
fn serialize(
&self,
distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
let local_to_handler_matrix =
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into())
* Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
let direction = local_to_handler_matrix.transform_vector3(vec3(0.0, 0.0, -1.0));
let ray_march = self.ray_march(&distance_link.method.spatial, &distance_link.handler.field);
let ray_march = self.ray_march(&method.spatial, &handler.field);
let direction = local_to_handler_matrix
.transform_vector3(vec3(0.0, 0.0, -1.0))
.normalize();
let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
InputDataType::Pointer(FlatPointer {
origin: origin.into(),
orientation: orientation.into(),
deepest_point: deepest_point.into(),
})
self.origin = origin.into();
self.orientation = orientation.into();
self.deepest_point = deepest_point.into();
}
}
pub fn create_pointer_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> color_eyre::eyre::Result<()> {
#[derive(Deserialize)]
struct CreatePointerInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
datamap: Option<Vec<u8>>,
}
let info: CreatePointerInfo = deserialize(message.as_ref())?;
let node = Node::create_parent_name(&calling_client, "/input/method/pointer", info.name, true);
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputMethod::add_to(
&node,
InputType::Pointer(Pointer),
info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
)?;
Ok(())
}

View File

@@ -1,82 +1,29 @@
use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client;
use crate::nodes::fields::Field;
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::{Message, Node};
use color_eyre::eyre::Result;
use glam::{vec3a, Mat4};
use serde::Deserialize;
use stardust_xr::schemas::flat::{InputDataType, Tip as FlatTip};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Datamap;
use super::{InputDataTrait, InputHandler, InputMethod, Tip};
use crate::nodes::{
fields::{Field, FieldTrait},
spatial::Spatial,
};
use glam::{Mat4, Quat};
use std::sync::Arc;
#[derive(Default)]
pub struct Tip {
pub radius: f32,
}
impl Tip {
fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
if let InputType::Tip(tip) = &mut *input_method.specialization.lock() {
tip.radius = deserialize(message.as_ref())?;
impl Default for Tip {
fn default() -> Self {
Tip {
origin: [0.0; 3].into(),
orientation: Quat::IDENTITY.into(),
}
Ok(())
}
}
impl InputSpecialization for Tip {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
impl InputDataTrait for Tip {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, self.origin.into())
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0))
}
fn serialize(
&self,
_distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
let local_to_handler_matrix =
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial))
* Mat4::from_rotation_translation(self.orientation.into(), self.origin.into());
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
InputDataType::Tip(FlatTip {
origin: origin.into(),
orientation: orientation.into(),
radius: self.radius,
})
self.origin = origin.into();
self.orientation = orientation.into();
}
}
pub fn create_tip_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateTipInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
radius: f32,
datamap: Option<Vec<u8>>,
}
let info: CreateTipInfo = deserialize(message.as_ref())?;
let node = Node::create_parent_name(&calling_client, "/input/method/tip", info.name, true);
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputMethod::add_to(
&node,
InputType::Tip(Tip {
radius: info.radius,
}),
info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
)?;
node.add_local_signal("set_radius", Tip::set_radius);
Ok(())
}

View File

@@ -1,40 +1,56 @@
use super::{Item, ItemType};
use super::{
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType,
};
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
scenegraph::MethodResponseSender,
},
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
create_interface,
nodes::{
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
drawable::{
model::{MaterialWrapper, ModelPart},
shaders::UNLIT_SHADER_BYTES,
},
items::TypeInfo,
spatial::{parse_transform, Spatial, Transform},
spatial::{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 mint::{ColumnMatrix4, Vector2};
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,
use stereokit_rust::{
material::{Material, Transparency},
shader::Shader,
sk::MainThreadToken,
system::Renderer,
tex::{Tex, TexFormat, TexType},
util::Color128,
};
use tracing::error;
pub struct TexWrapper(pub Tex);
unsafe impl Send for TexWrapper {}
unsafe impl Sync for TexWrapper {}
stardust_xr_server_codegen::codegen_item_camera_protocol!();
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![],
alias_info: CAMERA_ITEM_ASPECT_ALIAS_INFO.clone(),
ui_node_id: INTERFACE_NODE_ID,
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
new_acceptor_fn: |node, acceptor, acceptor_field| {
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
}
};
}
@@ -46,16 +62,16 @@ struct FrameInfo {
pub struct CameraItem {
space: Arc<Spatial>,
frame_info: Mutex<FrameInfo>,
sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<Material>>,
sk_tex: OnceCell<TexWrapper>,
sk_mat: OnceCell<Arc<MaterialWrapper>>,
applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>,
}
#[allow(unused)]
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(),
@@ -69,11 +85,7 @@ impl CameraItem {
apply_to: Registry::new(),
}),
);
node.add_local_method("frame", CameraItem::frame_flex);
node.add_local_signal(
"apply_preview_material",
CameraItem::apply_preview_material_flex,
);
// <CameraItem as CameraItemAspect>::node_methods(node);
}
fn frame_flex(
@@ -107,87 +119,113 @@ impl CameraItem {
Ok(())
}
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize(id)?.into())
pub fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
let _ = camera_item_ui_client::create_item(node, item);
}
pub fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
let _ = camera_item_acceptor_client::capture_item(node, item);
}
pub fn update(&self, sk: &impl StereoKitDraw) {
pub fn update(&self, token: &MainThreadToken) {
let frame_info = self.frame_info.lock();
let sk_tex = self.sk_tex.get_or_init(|| {
sk.tex_gen_color(
TexWrapper(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)
TexType::Rendertarget,
TexFormat::RGBA32Linear,
))
});
let sk_mat = self
.sk_mat
.get_or_try_init(|| -> Result<Arc<MaterialWrapper>> {
let shader = Shader::from_memory(UNLIT_SHADER_BYTES)?;
let mut mat = Material::new(&shader, None);
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
mat.transparency(Transparency::Blend);
Ok(Arc::new(MaterialWrapper(mat)))
});
let Ok(sk_mat) = sk_mat else {
error!("unable to make camera item stereokit texture");
return;
};
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,
Renderer::render_to(
token,
&sk_tex.0,
self.space.global_transform(),
RenderLayer::all(),
stereokit::RenderClear::All,
Rect {
x: 0.0,
y: 0.0,
w: 0.0,
h: 0.0,
},
);
frame_info.proj_matrix,
None,
None,
None,
)
}
}
}
impl CameraItemAspect for CameraItem {}
pub fn update(sk: &impl StereoKitDraw) {
impl CameraItemAcceptorAspect for ItemAcceptor {
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
super::acceptor_capture_item_flex(node, item)
}
}
pub fn update(token: &MainThreadToken) {
for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() {
let ItemType::Camera(camera) = &camera.specialization else {
continue;
};
camera.update(sk);
camera.update(token);
}
}
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,
create_interface!(ItemInterface);
impl InterfaceAspect for ItemInterface {
#[doc = "Create a camera item at a specific location"]
fn create_camera_item(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
proj_matrix: RowMatrix4<f32>,
proj_matrix: ColumnMatrix4<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);
) -> Result<()> {
let space = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(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(())
let node = Node::from_id(&calling_client, id, false).add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false);
CameraItem::add_to(&node, proj_matrix.into(), px_size);
Ok(())
}
#[doc = "Register this client to manage camera items and create default 3D UI for them."]
fn register_camera_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA)
}
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
fn create_camera_item_acceptor(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
create_item_acceptor_flex(
calling_client,
id,
parent,
transform,
&ITEM_TYPE_INFO_CAMERA,
field,
)
}
}

View File

@@ -1,97 +0,0 @@
use super::{Item, ItemType};
use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
scenegraph::MethodResponseSender,
},
nodes::{
items::TypeInfo,
spatial::{parse_transform, Spatial, Transform},
Message, Node,
},
};
use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
lazy_static! {
pub(super) static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
type_name: "environment",
aliased_local_signals: vec!["apply_sky_tex", "apply_sky_light"],
aliased_local_methods: vec![],
aliased_remote_signals: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
pub struct EnvironmentItem {
path: String,
}
impl EnvironmentItem {
pub fn add_to(node: &Arc<Node>, path: String) {
Item::add_to(
node,
nanoid!(),
&ITEM_TYPE_INFO_ENVIRONMENT,
ItemType::Environment(EnvironmentItem { path }),
);
node.add_local_method("get_path", EnvironmentItem::get_path_flex);
}
fn get_path_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
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())
});
}
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize((id, self.path.as_str()))?.into())
}
}
pub(super) fn create_environment_item_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateEnvironmentItemInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
item_data: String,
}
let info: CreateEnvironmentItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.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);
EnvironmentItem::add_to(&node, info.item_data);
node.get_aspect::<Item>().unwrap().make_alias_named(
&calling_client,
&parent_name,
info.name,
)?;
Ok(())
}

View File

@@ -1,45 +1,25 @@
pub mod camera;
mod environment;
pub mod panel;
use self::camera::CameraItem;
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL};
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Message, Node};
use self::panel::PanelItemTrait;
use super::alias::AliasList;
use super::fields::{Field, FIELD_ALIAS_INFO};
use super::spatial::Spatial;
use super::{Alias, Aspect, Node};
use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::{ensure, eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{ensure, Result};
use parking_lot::Mutex;
use portable_atomic::Ordering;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::hash::Hash;
use std::sync::{Arc, Weak};
lazy_static! {
static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![
"get_bounds",
"get_transform",
"set_transform",
"set_spatial_parent",
"set_spatial_parent_in_place",
"set_zoneable",
"release",
];
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec![];
static ref ITEM_ALIAS_REMOTE_SIGNALS: Vec<&'static str> = vec![];
}
stardust_xr_server_codegen::codegen_item_protocol!();
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if item.captured_acceptor.lock().strong_count() > 0 {
release(item);
}
@@ -55,19 +35,19 @@ fn release(item: &Item) {
*captured_acceptor = Weak::default();
acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() {
ui.handle_release_item(item, &acceptor);
ui.handle_release_item(item, acceptor);
}
}
}
pub struct TypeInfo {
pub type_name: &'static str,
pub aliased_local_signals: Vec<&'static str>,
pub aliased_local_methods: Vec<&'static str>,
pub aliased_remote_signals: Vec<&'static str>,
pub alias_info: AliasInfo,
pub ui_node_id: u64,
pub ui: Mutex<Weak<ItemUI>>,
pub items: Registry<Item>,
pub acceptors: Registry<ItemAcceptor>,
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
}
impl Hash for TypeInfo {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
@@ -82,8 +62,7 @@ impl PartialEq for TypeInfo {
impl Eq for TypeInfo {}
pub struct Item {
node: Weak<Node>,
uid: String,
spatial: Arc<Spatial>,
type_info: &'static TypeInfo,
captured_acceptor: Mutex<Weak<ItemAcceptor>>,
pub specialization: ItemType,
@@ -91,20 +70,18 @@ pub struct Item {
impl Item {
pub fn add_to(
node: &Arc<Node>,
uid: String,
type_info: &'static TypeInfo,
specialization: ItemType,
) -> Arc<Self> {
let item = Item {
node: Arc::downgrade(node),
uid,
spatial: node.aspects.get::<Spatial>().unwrap(),
type_info,
captured_acceptor: Default::default(),
specialization,
};
let item = type_info.items.add(item);
node.add_local_signal("release", Item::release_flex);
<Item as ItemAspect>::add_node_members(node);
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item);
}
@@ -122,54 +99,25 @@ impl Item {
item
}
fn make_alias_named(
&self,
client: &Arc<Client>,
parent: &str,
name: &str,
) -> Result<Arc<Node>> {
fn make_alias(&self, client: &Arc<Client>, alias_list: &AliasList) -> Result<Arc<Node>> {
Alias::create(
&self.spatial.node().unwrap(),
client,
parent,
name,
&self.node.upgrade().unwrap(),
AliasInfo {
server_signals: [
&self.type_info.aliased_local_signals,
ITEM_ALIAS_LOCAL_SIGNALS.as_slice(),
]
.concat(),
server_methods: [
&self.type_info.aliased_local_methods,
ITEM_ALIAS_LOCAL_METHODS.as_slice(),
]
.concat(),
client_signals: [
&self.type_info.aliased_remote_signals,
ITEM_ALIAS_REMOTE_SIGNALS.as_slice(),
]
.concat(),
},
self.type_info.alias_info.clone() + ITEM_ASPECT_ALIAS_INFO.clone(),
Some(alias_list),
)
}
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> Result<Arc<Node>> {
self.make_alias_named(client, parent, &self.uid)
}
fn release_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let item = node.get_aspect::<Item>()?;
release(&item);
Ok(())
}
}
impl Aspect for Item {
const NAME: &'static str = "Item";
}
impl ItemAspect for Item {
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let item = node.get_aspect::<Item>()?;
release(&item);
Ok(())
}
}
impl Drop for Item {
fn drop(&mut self) {
self.type_info.items.remove(self);
@@ -182,15 +130,19 @@ impl Drop for Item {
pub enum ItemType {
Camera(CameraItem),
Environment(EnvironmentItem),
Panel(Arc<dyn PanelItemTrait>),
}
impl ItemType {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
match self {
ItemType::Camera(c) => c.serialize_start_data(id),
ItemType::Environment(e) => e.serialize_start_data(id),
ItemType::Panel(p) => p.serialize_start_data(id),
ItemType::Camera(c) => c.send_ui_item_created(node, item),
ItemType::Panel(p) => p.send_ui_item_created(node, item),
}
}
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
match self {
ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
}
}
}
@@ -208,9 +160,9 @@ impl ItemType {
pub struct ItemUI {
node: Weak<Node>,
type_info: &'static TypeInfo,
item_aliases: LifeLinkedNodeMap<String>,
acceptor_aliases: LifeLinkedNodeMap<String>,
acceptor_field_aliases: LifeLinkedNodeMap<String>,
item_aliases: AliasList,
acceptor_aliases: AliasList,
acceptor_field_aliases: AliasList,
}
impl ItemUI {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
@@ -222,9 +174,9 @@ impl ItemUI {
let ui = Arc::new(ItemUI {
node: Arc::downgrade(node),
type_info,
item_aliases: Default::default(),
acceptor_aliases: Default::default(),
acceptor_field_aliases: Default::default(),
item_aliases: AliasList::default(),
acceptor_aliases: AliasList::default(),
acceptor_field_aliases: AliasList::default(),
});
*type_info.ui.lock() = Arc::downgrade(&ui);
node.add_aspect_raw(ui.clone());
@@ -237,16 +189,6 @@ impl ItemUI {
}
Ok(())
}
fn send_state(&self, state: &str, name: &str) {
let Ok(serialized_data) = serialize(name) else {
return;
};
let _ = self
.node
.upgrade()
.unwrap()
.send_remote_signal(state, serialized_data);
}
fn handle_create_item(&self, item: &Item) {
let Some(node) = self.node.upgrade() else {
@@ -256,38 +198,47 @@ impl ItemUI {
return;
};
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
self.item_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
let Ok(item_alias) = item.make_alias(&client, &self.item_aliases) else {
return;
};
let _ = node.send_remote_signal("create_item", serialized_data);
}
fn handle_destroy_item(&self, item: &Item) {
self.item_aliases.remove(&item.uid);
self.send_state("destroy_item", item.uid.as_str());
item.specialization.send_ui_item_created(&node, &item_alias);
}
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
return;
};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
return;
};
let _ = node.send_remote_signal("capture_item", message);
let _ = item_ui_client::capture_item(
&self.node.upgrade().unwrap(),
item_alias.id,
acceptor_alias.id,
);
}
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
return;
};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
return;
};
let _ = node.send_remote_signal("release_item", message);
let _ = item_ui_client::release_item(
&self.node.upgrade().unwrap(),
item_alias.id,
acceptor_alias.id,
);
}
fn handle_destroy_item(&self, item: &Item) {
let Some(item_alias) = self
.item_aliases
.get_from_original_node(item.spatial.node.clone())
else {
return;
};
let _ = item_ui_client::destroy_item(&self.node.upgrade().unwrap(), item_alias.id);
self.item_aliases.remove_aspect(item);
}
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
@@ -297,24 +248,40 @@ impl ItemUI {
return;
};
let Ok((alias, field_alias)) = acceptor.make_aliases(
let Some(acceptor_node) = acceptor.spatial.node() else {
return;
};
let Ok(acceptor_alias) = Alias::create(
&acceptor_node,
&client,
&format!("/item/{}/acceptor", self.type_info.type_name),
ITEM_ACCEPTOR_ASPECT_ALIAS_INFO.clone(),
Some(&self.acceptor_aliases),
) else {
return;
};
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
self.acceptor_field_aliases
.add(acceptor.uid.clone(), &field_alias);
let Ok(message) = serialize(&acceptor.uid) else {
let Some(acceptor_field_node) = acceptor.field.spatial.node() else {
return;
};
let _ = node.send_remote_signal("create_acceptor", message);
let Ok(acceptor_field_alias) = Alias::create(
&acceptor_field_node,
&client,
FIELD_ALIAS_INFO.clone(),
Some(&self.acceptor_aliases),
) else {
return;
};
(acceptor.type_info.new_acceptor_fn)(&node, &acceptor_alias, &acceptor_field_alias);
}
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
self.send_state("destroy_acceptor", acceptor.uid.as_str());
self.acceptor_aliases.remove(&acceptor.uid);
self.acceptor_field_aliases.remove(&acceptor.uid);
let acceptor_alias = self.acceptor_aliases.get_from_aspect(acceptor).unwrap();
let _ = item_ui_client::destroy_acceptor(&self.node.upgrade().unwrap(), acceptor_alias.id);
self.acceptor_aliases
.remove_aspect(acceptor.spatial.as_ref());
self.acceptor_field_aliases
.remove_aspect(acceptor.field.as_ref());
}
}
impl Aspect for ItemUI {
@@ -327,69 +294,29 @@ impl Drop for ItemUI {
}
pub struct ItemAcceptor {
uid: String,
node: Weak<Node>,
spatial: Arc<Spatial>,
pub type_info: &'static TypeInfo,
field: Arc<Field>,
accepted_aliases: LifeLinkedNodeMap<String>,
accepted_aliases: AliasList,
accepted_registry: Registry<Item>,
}
impl ItemAcceptor {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
let acceptor = type_info.acceptors.add(ItemAcceptor {
uid: nanoid!(),
node: Arc::downgrade(node),
spatial: node.get_aspect::<Spatial>().unwrap(),
type_info,
field,
accepted_aliases: Default::default(),
accepted_aliases: AliasList::default(),
accepted_registry: Registry::new(),
});
node.add_local_signal("capture", ItemAcceptor::capture_flex);
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor);
}
node.add_aspect_raw(acceptor);
node.add_aspect_raw(acceptor.clone());
}
fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
if !node.enabled.load(Ordering::Relaxed) {
return Ok(());
}
let acceptor = node.get_aspect::<ItemAcceptor>().unwrap();
let item_path: &str = deserialize(message.as_ref())?;
let item_node = calling_client.get_node("Item", item_path)?;
let item = item_node.get_aspect::<Item>()?;
capture(&item, &acceptor);
Ok(())
}
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Result<(Arc<Node>, Arc<Node>)> {
let acceptor_node = &self.node.upgrade().unwrap();
let acceptor_alias = Alias::create(
client,
parent,
&self.uid,
acceptor_node,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
)?;
let acceptor_field_alias = Alias::create(
client,
acceptor_alias.get_path(),
"field",
&self.field.spatial_ref().node.upgrade().unwrap(),
AliasInfo::default(),
)?;
Ok((acceptor_alias, acceptor_field_alias))
}
fn handle_capture(&self, item: &Arc<Item>) {
let Some(node) = self.node.upgrade() else {
let Some(node) = self.spatial.node() else {
return;
};
let Some(client) = node.get_client() else {
@@ -397,31 +324,28 @@ impl ItemAcceptor {
};
self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
self.accepted_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
let Ok(alias_node) = item.make_alias(&client, &self.accepted_aliases) else {
return;
};
let _ = node.send_remote_signal("capture", serialized_data);
item.specialization
.send_acceptor_item_created(&node, &alias_node);
}
fn handle_release(&self, item: &Item) {
let Some(node) = self.node.upgrade() else {
return;
};
self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid);
let Ok(message) = serialize(&item.uid) else {
self.accepted_aliases.remove_aspect(item);
let Some(node) = self.spatial.node() else {
return;
};
let _ = node.send_remote_signal("release", message);
let alias = self.accepted_aliases.get_from_aspect(item).unwrap();
let _ = item_acceptor_client::release_item(&node, alias.id);
}
}
impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor";
}
impl ItemAcceptorAspect for ItemAcceptor {}
impl Drop for ItemAcceptor {
fn drop(&mut self) {
self.type_info.acceptors.remove(self);
@@ -434,73 +358,39 @@ impl Drop for ItemAcceptor {
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
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(
"create_environment_item",
environment::create_environment_item_flex,
);
node.add_local_signal("register_item_ui", register_item_ui_flex);
node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
node.add_to_scenegraph().map(|_| ())
}
fn type_info(name: &str) -> Result<&'static TypeInfo> {
match name {
"environment" => Ok(&ITEM_TYPE_INFO_ENVIRONMENT),
#[cfg(feature = "wayland")]
"panel" => Ok(&ITEM_TYPE_INFO_PANEL),
_ => Err(eyre!("Invalid item type")),
}
}
pub fn register_item_ui_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
type_info: &'static TypeInfo,
) -> Result<()> {
#[derive(Deserialize)]
struct RegisterItemUIInfo<'a> {
item_type: &'a str,
}
let info: RegisterItemUIInfo = deserialize(message.as_ref())?;
let type_info = type_info(info.item_type)?;
let ui = Node::create_parent_name(&calling_client, "/item", type_info.type_name, true)
.add_to_scenegraph()?;
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?;
Ok(())
}
fn create_item_acceptor_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
id: u64,
parent: Arc<Node>,
transform: Transform,
type_info: &'static TypeInfo,
field: Arc<Node>,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateItemAcceptorInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
item_type: &'a str,
}
let info: CreateItemAcceptorInfo = deserialize(message.as_ref())?;
let space = calling_client
.get_node("Reference space", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let type_info = type_info(info.item_type)?;
let space = parent.get_aspect::<Spatial>()?;
let field = field.get_aspect::<Field>()?;
let transform = transform.to_mat4(true, true, false);
let node = Node::create_parent_name(
&calling_client,
&format!("/item/{}/acceptor", type_info.type_name),
info.name,
true,
)
.add_to_scenegraph()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(space.clone()), transform, false);
ItemAcceptor::add_to(&node, type_info, field);
Ok(())
}
fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
let acceptor = node.get_aspect::<ItemAcceptor>()?;
let item = item.get_aspect::<Item>()?;
capture(&item, &acceptor);
Ok(())
}
struct ItemInterface;
// create_interface!(ItemInterface);

View File

@@ -1,205 +1,76 @@
use super::{create_item_acceptor_flex, register_item_ui_flex, ItemAcceptor, ItemInterface};
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
use crate::{
core::{
client::{get_env, state, Client, INTERNAL_CLIENT},
registry::Registry,
},
create_interface,
nodes::{
drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo},
spatial::Spatial,
Message, Node,
spatial::{Spatial, Transform},
Node,
},
};
use color_eyre::eyre::{eyre, Result};
use color_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;
use tracing::{debug, info};
stardust_xr_server_codegen::codegen_item_panel_protocol!();
impl Default for Geometry {
fn default() -> Self {
Geometry {
origin: [0, 0].into(),
size: [0, 0].into(),
}
}
}
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",
],
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
ui_node_id: INTERFACE_NODE_ID,
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
new_acceptor_fn: |node, acceptor, acceptor_field| {
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
}
};
}
/// 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 apply_cursor_material(&self, model_part: &Arc<ModelPart>);
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_motion(&self, surface: &SurfaceId, position: Vector2<f32>);
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool);
fn pointer_scroll(
&self,
surface: &SurfaceID,
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 keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>);
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>);
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);
fn reset_input(&self);
}
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
@@ -209,35 +80,31 @@ pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
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 trait PanelItemTrait: Send + Sync + 'static {
fn backend(&self) -> &dyn Backend;
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>);
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>);
}
pub struct PanelItem<B: Backend + ?Sized> {
pub uid: String,
node: Weak<Node>,
pub struct PanelItem<B: Backend> {
pub 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>> {
impl<B: Backend> PanelItem<B> {
pub fn create(backend: Box<B>, pid: Option<i32>) -> (Arc<Node>, 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 node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
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,
});
@@ -245,353 +112,352 @@ impl<B: Backend + ?Sized> PanelItem<B> {
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item),
);
<Self as PanelItemAspect>::add_node_members(&node);
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();
(node, panel_item)
}
}
// Remote signals
impl<B: Backend + ?Sized> PanelItem<B> {
pub fn toplevel_parent_changed(&self, parent: &str) {
#[allow(unused)]
impl<B: Backend> PanelItem<B> {
pub fn toplevel_parent_changed(&self, parent: u64) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_parent_changed", serialize(parent).unwrap());
panel_item_client::toplevel_parent_changed(&node, parent);
}
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());
panel_item_client::toplevel_title_changed(&node, title);
}
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());
panel_item_client::toplevel_app_id_changed(&node, app_id);
}
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());
panel_item_client::toplevel_fullscreen_active(&node, active);
}
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());
panel_item_client::toplevel_move_request(&node);
}
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(),
);
panel_item_client::toplevel_resize_request(&node, up, down, left, right);
}
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());
panel_item_client::toplevel_size_changed(&node, size);
}
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());
if let Some(geometry) = geometry {
panel_item_client::set_cursor(&node, &geometry);
} else {
panel_item_client::hide_cursor(&node);
}
}
pub fn new_child(&self, uid: &str, info: ChildInfo) {
pub fn create_child(&self, id: u64, info: &ChildInfo) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("new_child", serialize((uid, info)).unwrap());
panel_item_client::create_child(&node, id, info);
}
pub fn reposition_child(&self, uid: &str, geometry: Geometry) {
pub fn reposition_child(&self, id: u64, geometry: &Geometry) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("reposition_child", serialize((uid, geometry)).unwrap());
panel_item_client::reposition_child(&node, id, geometry);
}
pub fn drop_child(&self, uid: &str) {
pub fn destroy_child(&self, id: u64) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("drop_child", serialize(uid).unwrap());
panel_item_client::destroy_child(&node, id);
}
}
// 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(
// make these stupid vectors u32 in the protocol somehow!!!!!!!1
#[allow(unused)]
impl<B: Backend> PanelItemAspect for PanelItem<B> {
#[doc = "Apply the cursor as a material to a model."]
fn apply_cursor_material(
node: Arc<Node>,
_calling_client: Arc<Client>,
model_part: Arc<Node>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let model_part = model_part.get_aspect::<ModelPart>()?;
panel_item.backend().apply_cursor_material(&model_part);
Ok(())
}
#[doc = "Apply a surface's visuals as a material to a model."]
fn apply_surface_material(
node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
surface: SurfaceId,
model_part: Arc<Node>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let model_part = model_part.get_aspect::<ModelPart>()?;
#[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);
panel_item
.backend()
.apply_surface_material(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);
#[doc = "Try to close the toplevel.\n \n The panel item UI handler or panel item acceptor will drop the panel item if this succeeds."]
fn close_toplevel(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().close_toplevel();
Ok(())
}
fn pointer_motion_flex(
#[doc = "Request a resize of the surface to whatever size the 2D app wants."]
fn auto_size_toplevel(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().auto_size_toplevel();
Ok(())
}
#[doc = "Request a resize of the surface (in pixels)."]
fn set_toplevel_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
size: mint::Vector2<u32>,
) -> 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);
panel_item.backend().set_toplevel_size(size);
Ok(())
}
fn pointer_button_flex(
#[doc = "Tell the toplevel to appear focused visually if true, or unfocused if false."]
fn set_toplevel_focused_visuals(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
focused: bool,
) -> 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);
panel_item.backend().set_toplevel_focused_visuals(focused);
Ok(())
}
fn pointer_scroll_flex(
#[doc = "Send an event to set the pointer's position (in pixels, relative to top-left of surface). This will activate the pointer."]
fn pointer_motion(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
surface: SurfaceId,
position: mint::Vector2<f32>,
) -> 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);
panel_item.backend().pointer_motion(&surface, position);
Ok(())
}
fn keyboard_keys_flex(
#[doc = "Send an event to set a pointer button's state if the pointer's active. The `button` is from the `input_event_codes` crate (e.g. BTN_LEFT for left click)."]
fn pointer_button(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
surface: SurfaceId,
button: u32,
pressed: bool,
) -> 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);
panel_item
.backend()
.pointer_button(&surface, button, pressed);
Ok(())
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else {
#[doc = "Send an event to scroll the pointer if it's active.\nScroll distance is a value in pixels corresponding to the `distance` the surface should be scrolled.\nScroll steps is a value in columns/rows corresponding to the wheel clicks of a mouse or such. This also supports fractions of a wheel click."]
fn pointer_scroll(
node: Arc<Node>,
_calling_client: Arc<Client>,
surface: SurfaceId,
scroll_distance: mint::Vector2<f32>,
scroll_steps: mint::Vector2<f32>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item
.backend()
.pointer_scroll(&surface, Some(scroll_distance), Some(scroll_steps));
Ok(())
}
#[doc = "Send an event to stop scrolling the pointer."]
fn pointer_stop_scroll(
node: Arc<Node>,
_calling_client: Arc<Client>,
surface: SurfaceId,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().pointer_scroll(&surface, None, None);
Ok(())
}
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
fn keyboard_keys(
node: Arc<Node>,
_calling_client: Arc<Client>,
surface: SurfaceId,
keymap_id: u64,
keys: Vec<i32>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item
.backend()
.keyboard_keys(&surface, keymap_id, keys);
Ok(())
}
#[doc = "Put a touch down on this surface with the unique ID `uid` at `position` (in pixels) from top left corner of the surface."]
fn touch_down(
node: Arc<Node>,
_calling_client: Arc<Client>,
surface: SurfaceId,
uid: u32,
position: mint::Vector2<f32>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().touch_down(&surface, uid, position);
Ok(())
}
#[doc = "Move an existing touch point."]
fn touch_move(
node: Arc<Node>,
_calling_client: Arc<Client>,
uid: u32,
position: mint::Vector2<f32>,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().touch_move(uid, position);
Ok(())
}
#[doc = "Release a touch from its surface."]
fn touch_up(node: Arc<Node>, _calling_client: Arc<Client>, uid: u32) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().touch_up(uid);
Ok(())
}
#[doc = "Reset all input, such as pressed keys and pointer clicks and touches. Useful for when it's newly captured into an item acceptor to make sure no input gets stuck."]
fn reset_input(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.backend().reset_input();
Ok(())
}
}
impl PanelItemAcceptorAspect for ItemAcceptor {
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
super::acceptor_capture_item_flex(node, item)
}
}
impl<B: Backend> PanelItemTrait for PanelItem<B> {
fn backend(&self) -> &dyn Backend {
self.backend.as_ref()
}
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
let Ok(init_data) = self.backend.start_data() else {
return;
};
let Ok(message) = serialize(sid) else { return };
let _ = node.send_remote_signal("grab_keyboard", message);
let _ = panel_item_ui_client::create_item(node, item, init_data);
}
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(());
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
let Ok(init_data) = self.backend.start_data() else {
return;
};
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())
let _ = panel_item_acceptor_client::capture_item(node, item, init_data);
}
}
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> {
impl<B: Backend> Drop for PanelItem<B> {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
info!("Dropped panel item");
}
}
create_interface!(ItemInterface);
impl InterfaceAspect for ItemInterface {
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]
fn register_panel_item_ui(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
}
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
fn create_panel_item_acceptor(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
create_item_acceptor_flex(
calling_client,
id,
parent,
transform,
&ITEM_TYPE_INFO_PANEL,
field,
)
}
}

View File

@@ -3,18 +3,21 @@ pub mod audio;
pub mod data;
pub mod drawable;
pub mod fields;
pub mod hmd;
pub mod input;
pub mod items;
pub mod root;
pub mod spatial;
use self::alias::Alias;
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
use color_eyre::eyre::{eyre, Result};
use nanoid::nanoid;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use spatial::Spatial;
use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::{deserialize, serialize};
@@ -24,12 +27,6 @@ use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use std::vec::Vec;
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
use self::alias::Alias;
#[derive(Default)]
pub struct Message {
pub data: Vec<u8>,
@@ -54,15 +51,21 @@ pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
stardust_xr_server_codegen::codegen_node_protocol!();
pub struct OwnedNode(pub Arc<Node>);
impl Drop for OwnedNode {
fn drop(&mut self) {
self.0.destroy();
}
}
pub struct Node {
pub enabled: Arc<AtomicBool>,
pub(super) uid: String,
path: String,
enabled: AtomicBool,
id: u64,
client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize,
local_signals: Mutex<FxHashMap<String, Signal>>,
local_methods: Mutex<FxHashMap<String, Method>>,
local_signals: Mutex<FxHashMap<u64, Signal>>,
local_methods: Mutex<FxHashMap<u64, Method>>,
aliases: Registry<Alias>,
aspects: Aspects,
destroyable: bool,
@@ -71,39 +74,26 @@ impl Node {
pub fn get_client(&self) -> Option<Arc<Client>> {
self.client.upgrade()
}
// pub fn get_name(&self) -> &str {
// &self.path[self.trailing_slash_pos + 1..]
// }
pub fn get_path(&self) -> &str {
self.path.as_str()
pub fn get_id(&self) -> u64 {
self.id
}
pub fn create_parent_name(
client: &Arc<Client>,
parent: &str,
name: &str,
destroyable: bool,
) -> Self {
let mut path = parent.to_string();
path.push('/');
path.push_str(name);
Self::create_path(client, path, destroyable)
pub fn generate(client: &Arc<Client>, destroyable: bool) -> Self {
Self::from_id(client, client.generate_id(), destroyable)
}
pub fn create_path(client: &Arc<Client>, path: impl ToString, destroyable: bool) -> Self {
pub fn from_id(client: &Arc<Client>, id: u64, destroyable: bool) -> Self {
let node = Node {
enabled: Arc::new(AtomicBool::new(true)),
uid: nanoid!(),
enabled: AtomicBool::new(true),
client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(),
path: path.to_string(),
// trailing_slash_pos: parent.len(),
id,
local_signals: Default::default(),
local_methods: Default::default(),
aliases: Default::default(),
aspects: Default::default(),
destroyable,
};
<Node as NodeAspect>::add_node_members(&node);
<Node as OwnedAspect>::add_node_members(&node);
node
}
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
@@ -113,9 +103,32 @@ impl Node {
.scenegraph
.add_node(self))
}
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
Ok(OwnedNode(
self.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
.scenegraph
.add_node(self),
))
}
pub fn enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
&& if let Ok(spatial) = self.get_aspect::<Spatial>() {
spatial
.global_transform()
.to_scale_rotation_translation()
.0
.length_squared() > 0.0
} else {
true
}
}
pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Relaxed)
}
pub fn destroy(&self) {
if let Some(client) = self.get_client() {
client.scenegraph.remove_node(self.get_path());
client.scenegraph.remove_node(self.get_id());
}
}
@@ -133,11 +146,11 @@ impl Node {
// Ok(serialize(pid)?.into())
// }
pub fn add_local_signal(&self, name: &str, signal: Signal) {
self.local_signals.lock().insert(name.to_string(), signal);
pub fn add_local_signal(&self, id: u64, signal: Signal) {
self.local_signals.lock().insert(id, signal);
}
pub fn add_local_method(&self, name: &str, method: Method) {
self.local_methods.lock().insert(name.to_string(), method);
pub fn add_local_method(&self, id: u64, method: Method) {
self.local_methods.lock().insert(id, method);
}
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
@@ -153,11 +166,11 @@ impl Node {
pub fn send_local_signal(
self: Arc<Self>,
calling_client: Arc<Client>,
method: &str,
method: u64,
message: Message,
) -> Result<(), ScenegraphError> {
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);
}
alias
@@ -169,7 +182,7 @@ impl Node {
let signal = self
.local_signals
.lock()
.get(method)
.get(&method)
.cloned()
.ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
@@ -180,12 +193,12 @@ impl Node {
pub fn execute_local_method(
self: Arc<Self>,
calling_client: Arc<Client>,
method: &str,
method: u64,
message: Message,
response: MethodResponseSender,
) {
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) {
response.send(Err(ScenegraphError::MethodNotFound));
return;
}
@@ -203,14 +216,14 @@ impl Node {
response,
)
} else {
let Some(method) = self.local_methods.lock().get(method).cloned() else {
let Some(method) = self.local_methods.lock().get(&method).cloned() else {
response.send(Err(ScenegraphError::MethodNotFound));
return;
};
method(self, calling_client, message, response);
}
}
pub fn send_remote_signal(&self, method: &str, message: impl Into<Message>) -> Result<()> {
pub fn send_remote_signal(&self, method: u64, message: impl Into<Message>) -> Result<()> {
let message = message.into();
self.aliases
.get_valid_contents()
@@ -227,16 +240,14 @@ impl Node {
},
);
});
let path = self.path.clone();
let method = method.to_string();
if let Some(handle) = self.message_sender_handle.as_ref() {
handle.signal(path.as_str(), method.as_str(), &message.data, message.fds)?;
handle.signal(self.id, method, &message.data, message.fds)?;
}
Ok(())
}
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
&self,
method: &str,
method: u64,
input: S,
fds: Vec<OwnedFd>,
) -> Result<(D, Vec<OwnedFd>)> {
@@ -247,7 +258,7 @@ impl Node {
let serialized = serialize(input)?;
let result = message_sender_handle
.method(self.path.as_str(), method, &serialized, fds)?
.method(self.id, method, &serialized, fds)?
.await
.map_err(|e| eyre!(e))?;
@@ -259,14 +270,16 @@ impl Node {
impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node")
.field("uid", &self.uid)
.field("path", &self.path)
.field("id", &self.id)
.field("local_signals", &self.local_signals.lock().keys())
.field("local_methods", &self.local_methods.lock().keys())
.field("destroyable", &self.destroyable)
.finish()
}
}
impl NodeAspect for Node {
impl OwnedAspect for Node {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.enabled.store(enabled, Ordering::Relaxed);
node.set_enabled(enabled);
Ok(())
}
@@ -289,6 +302,7 @@ pub trait Aspect: Any + Send + Sync + 'static {
#[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);
@@ -298,7 +312,7 @@ impl Aspects {
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>> {
fn get<A: Aspect>(&self) -> Result<Arc<A>> {
self.0
.lock()
.get(&Self::type_key::<A>())

View File

@@ -1,143 +1,104 @@
use super::spatial::Spatial;
use super::{Message, Node};
use super::Node;
use crate::core::client::Client;
use crate::core::client_state::{ClientState, ClientStateInternal};
use crate::core::client_state::ClientStateParsed;
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 crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use crate::session::connection_env;
use color_eyre::eyre::{bail, Result};
use glam::Mat4;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::path::PathBuf;
use std::sync::Arc;
use tracing::instrument;
use std::time::Instant;
use tracing::info;
static ROOT_REGISTRY: Registry<Root> = Registry::new();
stardust_xr_server_codegen::codegen_root_protocol!();
pub struct Root {
pub node: Arc<Node>,
send_frame_event: AtomicBool,
node: Arc<Node>,
connect_instant: Instant,
}
impl Root {
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
let node = Node::create_parent_name(client, "", "", false);
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_method("state_token", Root::state_token_flex);
node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
let node = Node::from_id(client, 0, false);
<Self as RootAspect>::add_node_members(&node);
let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(&node, None, client.state.root, false);
let _ = Spatial::add_to(&node, None, transform, false);
Ok(ROOT_REGISTRY.add(Root {
node,
send_frame_event: AtomicBool::from(false),
connect_instant: Instant::now(),
}))
}
fn subscribe_frame_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
calling_client
.root
.get()
.unwrap()
.send_frame_event
.store(true, Ordering::Relaxed);
Ok(())
}
#[instrument(level = "debug")]
pub fn send_frame_events(delta: f64) {
if let Ok(data) = serialize((delta, 0.0)) {
for root in ROOT_REGISTRY.get_valid_contents() {
if root.send_frame_event.load(Ordering::Relaxed) {
let _ = root.node.send_remote_signal("frame", data.clone());
}
}
for root in ROOT_REGISTRY.get_valid_contents() {
let _ = root_client::frame(
&root.node,
&FrameInfo {
delta: delta as f32,
elapsed: root.connect_instant.elapsed().as_secs_f32(),
},
);
}
}
fn set_base_prefixes_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
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)
pub async fn save_state(&self) -> Result<ClientState> {
Ok(root_client::save_state(&self.node).await?.0)
}
}
impl RootAspect for Root {
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
let Some(state) = calling_client.state.get() else {
bail!("Couldn't get state");
};
Ok(state.clone())
}
#[doc = "Get a hashmap of all the environment variables to connect a given app to the stardust server"]
async fn get_connection_environment(
_node: Arc<Node>,
_calling_client: Arc<Client>,
) -> Result<stardust_xr::values::Map<String, String>> {
Ok(connection_env())
}
#[doc = "Generate a client state token and return it back.\n\n When launching a new client, set the environment variable `STARDUST_STARTUP_TOKEN` to the returned string.\n Make sure the environment variable shows in `/proc/{pid}/environ` as that's the only reliable way to pass the value to the server (suggestions welcome).\n"]
async fn generate_state_token(
_node: Arc<Node>,
calling_client: Arc<Client>,
state: ClientState,
) -> Result<String> {
Ok(ClientStateParsed::from_deserialized(&calling_client, state).token())
}
#[doc = "Set initial list of folders to look for namespaced resources in"]
fn set_base_prefixes(
_node: Arc<Node>,
calling_client: Arc<Client>,
prefixes: Vec<String>,
) -> Result<()> {
info!(?calling_client, ?prefixes, "Set base prefixes");
*calling_client.base_resource_prefixes.lock() =
prefixes.into_iter().map(PathBuf::from).collect();
Ok(())
}
#[doc = "Cleanly disconnect from the server"]
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
calling_client.disconnect(Ok(()));
Ok(())
}
}
impl Drop for Root {
fn drop(&mut 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,25 +1,27 @@
pub mod zone;
use self::zone::Zone;
use super::fields::Field;
use super::{Aspect, Node};
use super::alias::Alias;
use super::fields::{Field, FieldTrait};
use super::Aspect;
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::create_interface;
use color_eyre::eyre::{eyre, Result};
use glam::{vec3a, Mat4, Quat};
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use color_eyre::eyre::{eyre, OptionExt, Result};
use glam::{vec3a, Mat4, Quat, Vec3};
use mint::Vector3;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
use std::ptr;
use std::sync::{Arc, Weak};
use stereokit::{bounds_grow_to_fit_box, Bounds};
use stereokit_rust::maths::Bounds;
stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform {
pub fn to_mat4(self, position: bool, rotation: bool, scale: bool) -> Mat4 {
pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(self.translation)
.flatten()
@@ -37,23 +39,25 @@ impl Transform {
}
}
lazy_static::lazy_static! {
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
}
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial {
uid: String,
pub(super) node: Weak<Node>,
pub node: Weak<Node>,
parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>,
transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub(super) bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
}
impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new(Spatial {
uid: nanoid!(),
node,
parent: Mutex::new(parent),
old_parent: Mutex::new(None),
@@ -77,6 +81,7 @@ impl Spatial {
if let Some(parent) = parent {
parent.children.add_raw(&spatial);
}
<Spatial as SpatialRefAspect>::add_node_members(node);
<Spatial as SpatialAspect>::add_node_members(node);
node.add_aspect_raw(spatial.clone());
spatial
@@ -103,11 +108,7 @@ impl Spatial {
.map(|b| (b)(&node))
.unwrap_or_default();
for child in self.children.get_valid_contents() {
bounds = bounds_grow_to_fit_box(
bounds,
child.get_bounding_box(),
Some(child.local_transform()),
);
bounds.grown_box(child.get_bounding_box(), child.local_transform());
}
bounds
}
@@ -189,7 +190,7 @@ impl Spatial {
}
fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) {
if let Some(parent) = self.get_parent() {
parent.children.remove(&self);
parent.children.remove(self);
}
if let Some(new_parent) = &new_parent {
new_parent.children.add_raw(self);
@@ -223,7 +224,7 @@ impl Spatial {
}
self.set_local_transform(Spatial::space_to_space_matrix(
Some(&self),
Some(self),
parent.map(AsRef::as_ref),
));
self.set_parent(parent);
@@ -235,7 +236,7 @@ impl Spatial {
self.zone
.lock()
.upgrade()
.and_then(|zone| zone.field.upgrade())
.map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX)
}
@@ -243,7 +244,7 @@ impl Spatial {
impl Aspect for Spatial {
const NAME: &'static str = "Spatial";
}
impl SpatialAspect for Spatial {
impl SpatialRefAspect for Spatial {
async fn get_local_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
@@ -252,8 +253,8 @@ impl SpatialAspect for Spatial {
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: mint::Vector3::from(bounds.center),
size: mint::Vector3::from(bounds.dimensions),
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
@@ -266,21 +267,18 @@ impl SpatialAspect for 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(),
},
let mut bounds = Bounds {
center: center.into(),
dimensions: [0.0; 3].into(),
};
bounds.grown_box(
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
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),
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
@@ -304,7 +302,8 @@ impl SpatialAspect for Spatial {
scale: Some(scale.into()),
})
}
}
impl SpatialAspect for Spatial {
fn set_local_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
@@ -361,16 +360,22 @@ impl SpatialAspect for Spatial {
}
Ok(())
}
// legit gotta find a way to remove old ones, this just keeps the node alive
async fn export_spatial(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
let id = rand::random();
EXPORTED_SPATIALS.lock().insert(id, node);
Ok(id)
}
}
impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
self.node.as_ptr() == other.node.as_ptr()
}
}
impl Debug for Spatial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Spatial")
.field("uid", &self.uid)
.field("parent", &self.parent)
.field("old_parent", &self.old_parent)
.field("transform", &self.transform)
@@ -379,7 +384,7 @@ impl Debug for Spatial {
}
impl Drop for Spatial {
fn drop(&mut self) {
zone::release_drop(self);
zone::release(self);
ZONEABLE_REGISTRY.remove(self);
}
}
@@ -402,26 +407,25 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
}
pub struct SpatialInterface;
impl SpatialInterfaceAspect for SpatialInterface {
impl InterfaceAspect for SpatialInterface {
fn create_spatial(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
id: u64,
parent: Arc<Node>,
transform: Transform,
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()?;
let node = Node::from_id(&calling_client, id, 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,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
@@ -430,12 +434,31 @@ impl SpatialInterfaceAspect for SpatialInterface {
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 node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent.clone()), transform, false);
Zone::add_to(&node, space, &field);
Zone::add_to(&node, space, field);
Ok(())
}
async fn import_spatial_ref(
_node: Arc<Node>,
calling_client: Arc<Client>,
uid: u64,
) -> Result<Arc<Node>> {
EXPORTED_SPATIALS
.lock()
.get(&uid)
.map(|s| {
Alias::create(
s,
&calling_client,
SPATIAL_REF_ASPECT_ALIAS_INFO.clone(),
None,
)
.unwrap()
})
.ok_or_eyre("Couldn't find spatial with that ID")
}
}
create_interface!(SpatialInterface, SpatialInterfaceAspect, "/spatial");
create_interface!(SpatialInterface);

View File

@@ -1,166 +1,147 @@
use super::{Spatial, ZoneAspect, ZONEABLE_REGISTRY};
use super::{
Spatial, ZoneAspect, SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO,
ZONEABLE_REGISTRY,
};
use crate::{
core::{client::Client, registry::Registry},
nodes::{
alias::{Alias, AliasInfo},
fields::Field,
alias::{get_original, Alias, AliasList},
fields::{Field, FieldTrait},
Aspect, Node,
},
};
use color_eyre::eyre::Result;
use glam::vec3a;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
let old_distance = spatial.zone_distance();
let new_distance = zone
.field
.upgrade()
.map(|field| field.distance(spatial, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX);
let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
if new_distance.abs() < old_distance.abs() {
release(spatial);
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
zone.captured.add_raw(spatial);
let Some(node) = zone.spatial.node.upgrade() else {
let Some(zone_node) = zone.spatial.node.upgrade() else {
return;
};
let _ = super::zone_client::capture(&node, &spatial.uid);
let Some(spatial_node) = spatial.node.upgrade() else {
return;
};
let Ok(spatial_alias) = Alias::create(
&spatial_node,
&zone_node.get_client().unwrap(),
SPATIAL_ASPECT_ALIAS_INFO.clone(),
Some(&zone.captured),
) else {
return;
};
let _ = super::zone_client::capture(&zone_node, &spatial_alias);
}
}
pub fn release(spatial: &Arc<Spatial>) {
pub fn release(spatial: &Spatial) {
let Some(spatial_node) = spatial.node.upgrade() else {
return;
};
let spatial = spatial_node.get_aspect::<Spatial>().unwrap();
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref());
let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
spatial_zone.captured.remove_aspect(spatial.as_ref());
let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
spatial_zone.captured.remove(spatial);
let _ = super::zone_client::release(&node, &spatial.uid);
let _ = super::zone_client::release(&node, spatial_node.id);
}
*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 {
spatial: Arc<Spatial>,
pub field: Weak<Field>,
zoneables: Mutex<FxHashMap<String, Arc<Node>>>,
captured: Registry<Spatial>,
pub field: Arc<Field>,
intersecting_spatials: Registry<Spatial>,
intersecting: AliasList,
captured: AliasList,
}
impl Zone {
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: &Arc<Field>) -> Arc<Zone> {
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: Arc<Field>) -> Arc<Zone> {
let zone = Arc::new(Zone {
spatial,
field: Arc::downgrade(field),
zoneables: Mutex::new(FxHashMap::default()),
captured: Registry::new(),
field,
intersecting_spatials: Registry::default(),
intersecting: AliasList::default(),
captured: AliasList::default(),
});
<Zone as ZoneAspect>::add_node_members(node);
node.add_aspect_raw(zone.clone());
zone
}
pub fn update(&self) -> Result<()> {
let node = self.spatial.node().unwrap();
let current_zoneables = Registry::new();
for zoneable in ZONEABLE_REGISTRY.get_valid_contents() {
let distance = self.field.distance(&zoneable, [0.0; 3].into());
if distance > 0.0 {
continue;
}
if let Some(zone) = zoneable.zone.lock().upgrade() {
let zoneable_distance = zone.field.distance(&zoneable, [0.0; 3].into());
if zoneable_distance < distance {
continue;
}
}
current_zoneables.add_raw(&zoneable);
}
let (added, removed) =
Registry::get_changes(&self.intersecting_spatials, &current_zoneables);
for added in added {
let Some(added_node) = added.node() else {
continue;
};
let Ok(alias) = Alias::create(
&added_node,
&self.spatial.node().unwrap().get_client().unwrap(),
SPATIAL_REF_ASPECT_ALIAS_INFO.clone(),
Some(&self.intersecting),
) else {
continue;
};
let _ = super::zone_client::enter(&node, &alias);
}
for removed in removed {
let Some(removed_node) = removed.node() else {
continue;
};
release(&removed);
let _ = super::zone_client::leave(&node, removed_node.id);
self.intersecting.remove_aspect(removed.as_ref());
}
self.intersecting_spatials.set(&current_zoneables);
Ok(())
}
}
impl Aspect for Zone {
const NAME: &'static str = "Zone";
}
impl ZoneAspect for Zone {
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let zone = node.get_aspect::<Zone>()?;
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
.spatial
.node
.upgrade()
.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();
for (_uid, zoneable) in old_zoneables.iter() {
zoneable.destroy();
}
let captured = zone.captured.get_valid_contents();
let zoneables = ZONEABLE_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|zoneable| zoneable.node.upgrade().is_some())
.filter(|zoneable| {
if captured
.iter()
.any(|captured| Arc::ptr_eq(captured, zoneable))
{
return true;
}
let spatial_zone_distance = zoneable.zone_distance();
let self_zone_distance = field.distance(zoneable, vec3a(0.0, 0.0, 0.0));
self_zone_distance < 0.0 && spatial_zone_distance > self_zone_distance
})
.filter_map(|zoneable| {
let alias = Alias::create(
&zone_client,
zone_node.get_path(),
&zoneable.uid,
&zoneable.node.upgrade().unwrap(),
AliasInfo {
server_signals: vec![
"set_transform",
"set_spatial_parent",
"set_spatial_parent_in_place",
],
server_methods: vec!["get_bounds", "get_transform"],
..Default::default()
},
)
.ok()?;
Some((zoneable.uid.clone(), alias))
})
.collect::<FxHashMap<String, Arc<Node>>>();
for (uid, zoneable) in zoneables
.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)) {
super::zone_client::leave(&node, &left_uid)?;
}
*old_zoneables = zoneables;
let _ = zone.update();
Ok(())
}
fn capture(
node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
fn capture(node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> 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<()> {
fn release(_node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> Result<()> {
let spatial = spatial.get_aspect()?;
release(&spatial);
Ok(())
@@ -168,7 +149,13 @@ impl ZoneAspect for Zone {
}
impl Drop for Zone {
fn drop(&mut self) {
for captured in self.captured.get_valid_contents() {
for captured in self
.captured
.get_aliases()
.into_iter()
.filter_map(|n| get_original(n, false))
.filter_map(|n| n.get_aspect::<Spatial>().ok())
{
release(&captured);
}
}

View File

@@ -1,18 +1,23 @@
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
input::{pointer::Pointer, InputMethod, InputType},
fields::{FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node,
Node, OwnedNode,
},
};
use color_eyre::eyre::Result;
use glam::Mat4;
use nanoid::nanoid;
use serde::Serialize;
use stardust_xr::{schemas::flex::flexbuffers, values::Datamap};
use glam::{vec3, Mat4};
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use std::sync::Arc;
use stereokit::StereoKitMultiThread;
use stereokit_rust::system::Input;
#[derive(Default, Deserialize, Serialize)]
pub struct EyeDatamap {
eye: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct KeyboardEvent {
@@ -23,33 +28,79 @@ pub struct KeyboardEvent {
}
pub struct EyePointer {
node: OwnedNode,
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
}
impl EyePointer {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let pointer = InputMethod::add_to(
&node.0,
InputDataType::Pointer(Pointer::default()),
Datamap::from_typed(EyeDatamap::default())?,
)
.unwrap();
Ok(EyePointer { spatial, pointer })
Ok(EyePointer {
node,
spatial,
pointer,
})
}
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let ray = sk.input_eyes();
pub fn update(&self) {
let ray = Input::get_eyes();
self.spatial
.set_local_transform(Mat4::from_rotation_translation(
ray.orientation,
ray.position,
ray.orientation.into(),
ray.position.into(),
));
{
// Set pointer input datamap
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("eye", 2);
map.end_map();
*self.pointer.datamap.lock() = Datamap::from_raw(fbb.take_buffer()).ok();
*self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
}
// send input to all the input handlers that are the closest to the ray as possible
let rx = INPUT_HANDLER_REGISTRY
.get_valid_contents()
.into_iter()
// filter out all the disabled handlers
.filter(|handler| {
let Some(node) = handler.spatial.node() else {
return false;
};
node.enabled()
})
// ray march to all the enabled handlers' fields
.map(|handler| {
let result = handler.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(),
});
(vec![handler], result)
})
// make sure the field isn't at the pointer origin and that it's being hit
.filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
// .inspect(|(_, result)| {
// dbg!(result);
// })
// now collect all handlers that are same distance if they're the closest
.reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
{
// distance is basically the same
handlers_a.extend(handlers_b);
(handlers_a, result_a)
} else if result_a.deepest_point_distance < result_b.deepest_point_distance {
(handlers_a, result_a)
} else {
(handlers_b, result_b)
}
})
.map(|(rx, _)| rx)
.unwrap_or_default();
self.pointer.set_handler_order(rx.iter());
}
}

View File

@@ -2,3 +2,97 @@ pub mod eye_pointer;
pub mod mouse_pointer;
pub mod sk_controller;
pub mod sk_hand;
use crate::nodes::{
fields::{Field, FieldTrait, Ray},
input::{InputDataTrait, InputDataType, InputHandler, InputMethod, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
};
use glam::vec3;
use std::sync::Arc;
#[derive(Default)]
pub struct CaptureManager {
pub capture: Option<Arc<InputHandler>>,
}
impl CaptureManager {
pub fn update_capture(&mut self, pointer: &InputMethod) {
if let Some(capture) = &self.capture {
if !pointer
.internal_capture_requests
.get_valid_contents()
.contains(capture)
{
self.capture.take();
}
}
}
pub fn set_new_capture(
&mut self,
pointer: &InputMethod,
distance_calculator: DistanceCalculator,
) {
if self.capture.is_none() {
self.capture = find_closest_capture(pointer, distance_calculator);
}
}
pub fn apply_capture(&self, method: &InputMethod) {
method.captures.clear();
if let Some(capture) = &self.capture {
method.set_handler_order([capture].into_iter());
method.captures.add_raw(capture);
}
}
}
type DistanceCalculator = fn(&Arc<Spatial>, &InputDataType, &Field) -> Option<f32>;
pub fn find_closest_capture(
method: &InputMethod,
distance_calculator: DistanceCalculator,
) -> Option<Arc<InputHandler>> {
method
.internal_capture_requests
.get_valid_contents()
.into_iter()
.filter_map(|h| {
distance_calculator(&method.spatial, &method.data.lock(), &h.field)
.map(|dist| (h.clone(), dist))
})
.min_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap())
.map(|(handler, _)| handler)
}
pub fn get_sorted_handlers(
method: &InputMethod,
distance_calculator: DistanceCalculator,
) -> Vec<Arc<InputHandler>> {
INPUT_HANDLER_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|handler| handler.spatial.node().map_or(false, |node| node.enabled()))
.filter(|handler| {
handler
.field
.spatial
.node()
.map_or(false, |node| node.enabled())
})
.filter_map(|handler| {
distance_calculator(&method.spatial, &method.data.lock(), &handler.field)
.map(|distance| (vec![handler], distance))
})
.filter(|(_, distance)| *distance > 0.0)
.reduce(|(mut handlers_a, distance_a), (handlers_b, distance_b)| {
if (distance_a - distance_b).abs() < 0.001 {
handlers_a.extend(handlers_b);
(handlers_a, distance_a)
} else if distance_a < distance_b {
(handlers_a, distance_a)
} else {
(handlers_b, distance_b)
}
})
.map(|(handlers, _)| handlers)
.unwrap_or_default()
}

View File

@@ -4,75 +4,87 @@ use crate::{
data::{
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
},
fields::{Field, Ray},
input::{pointer::Pointer, InputMethod, InputType},
fields::{FieldTrait, Ray},
input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node,
Node, OwnedNode,
},
};
use color_eyre::eyre::Result;
use glam::{vec2, vec3, Mat4, Vec2, Vec3};
use nanoid::nanoid;
use glam::{vec3, Mat4, Vec3};
use mint::Vector2;
use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::values::Datamap;
use std::{convert::TryFrom, sync::Arc};
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
use xkbcommon::xkb::{Context, Keymap, FORMAT_TEXT_V1};
use std::sync::Arc;
use stereokit_rust::system::{Input, Key};
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat};
#[derive(Default, Deserialize, Serialize)]
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
#[derive(Debug, Deserialize, Serialize)]
struct MouseEvent {
select: f32,
middle: f32,
context: f32,
grab: f32,
scroll_continuous: Vec2,
scroll_discrete: Vec2,
scroll_continuous: Vector2<f32>,
scroll_discrete: Vector2<f32>,
raw_input_events: Vec<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct KeyboardEvent {
pub keyboard: (),
pub xkbv1: (),
pub keymap_id: String,
pub keys: Vec<i32>,
}
impl Default for KeyboardEvent {
impl Default for MouseEvent {
fn default() -> Self {
Self {
keyboard: (),
xkbv1: (),
keymap_id: "flatscreen".to_string(),
keys: Default::default(),
MouseEvent {
select: 0.0,
middle: 0.0,
context: 0.0,
grab: 0.0,
scroll_continuous: [0.0; 2].into(),
scroll_discrete: [0.0; 2].into(),
raw_input_events: vec![],
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct KeyboardEvent {
pub keyboard: (),
pub xkbv1: (),
pub keymap_id: u64,
pub keys: Vec<i32>,
}
#[allow(unused)]
pub struct MousePointer {
node: Arc<Node>,
node: OwnedNode,
keymap: DefaultKey,
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
capture_manager: CaptureManager,
mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent,
keyboard_sender: Arc<PulseSender>,
}
impl MousePointer {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let pointer = InputMethod::add_to(
&node.0,
InputDataType::Pointer(Pointer::default()),
Datamap::from_typed(MouseEvent::default())?,
)?;
KEYMAPS.lock().insert(
"flatscreen".to_string(),
Keymap::new_from_names(&Context::new(0), "evdev", "", "", "", None, 0)
let context = Context::new(0).unwrap();
let keymap = KEYMAPS.lock().insert(
Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
.unwrap()
.get_as_string(FORMAT_TEXT_V1),
.get_as_string(KeymapFormat::TextV1)
.unwrap(),
);
let keyboard_sender = PulseSender::add_to(
&node,
&node.0,
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
)
.unwrap();
@@ -81,66 +93,78 @@ impl MousePointer {
node,
spatial,
pointer,
capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(),
keyboard_datamap: Default::default(),
keyboard_datamap: KeyboardEvent {
keyboard: (),
xkbv1: (),
keymap_id: keymap.data().as_ffi(),
keys: vec![],
},
keymap,
keyboard_sender,
})
}
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let mouse = sk.input_mouse();
pub fn update(&mut self) {
let mouse = Input::get_mouse();
let ray = ray_from_mouse(mouse.pos).unwrap();
let ray = mouse.get_ray();
self.spatial.set_local_transform(
Mat4::look_to_rh(
Vec3::from(ray.pos),
Vec3::from(ray.dir),
Vec3::from(ray.position),
Vec3::from(ray.direction),
vec3(0.0, 1.0, 0.0),
)
.inverse(),
);
{
// Set pointer input datamap
self.mouse_datamap.select =
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.middle =
if sk.input_key(Key::MouseCenter).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.context =
if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.grab = if sk.input_key(Key::MouseBack).contains(ButtonState::ACTIVE)
|| sk
.input_key(Key::MouseForward)
.contains(ButtonState::ACTIVE)
{
1.0f32
} else {
0.0f32
self.mouse_datamap = MouseEvent {
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
raw_input_events: vec![],
};
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.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
}
self.send_keyboard_input(sk);
self.target_pointer_input();
self.send_keyboard_input();
}
fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| {
let result = field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: space.clone(),
});
let valid =
result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
valid.then_some(result.deepest_point_distance)
};
self.capture_manager.update_capture(&self.pointer);
self.capture_manager
.set_new_capture(&self.pointer, distance_calculator);
self.capture_manager.apply_capture(&self.pointer);
if self.capture_manager.capture.is_some() {
return;
}
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
self.pointer.set_handler_order(sorted_handlers.iter());
}
fn send_keyboard_input(&mut self, sk: &impl StereoKitMultiThread) {
fn send_keyboard_input(&mut self) {
let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| {
let result = rx.field_node.get_aspect::<Field>().unwrap().ray_march(Ray {
let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(),
@@ -161,12 +185,12 @@ impl MousePointer {
if let Some(rx) = rx {
let keys = (8_u32..254)
.filter_map(|i| Key::try_from(i).ok())
.filter_map(|k| Some((map_key(k)?, sk.input_key(k))))
.map(|i| unsafe { std::mem::transmute(i) })
.filter_map(|k| Some((map_key(k)?, Input::key(k))))
.filter_map(|(i, k)| {
if k.contains(ButtonState::JUST_ACTIVE) {
if k.is_just_active() {
Some(i as i32)
} else if k.contains(ButtonState::JUST_INACTIVE) {
} else if k.is_just_inactive() {
Some(-(i as i32))
} else {
None
@@ -178,7 +202,7 @@ impl MousePointer {
if !self.keyboard_datamap.keys.is_empty() {
pulse_receiver_client::data(
&rx.node.upgrade().unwrap(),
&self.node.uid,
&self.node.0,
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
)
.unwrap();
@@ -260,9 +284,9 @@ fn map_key(key: Key) -> Option<u32> {
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::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!()),
@@ -277,7 +301,7 @@ fn map_key(key: Key) -> Option<u32> {
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::Backtick => Some(input_event_codes::KEY_GRAVE!()),
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!()),

View File

@@ -1,79 +1,128 @@
use super::{get_sorted_handlers, CaptureManager};
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
input::{tip::Tip, InputMethod, InputType},
fields::{Field, FieldTrait},
input::{InputDataType, InputHandler, InputMethod, Tip, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node,
Node, OwnedNode,
},
objects::{ObjectHandle, SpatialRef},
};
use color_eyre::eyre::Result;
use glam::{Mat4, Vec2, Vec3};
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use std::sync::Arc;
use stereokit::{
named_colors::WHITE, ButtonState, Handed, Model, RenderLayer, StereoKitDraw,
StereoKitMultiThread,
use stereokit_rust::{
material::Material,
model::Model,
sk::MainThreadToken,
system::{Handed, Input},
util::Color128,
};
use zbus::Connection;
#[derive(Default, Deserialize, Serialize)]
#[derive(Default, Debug, Deserialize, Serialize)]
struct ControllerDatamap {
select: f32,
middle: f32,
context: f32,
grab: f32,
scroll: Vec2,
}
pub struct SkController {
_node: Arc<Node>,
object_handle: ObjectHandle<SpatialRef>,
input: Arc<InputMethod>,
model: Model,
handed: Handed,
model: Model,
material: Material,
capture_manager: CaptureManager,
datamap: ControllerDatamap,
}
impl SkController {
pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name(
&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 input = InputMethod::add_to(&_node, tip, None)?;
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let (spatial, object_handle) = SpatialRef::create(
connection,
&("/org/stardustxr/Controller/".to_string()
+ match handed {
Handed::Left => "left",
_ => "right",
}),
);
let model = Model::copy(Model::from_memory(
"cursor.glb",
include_bytes!("cursor.glb"),
None,
)?);
let model_nodes = model.get_nodes();
let mut model_node = model_nodes.visuals().next().unwrap();
let material = Material::copy(&model_node.get_material().unwrap());
model_node.material(&material);
let tip = InputDataType::Tip(Tip::default());
let input = InputMethod::add_to(
&spatial.node().unwrap(),
tip,
Datamap::from_typed(ControllerDatamap::default())?,
)?;
Ok(SkController {
_node,
object_handle,
input,
handed,
model,
material,
capture_manager: CaptureManager::default(),
datamap: Default::default(),
})
}
pub fn update(&mut self, sk: &impl StereoKitDraw) {
let controller = sk.input_controller(self.handed);
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
if *self.input.enabled.lock() {
pub fn update(&mut self, token: &MainThreadToken) {
let controller = Input::controller(self.handed);
let input_node = self.input.spatial.node().unwrap();
input_node.set_enabled(controller.tracked.is_active());
if input_node.enabled() {
let world_transform = Mat4::from_rotation_translation(
controller.aim.orientation,
controller.aim.position,
controller.aim.orientation.into(),
controller.aim.position.into(),
);
sk.model_draw(
&self.model,
self.material
.color_tint(if self.capture_manager.capture.is_none() {
Color128::new_rgb(1.0, 1.0, 1.0)
} else {
Color128::new_rgb(0.0, 1.0, 0.75)
});
self.model.draw(
token,
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
WHITE,
RenderLayer::LAYER0,
None,
None,
);
self.input.spatial.set_local_transform(world_transform);
}
self.datamap.select = controller.trigger;
self.datamap.grab = controller.grip;
self.datamap.scroll = controller.stick;
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
self.datamap = ControllerDatamap {
select: controller.trigger,
middle: controller.stick_click.is_active() as u32 as f32,
context: controller.is_x2_pressed() as u32 as f32,
grab: controller.grip,
scroll: controller.stick.into(),
};
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
Some(field.distance(space, [0.0; 3].into()).abs())
};
self.capture_manager.update_capture(&self.input);
self.capture_manager
.set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() {
return;
}
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
self.input.set_handler_order(sorted_handlers.iter());
}
}

View File

@@ -1,26 +1,30 @@
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
input::{hand::Hand, InputMethod, InputType},
spatial::Spatial,
Node,
},
use crate::core::client::INTERNAL_CLIENT;
use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
use crate::nodes::OwnedNode;
use crate::nodes::{
input::{Hand, InputMethod, Joint},
spatial::Spatial,
Node,
};
use crate::objects::{ObjectHandle, SpatialRef};
use color_eyre::eyre::Result;
use glam::Mat4;
use nanoid::nanoid;
use glam::{Mat4, Quat, Vec3};
use serde::{Deserialize, Serialize};
use stardust_xr::{
schemas::flat::{Hand as FlatHand, Joint},
values::Datamap,
};
use stardust_xr::values::Datamap;
use std::f32::INFINITY;
use std::sync::Arc;
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
use stereokit_rust::util::Color128;
use zbus::Connection;
use super::{get_sorted_handlers, CaptureManager};
fn convert_joint(joint: HandJoint) -> Joint {
Joint {
position: joint.position.into(),
rotation: joint.orientation.into(),
position: Vec3::from(joint.position).into(),
rotation: Quat::from(joint.orientation).into(),
radius: joint.radius,
distance: 0.0,
}
@@ -33,53 +37,66 @@ struct HandDatamap {
}
pub struct SkHand {
_node: Arc<Node>,
input: Arc<InputMethod>,
_node: OwnedNode,
palm_spatial: Arc<Spatial>,
palm_object: ObjectHandle<SpatialRef>,
handed: Handed,
input: Arc<InputMethod>,
capture_manager: CaptureManager,
datamap: HandDatamap,
}
impl SkHand {
pub fn new(handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false);
let hand = InputType::Hand(Box::new(Hand {
base: FlatHand {
right: handed == Handed::Right,
..Default::default()
},
}));
let input = InputMethod::add_to(&_node, hand, None)?;
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let (palm_spatial, palm_object) = SpatialRef::create(
connection,
&("/org/stardustxr/Hand/".to_string()
+ match handed {
Handed::Left => "left",
_ => "right",
} + "/palm"),
);
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
let hand = InputDataType::Hand(Hand {
right: handed == Handed::Right,
..Default::default()
});
let datamap = Datamap::from_typed(HandDatamap::default())?;
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
Input::hand_visible(handed, false);
Ok(SkHand {
_node,
input,
palm_spatial,
palm_object,
handed,
input,
capture_manager: CaptureManager::default(),
datamap: Default::default(),
})
}
pub fn update(&mut self, controller_enabled: bool, sk: &impl StereoKitMultiThread) {
let sk_hand = sk.input_hand(self.handed);
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
let controller_active = controller_enabled
&& sk
.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() {
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.proximal = convert_joint(sk_hand.fingers[0][2]);
hand.base.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
let sk_hand = Input::hand(self.handed);
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
let input_node = self.input.spatial.node().unwrap();
input_node.set_enabled(
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
&& sk_hand.tracked.is_active(),
);
if input_node.enabled() {
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
hand.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
for (finger, sk_finger) in [
(&mut hand.base.index, sk_hand.fingers[1]),
(&mut hand.base.middle, sk_hand.fingers[2]),
(&mut hand.base.ring, sk_hand.fingers[3]),
(&mut hand.base.little, sk_hand.fingers[4]),
for (finger, mut sk_finger) in [
(&mut hand.index, sk_hand.fingers[1]),
(&mut hand.middle, sk_hand.fingers[2]),
(&mut hand.ring, sk_hand.fingers[3]),
(&mut hand.little, sk_hand.fingers[4]),
] {
sk_finger[4].radius = 0.0;
finger.tip = convert_joint(sk_finger[4]);
finger.distal = convert_joint(sk_finger[3]);
finger.intermediate = convert_joint(sk_finger[2]);
@@ -87,21 +104,134 @@ impl SkHand {
finger.metacarpal = convert_joint(sk_finger[0]);
}
hand.base.palm.position = sk_hand.palm.position.into();
hand.base.palm.rotation = sk_hand.palm.orientation.into();
hand.base.palm.radius =
hand.palm.position = Vec3::from(sk_hand.palm.position).into();
hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
hand.palm.radius =
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
hand.base.wrist.position = sk_hand.wrist.position.into();
hand.base.wrist.rotation = sk_hand.wrist.orientation.into();
hand.base.wrist.radius =
self.palm_spatial
.set_local_transform(Mat4::from_rotation_translation(
hand.palm.rotation.into(),
hand.palm.position.into(),
));
hand.wrist.position = Vec3::from(sk_hand.wrist.position).into();
hand.wrist.rotation = Quat::from(sk_hand.wrist.orientation).into();
hand.wrist.radius =
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
hand.base.elbow = None;
hand.elbow = None;
self.draw(
token,
if self.capture_manager.capture.is_none() {
Color128::new_rgb(1.0, 1.0, 1.0)
} else {
Color128::new_rgb(0.0, 1.0, 0.75)
},
hand,
);
}
}
self.datamap.pinch_strength = sk_hand.pinch_activation;
self.datamap.grab_strength = sk_hand.grip_activation;
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
let InputDataType::Hand(hand) = data else {
return None;
};
let thumb_tip_distance = field.distance(space, hand.thumb.tip.position.into());
let index_tip_distance = field.distance(space, hand.index.tip.position.into());
let middle_tip_distance = field.distance(space, hand.middle.tip.position.into());
let ring_tip_distance = field.distance(space, hand.ring.tip.position.into());
Some(
(thumb_tip_distance * 0.3)
+ (index_tip_distance * 0.4)
+ (middle_tip_distance * 0.15)
+ (ring_tip_distance * 0.15),
)
};
self.capture_manager.update_capture(&self.input);
self.capture_manager
.set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() {
return;
}
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
self.input.set_handler_order(sorted_handlers.iter());
}
fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) {
// thumb
Lines::add_list(
token,
&[
joint_to_line_point(&hand.thumb.tip, color),
joint_to_line_point(&hand.thumb.distal, color),
joint_to_line_point(&hand.thumb.proximal, color),
joint_to_line_point(&hand.thumb.metacarpal, color),
],
);
// index
Lines::add_list(
token,
&[
joint_to_line_point(&hand.index.tip, color),
joint_to_line_point(&hand.index.distal, color),
joint_to_line_point(&hand.index.intermediate, color),
joint_to_line_point(&hand.index.proximal, color),
joint_to_line_point(&hand.index.metacarpal, color),
],
);
// middle
Lines::add_list(
token,
&[
joint_to_line_point(&hand.middle.tip, color),
joint_to_line_point(&hand.middle.distal, color),
joint_to_line_point(&hand.middle.intermediate, color),
joint_to_line_point(&hand.middle.proximal, color),
joint_to_line_point(&hand.middle.metacarpal, color),
],
);
// ring
Lines::add_list(
token,
&[
joint_to_line_point(&hand.ring.tip, color),
joint_to_line_point(&hand.ring.distal, color),
joint_to_line_point(&hand.ring.intermediate, color),
joint_to_line_point(&hand.ring.proximal, color),
joint_to_line_point(&hand.ring.metacarpal, color),
],
);
// palm
Lines::add_list(
token,
&[
joint_to_line_point(&hand.wrist, color),
joint_to_line_point(&hand.thumb.metacarpal, color),
joint_to_line_point(&hand.index.metacarpal, color),
joint_to_line_point(&hand.middle.metacarpal, color),
joint_to_line_point(&hand.ring.metacarpal, color),
joint_to_line_point(&hand.little.metacarpal, color),
joint_to_line_point(&hand.wrist, color),
],
);
}
}
fn joint_to_line_point(joint: &Joint, color: Color128) -> LinePoint {
LinePoint {
pt: Vec3::from(joint.position).into(),
thickness: joint.radius * 2.0,
color: color.into(),
}
}

View File

@@ -1,2 +1,274 @@
#![allow(unused)]
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
fields::{Field, Shape, EXPORTED_FIELDS},
spatial::{Spatial, EXPORTED_SPATIALS},
Node, OwnedNode,
},
};
use glam::{vec3, Mat4};
use input::{
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
sk_hand::SkHand,
};
use play_space::PlaySpaceBounds;
use std::{marker::PhantomData, sync::Arc};
use stereokit_rust::{
sk::{DisplayMode, MainThreadToken, Sk},
system::{Handed, Input, Key, World},
util::Device,
};
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection};
pub mod input;
pub mod play_space;
enum Inputs {
XR {
controller_left: SkController,
controller_right: SkController,
hand_left: SkHand,
hand_right: SkHand,
eye_pointer: Option<EyePointer>,
},
MousePointer(MousePointer),
// Controllers((SkController, SkController)),
Hands {
left: SkHand,
right: SkHand,
},
}
pub struct ServerObjects {
connection: Connection,
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
inputs: Inputs,
disable_controllers: bool,
disable_hands: bool,
}
impl ServerObjects {
pub fn new(
connection: Connection,
sk: &Sk,
disable_controllers: bool,
disable_hands: bool,
) -> ServerObjects {
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
let play_space = (World::has_bounds()
&& World::get_bounds_size().x != 0.0
&& World::get_bounds_size().y != 0.0)
.then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
if play_space.is_some() {
let dbus_connection = connection.clone();
tokio::task::spawn(async move {
PlaySpaceBounds::create(&dbus_connection).await;
dbus_connection
.request_name("org.stardustxr.PlaySpace")
.await
.unwrap();
});
}
tokio::task::spawn({
let connection = connection.clone();
async move {
connection
.request_name("org.stardustxr.Controllers")
.await
.unwrap();
connection
.request_name("org.stardustxr.Hands")
.await
.unwrap();
}
});
let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
Inputs::XR {
controller_left: SkController::new(&connection, Handed::Left).unwrap(),
controller_right: SkController::new(&connection, Handed::Right).unwrap(),
hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
eye_pointer: Device::has_eye_gaze()
.then(EyePointer::new)
.transpose()
.unwrap(),
}
} else {
Inputs::MousePointer(MousePointer::new().unwrap())
};
ServerObjects {
connection,
hmd,
play_space,
inputs,
disable_controllers,
disable_hands,
}
}
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
let hmd_pose = Input::get_head();
self.hmd
.0
.set_local_transform(Mat4::from_scale_rotation_translation(
vec3(1.0, 1.0, 1.0),
hmd_pose.orientation.into(),
hmd_pose.position.into(),
));
if let Some(play_space) = self.play_space.as_ref() {
let pose = World::get_bounds_pose();
play_space
.0
.set_local_transform(Mat4::from_rotation_translation(
pose.orientation.into(),
pose.position.into(),
));
}
if sk.get_active_display_mode() != DisplayMode::MixedReality {
if Input::key(Key::F6).is_just_inactive() {
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
}
// if Input::key(Key::F7).is_just_inactive() {
// self.inputs = Inputs::Controllers((
// SkController::new(Handed::Left).unwrap(),
// SkController::new(Handed::Right).unwrap(),
// ));
// }
if Input::key(Key::F8).is_just_inactive() {
self.inputs = Inputs::Hands {
left: SkHand::new(&self.connection, Handed::Left).unwrap(),
right: SkHand::new(&self.connection, Handed::Right).unwrap(),
};
}
}
match &mut self.inputs {
Inputs::XR {
controller_left,
controller_right,
hand_left,
hand_right,
eye_pointer,
} => {
if !self.disable_controllers {
controller_left.update(token);
controller_right.update(token);
}
if !self.disable_hands {
hand_left.update(sk, token);
hand_right.update(sk, token);
}
if let Some(eye_pointer) = eye_pointer {
eye_pointer.update();
}
}
Inputs::MousePointer(mouse_pointer) => mouse_pointer.update(),
// Inputs::Controllers((left, right)) => {
// left.update(token);
// right.update(token);
// }
Inputs::Hands { left, right } => {
left.update(sk, token);
right.update(sk, token);
}
}
}
}
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
impl<I: Interface> Drop for ObjectHandle<I> {
fn drop(&mut self) {
let connection = self.0.clone();
let object_path = self.1.clone();
tokio::task::spawn(async move {
connection.object_server().remove::<I, _>(object_path);
});
}
}
pub struct SpatialRef(u64, OwnedNode);
impl SpatialRef {
pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<SpatialRef>) {
let node = OwnedNode(Arc::new(Node::generate(&INTERNAL_CLIENT, false)));
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let uid: u64 = rand::random();
EXPORTED_SPATIALS.lock().insert(uid, node.0.clone());
tokio::task::spawn({
let connection = connection.clone();
let path = path.to_string();
async move {
connection
.object_server()
.at(path, Self(uid, node))
.await
.unwrap();
}
});
(
spatial,
ObjectHandle(
connection.clone(),
OwnedObjectPath::try_from(path.to_string()).unwrap(),
PhantomData,
),
)
}
}
#[interface(name = "org.stardustxr.SpatialRef")]
impl SpatialRef {
#[zbus(property)]
fn uid(&self) -> u64 {
self.0
}
}
pub struct FieldRef(u64, OwnedNode);
impl FieldRef {
pub fn create(
connection: &Connection,
path: &str,
shape: Shape,
) -> (Arc<Field>, ObjectHandle<FieldRef>) {
let node = OwnedNode(Arc::new(Node::generate(&INTERNAL_CLIENT, false)));
Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let field = Field::add_to(&node.0, shape).unwrap();
let uid: u64 = rand::random();
EXPORTED_FIELDS.lock().insert(uid, node.0.clone());
tokio::task::spawn({
let connection = connection.clone();
let path = path.to_string();
async move {
connection
.object_server()
.at(path, Self(uid, node))
.await
.unwrap();
}
});
(
field,
ObjectHandle(
connection.clone(),
OwnedObjectPath::try_from(path.to_string()).unwrap(),
PhantomData,
),
)
}
}
#[interface(name = "org.stardustxr.FieldRef")]
impl FieldRef {
#[zbus(property)]
fn uid(&self) -> u64 {
self.0
}
}

View File

@@ -1,81 +1,26 @@
use std::sync::Arc;
use stereokit_rust::system::World;
use zbus::{interface, Connection, ObjectServer};
use color_eyre::eyre::Result;
use glam::Mat4;
use mint::Vector2;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use stereokit::StereoKitMultiThread;
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
data::PulseReceiver,
fields::{r#box::BoxField, Field},
spatial::Spatial,
Node,
},
};
#[derive(Debug, Deserialize, Serialize)]
struct PlaySpaceMap {
play_space: (),
size: Vector2<f32>,
}
impl Default for PlaySpaceMap {
fn default() -> Self {
Self {
play_space: (),
size: [0.0; 2].into(),
}
pub struct PlaySpaceBounds;
impl PlaySpaceBounds {
pub async fn create(connection: &Connection) {
connection
.object_server()
.at("/org/stardustxr/PlaySpace", Self)
.await
.unwrap();
}
}
pub struct PlaySpace {
_node: Arc<Node>,
spatial: Arc<Spatial>,
field: Arc<Field>,
_pulse_rx: Arc<PulseReceiver>,
}
impl PlaySpace {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
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 = PulseReceiver::add_to(
&node,
node.clone(),
Datamap::from_typed(PlaySpaceMap::default())?,
)?;
Ok(PlaySpace {
_node: node,
spatial,
field,
_pulse_rx: pulse_rx,
})
}
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let pose = sk.world_get_bounds_pose();
self.spatial
.set_local_transform(Mat4::from_rotation_translation(
pose.orientation,
pose.position,
));
let Field::Box(box_field) = self.field.as_ref() else {
return;
};
box_field.set_size(
[
sk.world_get_bounds_size().x,
0.0,
sk.world_get_bounds_size().y,
]
.into(),
);
#[interface(name = "org.stardustxr.PlaySpace")]
impl PlaySpaceBounds {
#[zbus(property)]
fn bounds(&self) -> Vec<(f64, f64)> {
let bounds = World::get_bounds_size();
vec![
((bounds.x).into(), (bounds.y).into()),
((bounds.x).into(), (-bounds.y).into()),
((-bounds.x).into(), (-bounds.y).into()),
((-bounds.x).into(), (bounds.y).into()),
]
}
}

114
src/session.rs Normal file
View File

@@ -0,0 +1,114 @@
use crate::core::client::CLIENTS;
use crate::core::client_state::ClientStateParsed;
#[cfg(feature = "wayland")]
use crate::wayland::WAYLAND_DISPLAY;
use crate::{CliArgs, STARDUST_INSTANCE};
use directories::ProjectDirs;
use rustc_hash::FxHashMap;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::time::Duration;
use tokio::task::LocalSet;
use tracing::info;
pub async fn save_session(project_dirs: &ProjectDirs) {
let session_id = nanoid::nanoid!();
let state_dir = project_dirs.state_dir().unwrap();
let session_dir = state_dir.join(&session_id);
std::fs::create_dir_all(&session_dir).unwrap();
let _ = std::fs::remove_dir_all(state_dir.join("latest"));
std::os::unix::fs::symlink(&session_dir, state_dir.join("latest")).unwrap();
let local_set = LocalSet::new();
for client in CLIENTS.get_vec() {
let session_dir = session_dir.clone();
local_set.spawn_local(async move {
tokio::select! {
biased;
s = client.save_state() => {if let Some(s) = s { s.to_file(&session_dir) }},
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
}
});
}
local_set.await;
info!("Session ID for restore is {session_id}");
}
pub fn launch_start(cli_args: &CliArgs, project_dirs: &ProjectDirs) -> Vec<Child> {
match (&cli_args.restore, &cli_args.startup_script) {
(Some(session_id), _) => restore_session(
&project_dirs.state_dir().unwrap().join(session_id),
cli_args.debug_launched_clients,
),
(None, Some(startup_script)) => run_script(
&startup_script.clone().canonicalize().unwrap_or_default(),
cli_args.debug_launched_clients,
),
(None, None) => run_script(
&project_dirs.config_dir().join("startup"),
cli_args.debug_launched_clients,
),
}
}
pub fn restore_session(session_dir: &Path, debug_launched_clients: bool) -> Vec<Child> {
let Ok(clients) = session_dir.read_dir() else {
return Vec::new();
};
clients
.filter_map(Result::ok)
.filter_map(|c| ClientStateParsed::from_file(&c.path()))
.filter_map(ClientStateParsed::launch_command)
.filter_map(|c| run_client(c, debug_launched_clients))
.collect()
}
pub fn run_script(script_path: &Path, debug_launched_clients: bool) -> Vec<Child> {
let _ = std::fs::set_permissions(script_path, std::fs::Permissions::from_mode(0o755));
let startup_command = Command::new(script_path);
run_client(startup_command, debug_launched_clients)
.map(|c| vec![c])
.unwrap_or_default()
}
pub fn run_client(mut command: Command, debug_launched_clients: bool) -> Option<Child> {
command.stdin(Stdio::null());
if !debug_launched_clients {
command.stdout(Stdio::null());
command.stderr(Stdio::null());
}
for (var, value) in connection_env() {
command.env(var, value);
}
let child = command.spawn().ok()?;
Some(child)
}
pub fn connection_env() -> FxHashMap<String, String> {
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
if let Some(flat_wayland_display) = std::env::var_os("WAYLAND_DISPLAY") {
env.insert(
"FLAT_WAYLAND_DISPLAY".to_string(),
flat_wayland_display.to_string_lossy().into_owned(),
);
}
#[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());
}
env
}

View File

@@ -1,13 +1,22 @@
use crate::wayland::surface::CoreSurface;
use super::state::{ClientState, WaylandState};
use super::{
state::{ClientState, WaylandState},
utils::{ChildInfoExt, WlSurfaceExt},
xdg_shell::surface_panel_item,
};
use crate::{
nodes::items::panel::{ChildInfo, Geometry, SurfaceId},
wayland::surface::CoreSurface,
};
use parking_lot::Mutex;
use portable_atomic::{AtomicU32, Ordering};
#[cfg(feature = "xwayland")]
use smithay::xwayland::XWaylandClientData;
use rand::Rng;
use smithay::{
backend::renderer::utils::{on_commit_buffer_handler, RendererSurfaceStateUserData},
delegate_compositor,
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
wayland::compositor::{self, CompositorClientState, CompositorHandler, CompositorState},
wayland::compositor::{
self, add_post_commit_hook, CompositorClientState, CompositorHandler, CompositorState,
},
};
use std::sync::Arc;
use tracing::debug;
@@ -19,8 +28,10 @@ impl CompositorHandler for WaylandState {
fn commit(&mut self, surface: &WlSurface) {
debug!(?surface, "Surface commit");
on_commit_buffer_handler::<WaylandState>(surface);
let mut count = 0;
let core_surface = compositor::with_states(surface, |data| {
compositor::with_states(surface, |data| {
let count_new = data
.data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
@@ -32,20 +43,83 @@ impl CompositorHandler for WaylandState {
data.data_map.get::<Arc<CoreSurface>>().cloned()
});
if let Some(core_surface) = core_surface {
core_surface.commit(count);
}
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
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;
&client.get_data::<ClientState>().unwrap().compositor_state
}
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
let id = rand::thread_rng().gen_range(0..u64::MAX);
surface.insert_data(SurfaceId::Child(id));
CoreSurface::add_to(surface);
let Some(parent_surface_id) = parent.get_data::<SurfaceId>() else {
return;
};
surface.insert_data(Mutex::new(ChildInfo {
id,
parent: parent_surface_id,
geometry: Geometry {
origin: [0; 2].into(),
size: [256; 2].into(),
},
z_order: 1,
receives_input: false,
}));
let Some(panel_item) = surface_panel_item(parent) else {
return;
};
let panel_item_weak = Arc::downgrade(&panel_item);
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
unimplemented!()
surf.insert_data(panel_item_weak.clone());
let Some(panel_item) = surface_panel_item(surf) else {
return;
};
panel_item.backend.new_child(surf);
});
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
let Some(view) = surf
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|s| s.lock().ok()?.view())
.flatten()
else {
return;
};
let mut changed = false;
surf.with_child_info(|info| {
if info.geometry.origin.x != view.offset.x
&& info.geometry.origin.y != view.offset.y
{
changed = true;
}
if info.geometry.size.x != view.dst.w as u32
&& info.geometry.size.y != view.dst.h as u32
{
changed = true;
}
info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into();
});
let Some(panel_item) = surface_panel_item(surf) else {
return;
};
if changed {
panel_item.backend.reposition_child(surf);
}
});
}
fn destroyed(&mut self, surface: &WlSurface) {
let Some(panel_item) = surface_panel_item(surface) else {
return;
};
if surface.get_child_info().is_some() {
panel_item.backend.drop_child(surface);
}
}
}

View File

@@ -90,7 +90,7 @@ impl KdeDecorationHandler for WaylandState {
decoration: &OrgKdeKwinServerDecoration,
mode: WEnum<KdeMode>,
) {
let Ok(mode) = mode.into_result() else {return};
let Ok(mode) = mode.into_result() else { return };
decoration.mode(mode);
}
}

View File

@@ -117,8 +117,13 @@ impl Dispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
return;
}
let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty());
dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid);
let mut dma = Dmabuf::builder(
(width, height),
format,
Modifier::Invalid,
DmabufFlags::empty(),
);
dma.add_plane(name, 0, offset0 as u32, stride0 as u32);
match dma.build() {
Some(dmabuf) => {
state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap();

View File

@@ -6,52 +6,40 @@ mod state;
mod surface;
// mod xdg_activation;
mod drm;
mod utils;
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 crate::wayland::seat::SeatData;
use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{ensure, Result};
use global_counter::primitive::exact::CounterU32;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use sk::StereoKitDraw;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::egl::EGLContext;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportDma;
use smithay::backend::renderer::{ImportDma, Renderer};
use smithay::output::Output;
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::fd::{IntoRawFd, OwnedFd};
use std::os::unix::prelude::AsRawFd;
use std::{
ffi::c_void,
os::unix::{net::UnixListener, prelude::FromRawFd},
sync::Arc,
};
use stereokit as sk;
use stereokit_rust::system::{Backend, BackendGraphics};
use tokio::io::unix::AsyncFdReadyGuard;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
};
use tracing::{debug_span, info, instrument};
pub static X_DISPLAY: OnceCell<u32> = OnceCell::new();
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
struct EGLRawHandles {
display: *const c_void,
@@ -60,16 +48,15 @@ struct EGLRawHandles {
}
fn get_sk_egl() -> Result<EGLRawHandles> {
ensure!(
unsafe { sk::sys::backend_graphics_get() }
== sk::sys::backend_graphics__backend_graphics_opengles_egl,
Backend::graphics() == BackendGraphics::OpenGLESEGL,
"StereoKit is not running using EGL!"
);
Ok(unsafe {
EGLRawHandles {
display: sk::sys::backend_opengl_egl_get_display() as *const c_void,
config: sk::sys::backend_opengl_egl_get_config() as *const c_void,
context: sk::sys::backend_opengl_egl_get_context() as *const c_void,
display: stereokit_rust::system::backend_opengl_egl_get_display() as *const c_void,
config: stereokit_rust::system::backend_opengl_egl_get_config() as *const c_void,
context: stereokit_rust::system::backend_opengl_egl_get_context() as *const c_void,
}
})
}
@@ -92,17 +79,25 @@ impl DisplayWrapper {
}
}
struct UnownedFd(Option<AsyncFd<OwnedFd>>);
impl UnownedFd {
async fn readable(&self) -> std::io::Result<AsyncFdReadyGuard<'_, OwnedFd>> {
self.0.as_ref().unwrap().readable().await
}
}
impl Drop for UnownedFd {
fn drop(&mut self) {
self.0.take().unwrap().into_inner().into_raw_fd();
}
}
pub struct Wayland {
display: Arc<DisplayWrapper>,
pub socket_name: Option<String>,
join_handle: JoinHandle<Result<()>>,
renderer: GlesRenderer,
output: Output,
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
wayland_state: Arc<Mutex<WaylandState>>,
#[cfg(feature = "xwayland_rootful")]
pub x_lock: X11Lock,
#[cfg(feature = "xwayland_rootless")]
pub xwayland_state: XWaylandState,
}
impl Wayland {
pub fn new() -> Result<Self> {
@@ -121,9 +116,8 @@ impl Wayland {
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone()));
#[cfg(feature = "xwayland_rootless")]
let xwayland_state = XWaylandState::create(&display_handle)?;
let wayland_state = WaylandState::new(display_handle, &renderer, dmabuf_tx);
let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx);
let output = wayland_state.lock().output.clone();
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
let socket_name = socket
@@ -133,23 +127,17 @@ impl Wayland {
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");
let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state.clone())?;
let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state)?;
Ok(Wayland {
display,
socket_name,
join_handle,
renderer,
output,
dmabuf_rx,
wayland_state,
#[cfg(feature = "xwayland_rootful")]
x_lock: x_display,
#[cfg(feature = "xwayland_rootless")]
xwayland_state,
})
}
@@ -162,30 +150,29 @@ impl Wayland {
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
let dispatch_poll_fd = display.poll_fd()?;
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
let dispatch_poll_listener = UnownedFd(Some(AsyncFd::new(dispatch_poll_fd)?));
let dh1 = display.handle();
let mut dh2 = dh1.clone();
Ok(task::new(|| "wayland loop", async move {
task::new(|| "wayland loop", async move {
let _socket = socket; // Keep the socket alive
loop {
tokio::select! {
acc = listen_async.accept() => { // New client connected
let (stream, _) = acc?;
let client_state = Arc::new(ClientState {
pid: stream.peer_cred().ok().and_then(|c| c.pid()),
id: OnceCell::new(),
compositor_state: Default::default(),
display: Arc::downgrade(&display),
seat: SeatData::new(&dh1)
seat: state.lock().seat.clone(),
});
let client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
let _ = client_state.seat.client.set(client.id());
let _client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
}
e = dispatch_poll_listener.readable() => { // Dispatch
let mut guard = e?;
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
display.dispatch_clients(&mut *state.lock())?;
display.dispatch_clients(&mut state.lock())?;
display.flush_clients(None);
Ok(())
})?;
@@ -193,11 +180,11 @@ impl Wayland {
}
}
}
})?)
})
}
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
pub fn update(&mut self, sk: &impl StereoKitDraw) {
#[instrument(level = "debug", name = "Wayland frame", skip(self))]
pub fn update(&mut self) {
while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
if let Some(notifier) = notifier {
@@ -206,17 +193,16 @@ impl Wayland {
}
}
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.process(sk, &mut self.renderer);
core_surface.process(&mut self.renderer);
}
let _ = self.renderer.cleanup_texture_cache();
self.display.flush_clients(None);
}
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
let output = self.wayland_state.lock().output.clone();
pub fn frame_event(&self) {
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.frame(sk, output.clone());
core_surface.frame(self.output.clone());
}
}

View File

@@ -1,471 +1,82 @@
use super::{
state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER,
};
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
use crate::{
core::task,
nodes::items::panel::{Backend, Geometry, PanelItem},
};
use color_eyre::eyre::{bail, eyre, Result};
use mint::Vector2;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rand::{seq::IteratorRandom, thread_rng};
use rustc_hash::{FxHashMap, FxHashSet};
use smithay::{
input::keyboard::{KeymapFile, ModifiersState},
reexports::wayland_server::{
backend::{ClientId, GlobalId, ObjectId},
protocol::{
wl_keyboard::{self, KeyState, WlKeyboard},
wl_pointer::{self, Axis, ButtonState, WlPointer},
wl_seat::{self, Capability, WlSeat, EVT_NAME_SINCE},
wl_surface::WlSurface,
wl_touch::{self, WlTouch},
},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak as WlWeak,
nodes::{
data::KEYMAPS,
items::panel::{Backend, Geometry, PanelItem},
},
};
use mint::Vector2;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use slotmap::KeyData;
use smithay::{
backend::input::{AxisRelativeDirection, ButtonState, KeyState},
delegate_seat,
input::{
keyboard::{FilterResult, LedState},
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent},
touch::{self, DownEvent, UpEvent},
Seat, SeatHandler,
},
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak as WlWeak},
utils::SERIAL_COUNTER,
wayland::compositor,
};
use std::{
collections::VecDeque,
sync::Arc,
time::{Duration, Instant},
};
use std::sync::{Arc, Weak};
use tokio::sync::watch;
use tracing::{debug, warn};
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keycode, Keymap};
impl SeatHandler for WaylandState {
type PointerFocus = WlSurface;
type KeyboardFocus = WlSurface;
type TouchFocus = WlSurface;
fn seat_state(&mut self) -> &mut smithay::input::SeatState<Self> {
&mut self.seat_state
}
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&Self::KeyboardFocus>) {}
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.seat.cursor_info_tx.send_modify(|c| match image {
CursorImageStatus::Hidden => c.surface = None,
CursorImageStatus::Surface(surface) => {
CoreSurface::add_to(&surface);
compositor::with_states(&surface, |data| {
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1);
}
});
c.surface = Some(surface.downgrade())
}
_ => (),
});
}
fn led_state_changed(&mut self, _seat: &Seat<Self>, _led_state: LedState) {}
}
delegate_seat!(WaylandState);
pub fn handle_cursor<B: Backend>(
panel_item: &Arc<PanelItem<B>>,
mut cursor: watch::Receiver<Option<CursorInfo>>,
mut cursor: watch::Receiver<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 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));
panel_item.set_cursor(cursor_info.cursor_data());
}
});
}
pub struct KeyboardInfo {
keymap_string: String,
keymap: KeymapFile,
state: xkb::State,
mods: ModifiersState,
keys: FxHashSet<u32>,
}
impl KeyboardInfo {
pub fn new(keymap_string: String, keymap: &Keymap) -> Self {
KeyboardInfo {
keymap_string,
state: xkb::State::new(keymap),
keymap: KeymapFile::new(keymap),
mods: ModifiersState::default(),
keys: FxHashSet::default(),
}
}
pub fn process(&mut self, key: u32, pressed: bool, keyboard: &WlKeyboard) -> Result<usize> {
let xkb_key_state = if pressed {
xkb::KeyDirection::Down
} else {
xkb::KeyDirection::Up
};
let state_components = self.state.update_key(Keycode::new(key + 8), xkb_key_state);
if state_components != 0 {
self.mods.update_with(&self.state);
keyboard.modifiers(
0,
self.mods.serialized.depressed,
self.mods.serialized.latched,
self.mods.serialized.locked,
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);
match wl_key_state {
KeyState::Pressed => {
self.keys.insert(key);
}
KeyState::Released => {
self.keys.remove(&key);
}
_ => unimplemented!(),
}
Ok(self.keys.len())
}
}
unsafe impl Send for KeyboardInfo {}
#[derive(Debug, Clone, Copy)]
pub enum PointerEvent {
Motion(Vector2<f32>),
Button {
button: u32,
state: u32,
},
Scroll {
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
},
}
#[derive(Debug, Clone)]
pub enum KeyboardEvent {
Keymap,
Key { key: u32, state: bool },
}
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_millis(50);
struct SurfaceInfo {
wl_surface: WlWeak<WlSurface>,
cursor_sender: watch::Sender<Option<CursorInfo>>,
pointer_queue: VecDeque<PointerEvent>,
pointer_latest_event: Instant,
keyboard_queue: VecDeque<KeyboardEvent>,
keyboard_info: Option<KeyboardInfo>,
}
impl SurfaceInfo {
fn new(wl_surface: &WlSurface, cursor_sender: watch::Sender<Option<CursorInfo>>) -> Self {
SurfaceInfo {
wl_surface: wl_surface.downgrade(),
cursor_sender,
pointer_queue: VecDeque::new(),
pointer_latest_event: Instant::now(),
keyboard_queue: VecDeque::new(),
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 {
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
let Some(focus_size) = core_surface.size() else { return false; };
if !self.pointer_queue.is_empty() {
self.pointer_latest_event = Instant::now();
}
while let Some(event) = self.pointer_queue.pop_front() {
match (locked, event) {
(false, PointerEvent::Motion(pos)) => {
pointer.enter(
SERIAL_COUNTER.inc(),
&focus,
(pos.x as f64).clamp(0.0, focus_size.x as f64),
(pos.y as f64).clamp(0.0, focus_size.y as f64),
);
locked = true;
}
(true, PointerEvent::Motion(pos)) => {
pointer.motion(
0,
(pos.x as f64).clamp(0.0, focus_size.x as f64),
(pos.y as f64).clamp(0.0, focus_size.y as f64),
);
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(true, PointerEvent::Button { button, state }) => {
pointer.button(
0,
0,
button,
match state {
0 => ButtonState::Released,
1 => ButtonState::Pressed,
_ => continue,
},
);
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(
true,
PointerEvent::Scroll {
axis_continuous,
axis_discrete,
},
) => {
if let Some(axis_continuous) = axis_continuous {
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
pointer.axis(0, Axis::VerticalScroll, -axis_continuous.y as f64);
}
if pointer.version() >= wl_pointer::EVT_AXIS_DISCRETE_SINCE {
if let Some(axis_discrete) = axis_discrete {
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
pointer.axis_discrete(Axis::VerticalScroll, -axis_discrete.y as i32);
}
}
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::VerticalScroll);
}
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(locked, event) => {
warn!(locked, ?event, "Invalid pointer event!");
}
}
}
if self.pointer_latest_event.elapsed() > POINTER_EVENT_TIMEOUT {
pointer.leave(SERIAL_COUNTER.inc(), &focus);
locked = false;
}
self.flush();
locked
}
fn handle_keyboard_events(&mut self, keyboard: &WlKeyboard, mut locked: bool) -> bool {
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
let Some(info) = self.keyboard_info.as_mut() else { return true; };
if !locked {
keyboard.enter(0, &focus, vec![]);
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
locked = info.keymap.send(keyboard).is_ok();
}
while let Some(event) = self.keyboard_queue.pop_front() {
debug!(locked, ?event, "Process keyboard event");
match (locked, event) {
(true, KeyboardEvent::Keymap) => {
let _ = info.keymap.send(keyboard);
}
(true, KeyboardEvent::Key { key, state }) => {
if let Ok(key_count) = info.process(key, state, keyboard) {
if key_count == 0 {
keyboard.leave(SERIAL_COUNTER.inc(), &focus);
return false;
}
}
}
(locked, event) => {
warn!(locked, ?event, "Invalid keyboard event!");
}
}
}
self.flush();
locked
}
}
pub struct SeatData {
pub client: OnceCell<ClientId>,
global_id: OnceCell<GlobalId>,
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
touch: OnceCell<WlTouch>,
touches: Mutex<FxHashMap<ObjectId, u32>>,
}
impl SeatData {
pub fn new(dh: &DisplayHandle) -> Arc<Self> {
let seat_data = Arc::new(SeatData {
client: OnceCell::new(),
global_id: OnceCell::new(),
surfaces: Mutex::new(FxHashMap::default()),
pointer: OnceCell::new(),
keyboard: OnceCell::new(),
touch: OnceCell::new(),
touches: Mutex::new(FxHashMap::default()),
});
let _ = seat_data
.global_id
.set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone()));
seat_data
}
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 Some((_, focus)) = self.keyboard.get() else {bail!("Could not get keyboard")};
for surface in surfaces {
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
.keyboard_info
.replace(KeyboardInfo::new(keymap_str.clone(), &keymap));
if *focus.lock() == surface.id() {
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
}
}
Ok(())
}
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
let mut surfaces = self.surfaces.lock();
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
surface_info.pointer_queue.push_back(event);
drop(surfaces);
self.handle_pointer_events();
}
pub fn keyboard_event(&self, surface: &WlSurface, event: KeyboardEvent) {
let mut surfaces = self.surfaces.lock();
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
surface_info.keyboard_queue.push_back(event);
drop(surfaces);
self.handle_keyboard_events();
}
fn handle_pointer_events(&self) {
let mut surfaces = self.surfaces.lock();
let Some((pointer, pointer_focus)) = self.pointer.get() else {return};
let mut pointer_focus = pointer_focus.lock();
loop {
let locked = !pointer_focus.is_null();
// Pick a pointer to focus on if there is none
if pointer_focus.is_null() {
*pointer_focus = surfaces
.iter()
.filter(|(_k, v)| !v.pointer_queue.is_empty())
.map(|(k, _v)| k)
.choose(&mut thread_rng())
.cloned()
.unwrap_or(ObjectId::null());
}
if pointer_focus.is_null() {
// If there's still none, guess we're done with pointer events for the time being
break;
}
let Some(surface_info) = surfaces.get_mut(&pointer_focus) else {break};
if surface_info.handle_pointer_events(pointer, locked) {
// We haven't gotten to a point where we can switch the focus
break;
} else {
*pointer_focus = ObjectId::null();
}
}
}
fn handle_keyboard_events(&self) {
let mut surfaces = self.surfaces.lock();
let Some((keyboard, keyboard_focus)) = self.keyboard.get() else {return};
let mut keyboard_focus = keyboard_focus.lock();
loop {
let locked = !keyboard_focus.is_null();
// Pick a keyboard to focus on if there is none
if keyboard_focus.is_null() {
*keyboard_focus = surfaces
.iter()
.filter(|(_k, v)| v.keyboard_info.is_some())
.filter(|(_k, v)| !v.keyboard_queue.is_empty())
.map(|(k, _v)| k)
.choose(&mut thread_rng())
.cloned()
.unwrap_or(ObjectId::null());
}
// If there's still none, guess we're done with keyboard events for the time being
let Some(surface_info) = surfaces.get_mut(&keyboard_focus) else {break};
if surface_info.handle_keyboard_events(keyboard, locked) {
// We haven't gotten to a point where we can switch the focus
break;
} else {
*keyboard_focus = ObjectId::null();
}
}
}
pub fn new_surface(&self, surface: &WlSurface) -> watch::Receiver<Option<CursorInfo>> {
let (tx, rx) = watch::channel(None);
self.surfaces
.lock()
.insert(surface.id(), SurfaceInfo::new(surface, tx));
rx
}
pub fn drop_surface(&self, surface: &WlSurface) {
self.surfaces.lock().remove(&surface.id());
if let Some((_, pointer_focus)) = self.pointer.get() {
let mut pointer_focus = pointer_focus.lock();
if *pointer_focus == surface.id() {
*pointer_focus = ObjectId::null();
}
}
if let Some((_, keyboard_focus)) = self.keyboard.get() {
let mut keyboard_focus = keyboard_focus.lock();
if *keyboard_focus == surface.id() {
*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);
}
}
}
pub struct CursorInfo {
pub surface: WlWeak<WlSurface>,
pub surface: Option<WlWeak<WlSurface>>,
pub hotspot_x: i32,
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()?;
let cursor_size = self.surface.as_ref()?.upgrade().ok()?.get_size()?;
Some(Geometry {
origin: [self.hotspot_x, self.hotspot_y].into(),
size: cursor_size,
@@ -473,134 +84,224 @@ impl CursorInfo {
}
}
impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlSeat>,
data: &Arc<SeatData>,
data_init: &mut DataInit<'_, WaylandState>,
) {
let resource = data_init.init(resource, data.clone());
if resource.version() >= EVT_NAME_SINCE {
resource.name(nanoid!());
}
resource.capabilities(Capability::Pointer | Capability::Keyboard | Capability::Touch);
}
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
let Some(seat_client) = data.client.get().cloned() else {return false};
client.id() == seat_client
}
pub struct SeatWrapper {
wayland_state: Weak<Mutex<WaylandState>>,
cursor_info_tx: watch::Sender<CursorInfo>,
pub cursor_info_rx: watch::Receiver<CursorInfo>,
seat: Seat<WaylandState>,
touches: Mutex<FxHashMap<u32, WlWeak<WlSurface>>>,
}
impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlSeat,
request: wl_seat::Request,
data: &Arc<SeatData>,
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_seat::Request::GetPointer { id } => {
let pointer = data_init.init(id, data.clone());
let _ = data.pointer.set((pointer, Mutex::new(ObjectId::null())));
impl SeatWrapper {
pub fn new(wayland_state: Weak<Mutex<WaylandState>>, seat: Seat<WaylandState>) -> Self {
let (cursor_info_tx, cursor_info_rx) = watch::channel(CursorInfo {
surface: None,
hotspot_x: 0,
hotspot_y: 0,
});
SeatWrapper {
wayland_state,
cursor_info_tx,
cursor_info_rx,
seat,
touches: Mutex::new(FxHashMap::default()),
}
}
pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) {
let pointer = self.seat.get_pointer().unwrap();
if pointer.current_focus() == Some(surface.clone()) {
pointer.motion(
state,
None,
&MotionEvent {
location: (0.0, 0.0).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
)
}
let keyboard = self.seat.get_keyboard().unwrap();
if keyboard.current_focus() == Some(surface.clone()) {
keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial());
}
for (id, touch_surface) in self.touches.lock().iter() {
if touch_surface.id() == surface.id() {
self.touch_up(*id);
}
wl_seat::Request::GetKeyboard { id } => {
let keyboard = data_init.init(id, data.clone());
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null())));
}
wl_seat::Request::GetTouch { id } => {
let _ = data.touch.set(data_init.init(id, data.clone()));
}
wl_seat::Request::Release => (),
_ => unreachable!(),
}
}
}
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlPointer,
request: wl_pointer::Request,
seat_data: &Arc<SeatData>,
dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
pub fn pointer_motion(&self, surface: WlSurface, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.motion(
&mut state,
Some((surface, (0.0, 0.0).into())),
&MotionEvent {
location: (position.x as f64, position.y as f64).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
pointer.frame(&mut state);
}
pub fn pointer_button(&self, button: u32, pressed: bool) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.button(
&mut state,
&ButtonEvent {
button,
state: if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
pointer.frame(&mut state);
}
pub fn pointer_scroll(
&self,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
match request {
wl_pointer::Request::SetCursor {
serial: _,
surface,
hotspot_x,
hotspot_y,
} => {
if let Some(surface) = surface.as_ref() {
CoreSurface::add_to(dh.clone(), surface, || (), |_| ());
compositor::with_states(surface, |data| {
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1);
}
})
}
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.axis(
&mut state,
AxisFrame {
source: None,
relative_direction: (
AxisRelativeDirection::Identical,
AxisRelativeDirection::Identical,
),
time: 0,
axis: scroll_distance
.map(|d| (d.x as f64, d.y as f64))
.unwrap_or((0.0, 0.0)),
v120: scroll_steps.map(|d| ((d.x * 120.0) as i32, (d.y * 120.0) as i32)),
stop: (false, false),
},
);
pointer.frame(&mut state);
}
let Some((_, focus)) = seat_data.pointer.get() else {return};
let focus = focus.lock();
let surfaces = seat_data.surfaces.lock();
let Some(surface_info) = surfaces.get(&focus) else {return};
let cursor_info = surface.map(|surface| CursorInfo {
surface: surface.downgrade(),
hotspot_x,
hotspot_y,
});
let _ = surface_info.cursor_sender.send_replace(cursor_info);
}
wl_pointer::Request::Release => (),
_ => unreachable!(),
}
}
}
impl Dispatch<WlKeyboard, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
_data: &Arc<SeatData>,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_keyboard::Request::Release => (),
_ => unreachable!(),
}
}
}
impl Dispatch<WlTouch, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlTouch,
request: <WlTouch as Resource>::Request,
_data: &Arc<SeatData>,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_touch::Request::Release => (),
_ => unreachable!(),
pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec<i32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(keyboard) = self.seat.get_keyboard() else {
return;
};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()).cloned() else {
return;
};
keyboard.set_focus(
&mut state.lock(),
Some(surface),
SERIAL_COUNTER.next_serial(),
);
if keyboard
.set_keymap_from_string(&mut state.lock(), keymap)
.is_err()
{
return;
}
for key in keys {
keyboard.input(
&mut state.lock(),
key.unsigned_abs(),
if key > 0 {
KeyState::Pressed
} else {
KeyState::Released
},
SERIAL_COUNTER.next_serial(),
0,
|_, _, _| FilterResult::Forward::<()>,
);
}
}
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.down(
&mut state.lock(),
Some((surface, (0.0, 0.0).into())),
&DownEvent {
slot: Some(id).into(),
location: (position.x as f64, position.y as f64).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn touch_move(&self, id: u32, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(surface) = self.touches.lock().get(&id).and_then(|c| c.upgrade().ok()) else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.motion(
&mut state.lock(),
Some((surface, (0.0, 0.0).into())),
&touch::MotionEvent {
slot: Some(id).into(),
location: (position.x as f64, position.y as f64).into(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn touch_up(&self, id: u32) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.up(
&mut state.lock(),
&UpEvent {
slot: Some(id).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn reset_input(&self) {
for id in self.touches.lock().keys() {
self.touch_up(*id)
}
}
}

View File

@@ -1,5 +1,5 @@
use super::DisplayWrapper;
use crate::wayland::{drm::wl_drm::WlDrm, seat::SeatData};
use super::seat::SeatWrapper;
use crate::wayland::drm::wl_drm::WlDrm;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use smithay::{
@@ -9,16 +9,20 @@ use smithay::{
renderer::gles::GlesRenderer,
},
delegate_dmabuf, delegate_output, delegate_shm,
input::{keyboard::XkbConfig, SeatState},
output::{Mode, Output, Scale, Subpixel},
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
shell::server::xdg_wm_base::XdgWmBase,
shell::server::xdg_toplevel::WmCapabilities,
},
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
protocol::{
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
wl_output::WlOutput,
},
DisplayHandle,
},
},
@@ -29,27 +33,23 @@ use smithay::{
dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
},
shell::kde::decoration::KdeDecorationState,
output::OutputHandler,
shell::{
kde::decoration::KdeDecorationState,
xdg::{WmCapabilitySet, XdgShellState},
},
shm::{ShmHandler, ShmState},
},
};
use std::sync::{Arc, Weak};
use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn};
pub struct ClientState {
pub pid: Option<i32>,
pub id: OnceCell<ClientId>,
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());
}
pub seat: Arc<SeatWrapper>,
}
impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) {
@@ -66,9 +66,6 @@ impl ClientData for ClientState {
}
pub struct WaylandState {
pub weak_ref: Weak<Mutex<WaylandState>>,
pub display_handle: DisplayHandle,
pub compositor_state: CompositorState,
// pub xdg_activation_state: XdgActivationState,
pub kde_decoration_state: KdeDecorationState,
@@ -76,6 +73,9 @@ pub struct WaylandState {
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub drm_formats: Vec<Fourcc>,
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
pub seat_state: SeatState<Self>,
pub seat: Arc<SeatWrapper>,
pub xdg_shell: XdgShellState,
pub output: Output,
}
@@ -129,6 +129,12 @@ impl WaylandState {
(dmabuf_state, dmabuf_global, None)
};
let mut seat_state = SeatState::new();
let mut seat = seat_state.new_wl_seat(&display_handle, "seat0");
seat.add_pointer();
seat.add_keyboard(XkbConfig::default(), 200, 25).unwrap();
seat.add_touch();
let output = Output::new(
"1x".to_owned(),
smithay::output::PhysicalProperties {
@@ -140,7 +146,7 @@ impl WaylandState {
);
let _output_global = output.create_global::<Self>(&display_handle);
let mode = Mode {
size: (2048, 2048).into(),
size: (1024, 1024).into(),
refresh: 60000,
};
output.change_current_state(
@@ -150,8 +156,15 @@ impl WaylandState {
None,
);
output.set_preferred(mode);
let mut xdg_shell = XdgShellState::new::<Self>(&display_handle);
let mut capabilities = WmCapabilitySet::default();
capabilities.set(WmCapabilities::Maximize);
capabilities.set(WmCapabilities::Fullscreen);
capabilities.unset(WmCapabilities::Minimize);
capabilities.unset(WmCapabilities::WindowMenu);
xdg_shell.replace_capabilities(capabilities);
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
display_handle.create_global::<Self, WlDrm, _>(2, ());
@@ -159,9 +172,6 @@ impl WaylandState {
Arc::new_cyclic(|weak| {
Mutex::new(WaylandState {
weak_ref: weak.clone(),
display_handle,
compositor_state,
// xdg_activation_state,
kde_decoration_state,
@@ -169,6 +179,9 @@ impl WaylandState {
drm_formats,
dmabuf_state,
dmabuf_tx,
seat_state,
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
xdg_shell,
output,
})
})
@@ -201,6 +214,9 @@ impl DmabufHandler for WaylandState {
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap();
}
}
impl OutputHandler for WaylandState {
fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {}
}
delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState);
delegate_output!(WaylandState);

View File

@@ -1,34 +1,39 @@
use super::state::WaylandState;
use super::utils::WlSurfaceExt;
use crate::{
core::{delta::Delta, destroy_queue, registry::Registry},
nodes::drawable::{model::ModelPart, shaders::PANEL_SHADER_BYTES},
nodes::{
drawable::{
model::{MaterialWrapper, ModelPart},
shaders::PANEL_SHADER_BYTES,
},
items::camera::TexWrapper,
},
};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use smithay::{
backend::renderer::{
gles::{GlesRenderer, GlesTexture},
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
utils::{import_surface_tree, RendererSurfaceStateUserData},
Renderer, Texture,
},
desktop::utils::send_frames_surface_tree,
output::Output,
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, DisplayHandle, Resource},
wayland::compositor::{self, SurfaceData},
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, Resource},
};
use std::{cell::RefCell, ffi::c_void, sync::Arc, time::Duration};
use stereokit::{
Material, Shader, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample,
TextureType, Transparency,
use std::{ffi::c_void, sync::Arc, time::Duration};
use stereokit_rust::{
material::{Material, Transparency},
shader::Shader,
tex::{Tex, TexAddress, TexFormat, TexSample, TexType},
util::Time,
};
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
pub struct CoreSurfaceData {
wl_tex: Option<SendWrapper<GlesTexture>>,
pub size: Vector2<u32>,
}
impl Drop for CoreSurfaceData {
fn drop(&mut self) {
@@ -37,139 +42,133 @@ impl Drop for CoreSurfaceData {
}
pub struct CoreSurface {
pub dh: DisplayHandle,
pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<Material>>,
sk_tex: OnceCell<Mutex<TexWrapper>>,
sk_mat: OnceCell<Mutex<MaterialWrapper>>,
material_offset: Mutex<Delta<u32>>,
on_mapped: Box<dyn Fn() + Send + Sync>,
on_commit: Box<dyn Fn(u32) + Send + Sync>,
pub pending_material_applications: Registry<ModelPart>,
}
impl CoreSurface {
pub fn add_to(
dh: DisplayHandle,
surface: &WlSurface,
on_mapped: impl Fn() + Send + Sync + 'static,
on_commit: impl Fn(u32) + Send + Sync + 'static,
) {
compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| {
CORE_SURFACES.add(CoreSurface {
dh,
weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
material_offset: Mutex::new(Delta::new(0)),
on_mapped: Box::new(on_mapped) as Box<dyn Fn() + Send + Sync>,
on_commit: Box::new(on_commit) as Box<dyn Fn(u32) + Send + Sync>,
pending_material_applications: Registry::new(),
})
});
pub fn add_to(surface: &WlSurface) {
let core_surface = CORE_SURFACES.add(CoreSurface {
weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
material_offset: Mutex::new(Delta::new(0)),
pending_material_applications: Registry::new(),
});
}
pub fn commit(&self, count: u32) {
(self.on_commit)(count);
surface.insert_data(core_surface);
}
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
compositor::with_states(surf, |data| {
data.data_map.get::<Arc<CoreSurface>>().cloned()
})
surf.get_data()
}
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {return};
pub fn process(&self, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let sk_tex = self
.sk_tex
.get_or_init(|| sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32));
let sk_tex = self.sk_tex.get_or_init(|| {
Mutex::new(TexWrapper(Tex::new(
TexType::ImageNomips,
TexFormat::RGBA32Linear,
nanoid::nanoid!(),
)))
});
self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES);
let shader = Shader::from_memory(PANEL_SHADER_BYTES).unwrap();
// let _ = renderer.with_context(|c| unsafe {
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
// });
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_transparency(&mat, Transparency::Blend);
Arc::new(mat)
let mut mat = Material::new(shader, None);
mat.diffuse_tex(&sk_tex.lock().0);
mat.transparency(Transparency::Blend);
Mutex::new(MaterialWrapper(mat))
});
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
on_commit_buffer_handler::<WaylandState>(&wl_surface);
// Import all surface buffers into textures
if import_surface_tree(renderer, &wl_surface).is_err() {
return;
}
let mapped = compositor::with_states(&wl_surface, |data| {
data.data_map
.get::<RendererSurfaceStateUserData>()
.map(|surface_states| surface_states.borrow().buffer().is_some())
.unwrap_or(false)
});
self.update_textures(renderer);
self.apply_surface_materials();
}
pub fn update_textures(&self, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let mapped = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states.lock().unwrap().buffer().is_some()
})
.unwrap_or(false);
if !mapped {
return;
}
let mut mapped_data = self.mapped_data.lock();
let just_mapped = mapped_data.is_none();
self.with_states(|data| {
let Some(renderer_surface_state) = data
.data_map
.get::<RendererSurfaceStateUserData>()
.map(RefCell::borrow) else {return};
let Some(smithay_tex) = renderer_surface_state
.texture::<GlesRenderer>(renderer.id())
.cloned() else {return};
let Some(sk_tex) = self.sk_tex.get() else {return};
let Some(sk_mat) = self.sk_mat.get() else {return};
unsafe {
sk.tex_set_surface(
sk_tex.as_ref(),
smithay_tex.tex_id() as usize as *mut c_void,
TextureType::IMAGE_NO_MIPS,
smithay::backend::renderer::gles::ffi::RGBA8.into(),
smithay_tex.width() as i32,
smithay_tex.height() as i32,
1,
false,
);
sk.tex_set_sample(sk_tex.as_ref(), TextureSample::Point);
sk.tex_set_address(sk_tex.as_ref(), TextureAddress::Clamp);
}
if let Some(material_offset) = self.material_offset.lock().delta() {
sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32);
}
let Some(smithay_tex) = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states
.lock()
.unwrap()
.texture::<GlesRenderer>(renderer.id())
.cloned()
})
.flatten()
else {
return;
};
let Some(surface_size) = renderer_surface_state.surface_size() else {return};
let new_mapped_data = CoreSurfaceData {
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
wl_tex: Some(SendWrapper::new(smithay_tex)),
};
*mapped_data = Some(new_mapped_data);
});
drop(mapped_data);
if just_mapped {
(self.on_mapped)();
let Some(sk_tex) = self.sk_tex.get() else {
return;
};
let Some(sk_mat) = self.sk_mat.get() else {
return;
};
sk_tex
.lock()
.0
.set_native_surface(
smithay_tex.tex_id() as usize as *mut c_void,
TexType::ImageNomips,
smithay::backend::renderer::gles::ffi::RGBA8.into(),
smithay_tex.width() as i32,
smithay_tex.height() as i32,
1,
false,
)
.sample_mode(TexSample::Point)
.address_mode(TexAddress::Clamp);
if let Some(material_offset) = self.material_offset.lock().delta() {
sk_mat.lock().0.queue_offset(*material_offset as i32);
}
self.apply_surface_materials();
let new_mapped_data = CoreSurfaceData {
wl_tex: Some(SendWrapper::new(smithay_tex)),
};
*mapped_data = Some(new_mapped_data);
}
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
let Some(wl_surface) = self.wl_surface() else {return};
pub fn frame(&self, output: Output) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
send_frames_surface_tree(
&wl_surface,
&output,
Duration::from_secs_f64(sk.time_get()),
Duration::from_secs_f64(Time::get_total_unscaled()),
None,
|_, _| Some(output.clone()),
);
@@ -185,8 +184,9 @@ impl CoreSurface {
fn apply_surface_materials(&self) {
if let Some(sk_mat) = self.sk_mat.get() {
let sk_mat = sk_mat.lock();
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(sk_mat.clone());
model_node.replace_material_now(&sk_mat.0);
}
self.pending_material_applications.clear();
}
@@ -195,18 +195,6 @@ impl CoreSurface {
pub fn wl_surface(&self) -> Option<WlSurface> {
self.weak_surface.upgrade().ok()
}
pub fn with_states<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&SurfaceData) -> T,
{
self.wl_surface()
.map(|wl_surface| compositor::with_states(&wl_surface, f))
}
pub fn size(&self) -> Option<Vector2<u32>> {
self.mapped_data.lock().as_ref().map(|d| d.size)
}
}
impl Drop for CoreSurface {
fn drop(&mut self) {

121
src/wayland/utils.rs Normal file
View File

@@ -0,0 +1,121 @@
use mint::Vector2;
use parking_lot::Mutex;
use smithay::{
backend::renderer::utils::RendererSurfaceStateUserData,
reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::{
compositor,
shell::xdg::{SurfaceCachedState, XdgToplevelSurfaceData},
},
};
use crate::nodes::items::panel::{ChildInfo, Geometry, ToplevelInfo};
use super::xdg_shell::surface_panel_item;
pub trait WlSurfaceExt {
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool;
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T>;
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O>;
fn get_current_surface_state(&self) -> SurfaceCachedState;
fn get_pending_surface_state(&self) -> SurfaceCachedState;
fn get_size(&self) -> Option<Vector2<u32>>;
fn get_geometry(&self) -> Option<Geometry>;
}
impl WlSurfaceExt for WlSurface {
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool {
compositor::with_states(self, |d| {
d.data_map.insert_if_missing_threadsafe(move || data)
})
}
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T> {
compositor::with_states(self, |d| d.data_map.get::<T>().cloned())
}
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O> {
compositor::with_states(self, |d| Some((f)(d.data_map.get::<T>()?)))
}
fn get_current_surface_state(&self) -> SurfaceCachedState {
compositor::with_states(self, |states| {
*states.cached_state.get::<SurfaceCachedState>().current()
})
}
fn get_pending_surface_state(&self) -> SurfaceCachedState {
compositor::with_states(self, |states| {
*states.cached_state.get::<SurfaceCachedState>().pending()
})
}
fn get_size(&self) -> Option<Vector2<u32>> {
self.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states.lock().unwrap().surface_size()
})
.flatten()
.map(|size| Vector2::from([size.w as u32, size.h as u32]))
}
fn get_geometry(&self) -> Option<Geometry> {
self.get_current_surface_state().geometry.map(|r| r.into())
}
}
pub trait ToplevelInfoExt {
fn get_toplevel_info(&self) -> Option<ToplevelInfo>;
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O>;
fn get_parent(&self) -> Option<u64>;
fn get_app_id(&self) -> Option<String>;
fn get_title(&self) -> Option<String>;
fn min_size(&self) -> Option<Vector2<u32>>;
fn max_size(&self) -> Option<Vector2<u32>>;
}
impl ToplevelInfoExt for WlSurface {
fn get_toplevel_info(&self) -> Option<ToplevelInfo> {
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|c| c.lock().clone())
}
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O> {
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|r| (f)(&mut r.lock()))
}
fn get_parent(&self) -> Option<u64> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().unwrap().parent.clone())
.flatten()
.and_then(|p| surface_panel_item(&p))
.and_then(|p| p.node.upgrade())
.map(|p| p.get_id())
}
fn get_app_id(&self) -> Option<String> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.app_id.clone())
.flatten()
}
fn get_title(&self) -> Option<String> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.title.clone())
.flatten()
}
fn min_size(&self) -> Option<Vector2<u32>> {
let state = self.get_pending_surface_state();
let size = state.min_size;
if size.w == 0 && size.h == 0 {
None
} else {
Some(Vector2::from([size.w as u32, size.h as u32]))
}
}
fn max_size(&self) -> Option<Vector2<u32>> {
let state = self.get_pending_surface_state();
let size = state.max_size;
if size.w == 0 && size.h == 0 {
None
} else {
Some(Vector2::from([size.w as u32, size.h as u32]))
}
}
}
pub trait ChildInfoExt {
fn get_child_info(&self) -> Option<ChildInfo>;
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O>;
}
impl ChildInfoExt for WlSurface {
fn get_child_info(&self) -> Option<ChildInfo> {
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|c| c.lock().clone())
}
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O> {
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|r| (f)(&mut r.lock()))
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,267 +0,0 @@
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

@@ -1,430 +0,0 @@
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()
}
}