121 Commits
0.44.0 ... main

Author SHA1 Message Date
Cyberneticmelon
3e31905b5b feat: better server docs
Replaced embedded youtube with gifs

Replaced embedded videos with GIFs

Workflow img

Workflow img

Updated youtube links with thumbnails

Updated text
2025-04-01 14:23:33 -07:00
Nova
c830becbff fix(packaging): use workspace deps 2024-11-01 15:10:00 -04:00
Nova
e8e485b472 fix(packaging): even more shenanigans 2024-11-01 12:31:44 -04:00
Nova
bf68b87813 fix(packaging): don't do dev in core 2024-11-01 12:10:45 -04:00
Nova
9546a36200 fix(packaging): update stereokit 2024-09-15 12:27:10 -04:00
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
83 changed files with 7007 additions and 7192 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
/libs/ /libs/
*.AppImage *.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",
# ]

2573
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
edition = "2021" edition = "2021"
rust-version = "1.75" rust-version = "1.75"
name = "stardust-xr-server" name = "stardust-xr-server"
version = "0.44.0" version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"] authors = ["Nova King <technobaboo@proton.me>"]
description = "Stardust XR reference display server" description = "Stardust XR reference display server"
license = "GPLv2" license = "GPLv2"
@@ -12,17 +12,19 @@ homepage = "https://stardustxr.org"
[workspace] [workspace]
members = ["codegen"] members = ["codegen"]
[workspace.dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
[[bin]] [[bin]]
name = "stardust-xr-server" name = "stardust-xr-server"
path = "src/main.rs" path = "src/main.rs"
[features] [features]
default = ["wayland"] default = ["wayland"]
wayland = ["dep:smithay", "dep:xkbcommon"] wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
xwayland_rootful = []
xwayland_rootless = ["smithay/xwayland"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-tracy"] profile_app = ["dep:tracing-tracy"]
local_deps = ["stereokit-rust/force-local-deps"]
[package.metadata.appimage] [package.metadata.appimage]
auto_link = true auto_link = true
@@ -37,77 +39,81 @@ auto_link_exclude_list = [
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
debug = true
strip = "none"
debug-assertions = true
overflow-checks = true
[profile.release] [profile.release]
strip = true opt-level = 3
lto = true debug = "line-tables-only"
strip = "none"
debug-assertions = true
overflow-checks = false
[dependencies] [dependencies]
color-eyre = { version = "0.6.2", default-features = false } # small utility thingys
clap = { version = "4.2.4", features = ["derive"] } once_cell = "1.19.0"
glam = { version = "0.23.0", features = ["mint"] }
lazy_static = "1.4.0"
mint = "0.5.9"
nanoid = "0.4.0" nanoid = "0.4.0"
once_cell = "1.17.1" lazy_static = "1.5.0"
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"
rand = "0.8.5" rand = "0.8.5"
atty = "0.2.14" rustc-hash = "2.0.0"
xkbcommon = { version = "0.7.0", default-features = false, optional = true } portable-atomic = { version = "1.7.0", features = ["float", "std"] }
ctrlc = "3.4.1" send_wrapper = "0.6.0"
libc = "0.2.148" slotmap = "1.0.7"
input-event-codes = "5.16.8" global_counter = "=0.2.2"
nix = "0.27.1" parking_lot = "0.12.3"
wayland-scanner = "0.31.0"
wayland-backend = "0.3.2" # rust errors/logging
cluFlock = "1.2.7" color-eyre = { version = "0.6.3", default-features = false }
fxtypemap = "0.2.0" 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.29.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] [dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures # git = "https://github.com/technobaboo/smithay.git"
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures # git = "https://github.com/colinmarc/smithay.git"
git = "https://github.com/smithay/smithay.git"
# path = "../smithay" # path = "../smithay"
default-features = false default-features = false
features = [ features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
"desktop",
"backend_drm",
"backend_egl",
"renderer_gl",
"wayland_frontend",
]
version = "*"
optional = true 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 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] [dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git" workspace = true
[dependencies.stardust-xr-server-codegen] [dependencies.stardust-xr-server-codegen]
path = "codegen" path = "codegen"
# [patch.crates-io.stereokit]
# path = "../stereokit-rs"
# [patch.crates-io.stereokit-sys]
# path = "../stereokit-sys"

203
README.md
View File

@@ -1,85 +1,164 @@
# Stardust XR Reference Server # Stardust XR Server
This project is a usable Linux display server that reinvents human-computer interaction for all kinds of XR, from putting 2D/XR apps into various 3D shells for varying uses to SDF-based interaction. Stardust XR is a display server for VR and AR headsets on Linux-based systems. [Stardust provides a 3D environment](https://www.youtube.com/watch?v=v2WblwbaLaA), where anything from 2D windows (including your existing apps!), to 3D apps built from objects, can exist together in physical space.
## Prerequisites ![workflow](/img/workflow.png)
1. Cargo
2. CMake
3. EGL+GLES 3.2
4. GLX+Xlib
5. fontconfig
6. dlopen
7. OpenXR Loader (required even if run in flatscreen mode)
## Build ## Core Dependencies
| **Dependency** | **Ubuntu/Debian** | **Arch Linux** | **Fedora** |
|-----------------------------|-------------------------------------------------------------------------------------------------|---------------------------------------------------|-------------------------------------------------------------|
| **Cargo** | `cargo` | `cargo` | `cargo` |
| **CMake** | `cmake` | `cmake` | `cmake` |
| **EGL+GLES 3.2** | `libegl1-mesa-dev`, `libgles2-mesa-dev` | `mesa` *(provides EGL/GLES libraries and headers)* | `mesa-libEGL-devel`, `mesa-libGLES-devel` |
| **GLX+Xlib** | `libx11-dev`, `libxfixes-dev`, `libxcb1-dev`, `libgl1-mesa-dev`, `libxkbcommon-dev` | `libx11`, `libxfixes`, `libxcb` *(and GLX via mesa)*| `libX11-devel`, `libXfixes-devel`, `libxcb-devel`, `mesa-libGL-devel` *(or equivalent)* |
| **fontconfig** | `libfontconfig1-dev` | `fontconfig` | `fontconfig-devel` |
| **dlopen** (glibc function) | Provided by `libc6-dev` (part of the core C library) | Provided by `glibc` *(included in base-devel)* | Provided by `glibc-devel` |
| **OpenXR Loader** | `libopenxr-loader1`, `libopenxr-dev`, `libopenxr1-monado` | `openxr` | `openxr-devel` |
Command line installation of core & dynamic dependencies are provided below:
<details>
<summary>Ubuntu/Debian</summary>
<pre><code class="language-bash">
sudo apt-get update && sudo apt-get install -y \
build-essential \
cargo \
cmake \
libegl1-mesa-dev libgles2-mesa-dev \
libx11-dev libxfixes-dev libxcb1-dev libxau-dev libgl1-mesa-dev libxkbcommon-dev \
libfontconfig1-dev libfreetype6-dev libharfbuzz-dev libgraphite2-dev \
libc6-dev \
libopenxr-loader1 libopenxr-dev libopenxr1-monado libwayland-dev \
libjsoncpp-dev libdrm-dev libexpat1-dev libxcb-randr0-dev \
libxml2-dev libffi-dev libbz2-dev libpng-dev libbrotli-dev liblzma-dev libglib2.0-dev libpcre2-dev
</code></pre>
</details>
<details>
<summary>Arch Linux</summary>
<pre><code class="language-bash">
sudo pacman -Syu --needed \
base-devel \
rust \
cmake \
mesa \
libx11 \
libxfixes \
libxcb \
libxkbcommon \
fontconfig \
freetype2 \
openxr \
jsoncpp \
libffi \
wayland \
expat \
libxml2 \
libxau \
bzip2 \
xz \
libpng \
brotli \
pcre2 \
glib2 \
libdrm
</code></pre>
</details>
<details>
<summary>Fedora</summary>
<pre><code class="language-bash">
sudo dnf group install development-tools && \
sudo dnf install -y \
cargo \
cmake \
mesa-libEGL-devel \
mesa-libGLES-devel \
libX11-devel \
libXfixes-devel \
libxcb-devel \
libxkbcommon-devel \
fontconfig-devel \
freetype-devel \
harfbuzz-devel \
graphite2-devel \
openxr-devel \
wayland-devel \
jsoncpp-devel \
libdrm-devel \
expat-devel \
xcb-util-devel \
libxml2-devel \
libXau-devel \
bzip2-devel \
xz-devel \
libpng-devel \
brotli-devel \
pcre2-devel \
glib2-devel
</code></pre>
</details>
## Installation
More detailed instructions and walkthroughs are provided at https://www.stardustxr.org
The [Terra Repository](https://terra.fyralabs.com/) is required, and comes pre-installed with [Ultramarine Linux](https://ultramarine-linux.org/). Other Fedora Editions and derivatives can directly install terra-release:
```bash
sudo dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release
```
For a full installation of the Stardust XR server *and* a selected group of clients, run:
```bash
sudo dnf group install stardust-xr
```
## Manual Build
We've provided a manual installation script [here](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh) that clones and builds the Stardust XR server along with a number of other clients from their respective repositories, and provides a startup script for automatically launching some clients.
After cloning the repository
```bash ```bash
cargo build cargo build
``` ```
The latest stable server is automatically built to an appimage at https://github.com/StardustXR/server/releases for easy testing.
## Usage ## Usage
> [!NOTE]
> For help with setting up an XR headset on linux, visit https://stardustxr.org/docs/get-started/setup-openxr
First, try running `cargo run` in a terminal window. If a headset is plugged in and OpenXR is working no window will show up. However, the headset should show the same things as the window that opens:
![A pitch black void with a single bleach white hand in the middle](/img/xr_mode_windowed_blank.png) The **Stardust XR Server** is a server that runs clients, so without any running, you will see a black screen. If you only have the server installed, we recommend also cloning and building the following clients to start: [Flatland](https://github.com/StardustXR/flatland), which allows normal 2D apps to run in Stardust, [Protostar](https://github.com/StardustXR/protostar), which contains Hexagon Launcher, an app launcher menu, and [Black Hole](https://github.com/StardustXR/black-hole) to quickly tuck away your objects and apps (kind of like desktop peek on Windows).
Stardust won't do anything interesting without clients! Try some from https://github.com/StardustXR. First, try running `cargo run -- -f` in a terminal window to check out flatscreen mode, (or `stardust-xr-server -f` / `stardust-xr-server_dev -f` if you installed via dnf or the manual installation script, respectively, as they provide symlinks.)
### Default Sky If there aren't already any clients running, you'll need to manually launch them by either navigating to their repositories and running `cargo run`, or running them via their names if you installed via dnf or the manual installation script, such as `flatland`, `hexagon_launcher`, etc.
You can set a default skytex/skylight by putting your favorite HDRI equirectangular sky in `~/.config/stardust/skytex.hdr`. Certain clients can override this. > [!IMPORTANT]
> [Flatland](https://github.com/StardustXR/flatland) must be running for 2D apps to launch.
Flatscreen mode when the default skybox is [Zhengyang Gate](https://polyhaven.com/a/zhengyang_gate): ### Startup Script
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_3.png) A startup script can be created at `~/.config/stardust/startup` that will launch specified settings and clients/applications, an example of which is shown [here](https://github.com/cyberneticmelon/usefulscripts/blob/main/startup). If you used the [installation script](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh), one will have already been made for you. This allows wide flexibility of what clients to launch upon startup (and, for example, *where*, using the [Gravity](https://github.com/StardustXR/gravity) client to specify X Y and Z co-ordinates).
### Windowed Mode ### Flatscreen Navigation
A video guide showcasing flatscreen controls is available [here](https://www.youtube.com/watch?v=JCYecSlKlDI)
If the stardust server can't connect to an OpenXR runtime or you force it into flatscreen mode with `-f`, the server will show in a window. To move around, hold down `Shift + W A S D`, with `Q` for moving down and `E` for moving up.
![A black void representing Stardust in XR mode with a hand skeleton in the middle](/img/flatscreen_2.png) ![wasd](https://github.com/StardustXR/website/blob/main/static/img/updated_flat_wasd.GIF)
You can navigate around by right click + dragging to look around, Shift+W/A/S/D/Q/E to move. If you have a virtual hand, left click pinches, right click points, both make a fist. To look around, hold down `Shift + Right` Click while moving the mouse.
![updated_look](https://github.com/StardustXR/website/blob/main/static/img/updated_flat_look.GIF)
### Flags To drag applications out of the app launcher, hold down `Shift + ~`
#### Flatscreen (-f) ![updated_drag](https://github.com/StardustXR/website/blob/main/static/img/updated_flat_drag.GIF)
The server will show up in windowed mode no matter what with your mouse pointer being turned into a 3D pointer. Keyboard input will be sent to whatever your mouse is hovering over like visionOS simulator. ### XR Navigation
Flatscreen mode upon initial startup: A video guide showcasing XR controls is available [here](https://www.youtube.com/watch?v=RbxFq6JjliA)
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_1.png)
#### Overlay (-o \<PRIORITY>) **Quest 3 Hand tracking**:
Pinch to drag and drop, grasp with full hand for grabbing, point and click with pointer finger to click or pinch from a distance
The server will, if in XR mode, be overlaid using the OpenXR overlay extension with the given priority. ![hand_pinching](https://github.com/StardustXR/website/blob/main/static/img/hand_pinching.GIF)
#### Disable controller (--disable-controller) **Quest 3 Controller**:
Grab with the grip buttons, click by touching the tip of the cones or by using the trigger from a distance
Some runtimes such as Monado may emulate a controller using a hand, and this messes with Stardust's input system. Set this flag to ignore the controllers that the OpenXR runtime provides. ![controller_click](https://github.com/StardustXR/website/blob/main/static/img/controller_click.GIF)
#### Execute (-e </path/to/executable>)
When wayland and OpenXR and such are initialized, run the given executable (such as a bash script) with all the environment variables needed to connect all clients of any type to the server. If not set, the server will run the executable at `~/.config/stardust/startup` if it exists. This is how stardust desktop environments can be made.
#### Help (-h, --help)
help
## Test
##### Gnome Graphical Integration Test
- `nix build .#gnome-graphical-test`
This test uses Nix to reproducibly execute a QEMU virtual machine which
spawns a full Gnome desktop. It runs `monado-service`, `stardust-xr-server`
`flatland` underneath of Gnome and then attaches `weston-cliptest` to the
`flatland` process running underneath of `stardust-xr-server`, the result is
a screenshot in PNG format that should look like expected. If any process in
this test produces an exit code above 0, the test will fail, graphical bugs
should be visible in the screenshot. An example of the result is below.
###### Result
![image](https://github.com/StardustXR/server/assets/26458780/e21cd039-2528-4568-b20a-ce4abfab6d9b)
##### Everything
`nix flake check` will build every test underneath of the `checks` attribute in the `flake.nix`

View File

@@ -13,5 +13,4 @@ mint = "0.5.9"
proc-macro2 = "1.0.71" proc-macro2 = "1.0.71"
split-iter = "0.1.0" split-iter = "0.1.0"
[dependencies.stardust-xr-schemas] stardust-xr = { workspace = true }
git = "https://github.com/StardustXR/core.git"

View File

@@ -2,16 +2,16 @@ use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use split_iter::Splittable; use split_iter::Splittable;
use stardust_xr_schemas::protocol::*; use stardust_xr::schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream { fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
quote!(#a #b) quote!(#a #b)
} }
// #[proc_macro] #[proc_macro]
// pub fn codegen_root_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn codegen_root_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(ROOT_PROTOCOL) codegen_protocol(ROOT_PROTOCOL)
// } }
#[proc_macro] #[proc_macro]
pub fn codegen_node_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn codegen_node_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(NODE_PROTOCOL) 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 { pub fn codegen_drawable_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DRAWABLE_PROTOCOL) codegen_protocol(DRAWABLE_PROTOCOL)
} }
// #[proc_macro] #[proc_macro]
// pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(INPUT_PROTOCOL) 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 { fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let protocol = Protocol::parse(protocol).unwrap(); let protocol = Protocol::parse(protocol).unwrap();
let interface = protocol let interface = protocol
.interface .interface
.map(|p| { .map(|p| {
let virtual_aspect_name = p.path[1..] let node_id = p.node_id;
.split('/') let node_id = quote! {
.map(ToString::to_string) const INTERFACE_NODE_ID: u64 = #node_id;
.reduce(|a, b| format!("{a}_{b}")) };
.unwrap_or_default() let aspect = generate_aspect(&Aspect {
+ "_interface"; name: "interface".to_string(),
generate_aspect(&Aspect {
name: virtual_aspect_name,
description: protocol.description.clone(), description: protocol.description.clone(),
inherits: vec![],
members: p.members, members: p.members,
}) });
quote!(#node_id #aspect)
}) })
.unwrap_or_default(); .unwrap_or_default();
let custom_enums = protocol let custom_enums = protocol
@@ -83,12 +95,6 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
.map(generate_aspect) .map(generate_aspect)
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .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() 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 { fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description; let description = &aspect.description;
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server); 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) .map(generate_member)
.reduce(fold_tokens) .reduce(fold_tokens)
.map(|t| { .map(|t| {
// TODO: properly import all dependencies
quote! { quote! {
pub mod #client_mod_name { pub mod #client_mod_name {
use super::*;
#t #t
} }
} }
@@ -225,6 +194,30 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)), &format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(), 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 let server_side_members = server_members
.map(generate_member) .map(generate_member)
.reduce(fold_tokens) .reduce(fold_tokens)
@@ -250,11 +243,58 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
#server_side_members #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 { 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 name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
let description = &member.description; let description = &member.description;
@@ -282,15 +322,15 @@ fn generate_member(member: &Member) -> TokenStream {
let return_type = member let return_type = member
.return_type .return_type
.as_ref() .as_ref()
.map(|r| generate_argument_type(&r, false, true)) .map(|r| generate_argument_type(r, false, true))
.unwrap_or_else(|| quote!(())); .unwrap_or_else(|| quote!(()));
match (side, _type) { match (side, _type) {
(Side::Client, MemberType::Method) => { (Side::Client, MemberType::Method) => {
quote! { quote! {
#[doc = #description] #[doc = #description]
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<#return_type> { pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
_node.execute_remote_method(#name_str, &(#argument_uses)).await _node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
} }
} }
} }
@@ -299,7 +339,7 @@ fn generate_member(member: &Member) -> TokenStream {
#[doc = #description] #[doc = #description]
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> { pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?; 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) => { (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! { quote! {
#prefix
#[doc = #description] #[doc = #description]
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>; fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
} }
@@ -334,8 +358,8 @@ fn generate_member(member: &Member) -> TokenStream {
} }
} }
fn generate_handler(member: &Member) -> TokenStream { fn generate_handler(member: &Member) -> TokenStream {
let member_name = &member.name; let opcode = member.opcode;
let member_name_ident = Ident::new(&member_name, Span::call_site()); let member_name_ident = Ident::new(&member.name, Span::call_site());
let argument_names = member let argument_names = member
.arguments .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())?;) quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
}) })
.unwrap_or_default(); .unwrap_or_default();
let serialize = generate_argument_serialize(
"result",
&member.return_type.clone().unwrap_or(ArgumentType::Empty),
false,
);
let argument_uses = member let argument_uses = member
.arguments .arguments
.iter() .iter()
@@ -366,16 +395,17 @@ fn generate_handler(member: &Member) -> TokenStream {
.unwrap_or_default(); .unwrap_or_default();
match member._type { match member._type {
MemberType::Signal => quote! { MemberType::Signal => quote! {
node.add_local_signal(#member_name, |_node, _calling_client, _message| { node.add_local_signal(#opcode, |_node, _calling_client, _message| {
#deserialize #deserialize
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses) Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
}); });
}, },
MemberType::Method => quote! { 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 { _method_response.wrap_async(async move {
#deserialize #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 { fn convert_deserializeable_argument_type(argument_type: &ArgumentType) -> ArgumentType {
match argument_type { 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(), f => f.clone(),
} }
} }
@@ -397,12 +433,18 @@ fn generate_argument_deserialize(
optional: bool, optional: bool,
) -> TokenStream { ) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site()); 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 { 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::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
ArgumentType::Vec(v) => { ArgumentType::Vec(v) => {
let mapping = generate_argument_deserialize("a", v, false); let mapping = generate_argument_deserialize("a", v, false);
@@ -424,10 +466,10 @@ fn generate_argument_serialize(
match argument_type { match argument_type {
ArgumentType::Node { ArgumentType::Node {
_type, _type,
return_info: _, return_id_parameter_name: _,
} => match optional { } => match optional {
true => quote!(#name.map(|n| n.get_path())), true => quote!(#name.map(|n| n.get_id())),
false => quote!(#name.get_path()), false => quote!(#name.get_id()),
}, },
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]), ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
ArgumentType::Vec(v) => { 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 { fn argument_type_option_name(argument_type: &ArgumentType) -> String {
match argument_type { match argument_type {
ArgumentType::Empty => "Empty".to_string(),
ArgumentType::Bool => "Bool".to_string(), ArgumentType::Bool => "Bool".to_string(),
ArgumentType::Int => "Int".to_string(), ArgumentType::Int => "Int".to_string(),
ArgumentType::UInt => "UInt".to_string(), ArgumentType::UInt => "UInt".to_string(),
ArgumentType::Float => "Float".to_string(), ArgumentType::Float => "Float".to_string(),
ArgumentType::Vec2 => "Vec2".to_string(), ArgumentType::Vec2(_) => "Vec2".to_string(),
ArgumentType::Vec3 => "Vec3".to_string(), ArgumentType::Vec3(_) => "Vec3".to_string(),
ArgumentType::Quat => "Quat".to_string(), ArgumentType::Quat => "Quat".to_string(),
ArgumentType::Mat4 => "Mat4".to_string(),
ArgumentType::Color => "Color".to_string(), ArgumentType::Color => "Color".to_string(),
ArgumentType::String => "String".to_string(), ArgumentType::String => "String".to_string(),
ArgumentType::Bytes => "Bytes".to_string(), ArgumentType::Bytes => "Bytes".to_string(),
ArgumentType::Vec(v) => format!("{}Vector", argument_type_option_name(&v)), ArgumentType::Vec(v) => format!("{}Vector", argument_type_option_name(v)),
ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(&m)), ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(m)),
ArgumentType::NodeID => "Node ID".to_string(),
ArgumentType::Datamap => "Datamap".to_string(), ArgumentType::Datamap => "Datamap".to_string(),
ArgumentType::ResourceID => "ResourceID".to_string(), ArgumentType::ResourceID => "ResourceID".to_string(),
ArgumentType::Enum(e) => e.clone(), ArgumentType::Enum(e) => e.clone(),
@@ -474,13 +519,21 @@ fn generate_argument_type(
owned: bool, owned: bool,
) -> TokenStream { ) -> TokenStream {
let _type = match argument_type { let _type = match argument_type {
ArgumentType::Empty => quote!(()),
ArgumentType::Bool => quote!(bool), ArgumentType::Bool => quote!(bool),
ArgumentType::Int => quote!(i32), ArgumentType::Int => quote!(i32),
ArgumentType::UInt => quote!(u32), ArgumentType::UInt => quote!(u32),
ArgumentType::Float => quote!(f32), ArgumentType::Float => quote!(f32),
ArgumentType::Vec2 => quote!(mint::Vector2<f32>), ArgumentType::Vec2(t) => {
ArgumentType::Vec3 => quote!(mint::Vector3<f32>), let t = generate_argument_type(t, false, true);
ArgumentType::Quat => quote!(mint::Quaternion<f32>), 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::Color => quote!(stardust_xr::values::Color),
ArgumentType::Bytes => { ArgumentType::Bytes => {
if !owned { if !owned {
@@ -497,7 +550,7 @@ fn generate_argument_type(
} }
} }
ArgumentType::Vec(t) => { ArgumentType::Vec(t) => {
let t = generate_argument_type(&t, false, true); let t = generate_argument_type(t, false, true);
if !owned { if !owned {
quote!(&[#t]) quote!(&[#t])
} else { } else {
@@ -505,14 +558,15 @@ fn generate_argument_type(
} }
} }
ArgumentType::Map(t) => { ArgumentType::Map(t) => {
let t = generate_argument_type(&t, false, true); let t = generate_argument_type(t, false, true);
if !owned { if !owned {
quote!(&rustc_hash::FxHashMap<String, #t>) quote!(&stardust_xr::values::Map<String, #t>)
} else { } else {
quote!(rustc_hash::FxHashMap<String, #t>) quote!(stardust_xr::values::Map<String, #t>)
} }
} }
ArgumentType::NodeID => quote!(u64),
ArgumentType::Datamap => { ArgumentType::Datamap => {
if !owned { if !owned {
quote!(&stardust_xr::values::Datamap) quote!(&stardust_xr::values::Datamap)
@@ -545,7 +599,7 @@ fn generate_argument_type(
} }
ArgumentType::Node { ArgumentType::Node {
_type, _type,
return_info: _, return_id_parameter_name: _,
} => { } => {
if !owned { if !owned {
quote!(&std::sync::Arc<crate::nodes::Node>) quote!(&std::sync::Arc<crate::nodes::Node>)

200
flake.lock generated
View File

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

View File

@@ -1,7 +1,9 @@
{ {
nixConfig = { nixConfig = {
extra-substituters = [ "https://stardustxr.cachix.org" ]; extra-substituters = [ "https://stardustxr.cachix.org" ];
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ]; extra-trusted-public-keys = [
"stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo="
];
}; };
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
@@ -12,37 +14,44 @@
# it to create VM Tests # it to create VM Tests
flatland.url = "github:StardustXR/flatland"; 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 let
name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
src = builtins.path { src = builtins.path {
name = "${name}-source"; name = "${name}-source";
path = toString ./.; path = toString ./.;
filter = path: type: filter = path: type:
nixpkgs.lib.all nixpkgs.lib.all (n: builtins.baseNameOf path != n) [
(n: builtins.baseNameOf path != n)
[
"flake.nix" "flake.nix"
"flake.lock" "flake.lock"
"nix" "nix"
"README.md" "README.md"
]; ];
}; };
in in flake-parts.lib.mkFlake { inherit inputs; } {
flake-parts.lib.mkFlake { inherit inputs; } { imports = [ flake-parts.flakeModules.easyOverlay ];
imports = [
flake-parts.flakeModules.easyOverlay
];
systems = [ "aarch64-linux" "x86_64-linux" "riscv64-linux" ]; systems = [ "aarch64-linux" "x86_64-linux" "riscv64-linux" ];
perSystem = { config, self', inputs', pkgs, system, ... }: { 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; overlayAttrs = config.packages;
packages = { packages = let sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
in {
default = self'.packages.${name}; default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test; 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 { devShells.default = pkgs.mkShell {
inputsFrom = [ self'.packages.default ]; inputsFrom = [ self'.packages.default ];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
@@ -61,7 +70,9 @@
readSecretString stardustxrDiscord .webhook > .webhook readSecretString stardustxrDiscord .webhook > .webhook
readSecretString stardustxrIpfs .basicauth > .basicauth readSecretString stardustxrIpfs .basicauth > .basicauth
set -x 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) export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
set +x set +x
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID" export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 789 KiB

BIN
img/workflow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

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 { rustPlatform
, src , src
, name , name
, openxr-loader
, libGL , libGL
, mesa , mesa
, xorg , xorg
, fontconfig , fontconfig
, libxkbcommon , libxkbcommon
, libclang , libclang
, cmake , cmake
, cpm-cmake , cpm-cmake
, pkg-config , pkg-config
, llvmPackages , llvmPackages
, fetchFromGitHub
, sk_gpu
, libXau
, libXdmcp
, stdenv
, lib
, openxr-loader
}: }:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
@@ -20,21 +28,59 @@ rustPlatform.buildRustPackage rec {
lockFile = (src + "/Cargo.lock"); lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true; allowBuiltinFetchGit = true;
}; };
buildFeatures = [ "local_deps" ];
FORCE_LOCAL_DEPS = true;
CPM_LOCAL_PACKAGES_ONLY = true;
CPM_SOURCE_CACHE = "./build"; CPM_SOURCE_CACHE = "./build";
postPatch = '' CPM_USE_LOCAL_PACKAGES = true;
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit) 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 mkdir -p $sk/build/cpm
# This is not ideal, the original approach was to fetch the exact cmake # 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 # 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 = [ nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = [ 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"; LIBCLANG_PATH = "${libclang.lib}/lib";
} }

View File

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

View File

@@ -1,13 +1,17 @@
use super::client::{get_env, Client}; use super::client::{get_env, Client};
use crate::nodes::{spatial::Spatial, Node}; use crate::nodes::{root::ClientState, spatial::Spatial, Node};
use glam::Mat4; use glam::Mat4;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; 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! { 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)] #[derive(Debug, Serialize, Deserialize)]
@@ -27,28 +31,27 @@ impl LaunchInfo {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ClientState { pub struct ClientStateParsed {
pub launch_info: Option<LaunchInfo>, pub launch_info: Option<LaunchInfo>,
pub data: Option<Vec<u8>>, pub data: Vec<u8>,
pub root: Mat4, pub root: Mat4,
pub spatial_anchors: FxHashMap<String, Mat4>, pub spatial_anchors: FxHashMap<String, Mat4>,
} }
impl ClientState { impl ClientStateParsed {
pub fn from_deserialized(client: &Client, state: ClientStateInternal) -> Self { pub fn from_deserialized(client: &Client, state: ClientState) -> Self {
ClientState { ClientStateParsed {
launch_info: LaunchInfo::from_client(client), launch_info: LaunchInfo::from_client(client),
data: state.data, data: state.data.unwrap_or_default(),
root: Self::spatial_transform(client, &state.root.unwrap_or_default()) root: Self::spatial_transform(client, state.root).unwrap_or_default(),
.unwrap_or_default(),
spatial_anchors: state spatial_anchors: state
.spatial_anchors .spatial_anchors
.into_iter() .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(), .collect(),
} }
} }
fn spatial_transform(client: &Client, path: &str) -> Option<Mat4> { fn spatial_transform(client: &Client, id: u64) -> Option<Mat4> {
let node = client.scenegraph.get_node(path)?; let node = client.scenegraph.get_node(id)?;
let spatial = node.get_aspect::<Spatial>().ok()?; let spatial = node.get_aspect::<Spatial>().ok()?;
Some(spatial.global_transform()) Some(spatial.global_transform())
} }
@@ -58,51 +61,59 @@ impl ClientState {
CLIENT_STATES.lock().insert(token.clone(), Arc::new(self)); CLIENT_STATES.lock().insert(token.clone(), Arc::new(self));
token token
} }
pub fn to_file(self) { pub fn from_file(file: &Path) -> Option<Self> {
let project_dirs = directories::ProjectDirs::from("", "", "stardust").unwrap(); let file_string = std::fs::read_to_string(file).ok()?;
let state_dir = project_dirs.state_dir().unwrap(); toml::from_str(&file_string).ok()
std::fs::create_dir_all(state_dir).unwrap();
let mut file = std::fs::File::create(state_dir.join(nanoid::nanoid!())).unwrap();
file.write_all(&stardust_xr::schemas::flex::flexbuffers::to_vec(self).unwrap())
.unwrap();
} }
pub fn apply_to(&self, client: &Arc<Client>) -> ClientStateInternal { 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() { if let Some(root) = client.root.get() {
root.set_transform(self.root) root.set_transform(self.root)
} }
ClientStateInternal { ClientState {
data: self.data.clone(), data: Some(self.data.clone()),
root: Some("/".to_string()), root: 0,
spatial_anchors: self spatial_anchors: self
.spatial_anchors .spatial_anchors
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| {
(k.clone(), { let node = Node::generate(client, true).add_to_scenegraph().unwrap();
let node = Node::create_parent_name(client, "/spatial/anchor", k, true)
.add_to_scenegraph()
.unwrap();
Spatial::add_to(&node, None, *v, false); Spatial::add_to(&node, None, *v, false);
k.clone() (k.clone(), node.get_id())
})
}) })
.collect(), .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 { fn default() -> Self {
Self { Self {
launch_info: None, launch_info: None,
data: None, data: Default::default(),
root: Mat4::IDENTITY, root: Mat4::IDENTITY,
spatial_anchors: Default::default(), 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 std::any::Any;
use tokio::sync::mpsc::{self, unbounded_channel}; use tokio::sync::mpsc::{self, unbounded_channel};
type Anything = Box<dyn Any + Send + Sync>;
static MAIN_DESTROY_QUEUE: Lazy<( static MAIN_DESTROY_QUEUE: Lazy<(
mpsc::UnboundedSender<Box<dyn Any + Send + Sync>>, mpsc::UnboundedSender<Anything>,
Mutex<mpsc::UnboundedReceiver<Box<dyn Any + Send + Sync>>>, Mutex<mpsc::UnboundedReceiver<Anything>>,
)> = Lazy::new(|| { )> = Lazy::new(|| {
let (tx, rx) = unbounded_channel(); let (tx, rx) = unbounded_channel();
(tx, Mutex::new(rx)) (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_export]
macro_rules! create_interface { macro_rules! create_interface {
($iface:ident, $aspect:ident, $path:expr) => { ($iface:ident) => {
pub fn create_interface(client: &Arc<Client>) -> Result<()> { pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create_path(client, $path, false); let node = Node::from_id(client, INTERFACE_NODE_ID, false);
<$iface as $aspect>::add_node_members(&node); <$iface as self::InterfaceAspect>::add_node_members(&node);
node.add_to_scenegraph()?; node.add_to_scenegraph()?;
Ok(()) Ok(())
} }

View File

@@ -2,9 +2,7 @@ pub mod client;
pub mod client_state; pub mod client_state;
pub mod delta; pub mod delta;
pub mod destroy_queue; pub mod destroy_queue;
pub mod eventloop;
pub mod idl_utils; pub mod idl_utils;
pub mod node_collections;
pub mod registry; pub mod registry;
pub mod resource; pub mod resource;
pub mod scenegraph; 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)) Registry(const_mutex(None))
} }
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> { fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
MutexGuard::map(self.0.lock(), |r| { MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
r.get_or_insert_with(|| FxHashMap::default())
})
} }
pub fn add(&self, t: T) -> Arc<T> pub fn add(&self, t: T) -> Arc<T>
where where
@@ -33,12 +31,38 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
self.lock() self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize)) .contains_key(&(ptr::addr_of!(*t) as *const () as usize))
} }
pub fn 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>> { pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.lock() self.lock()
.iter() .iter()
.filter_map(|pair| pair.1.upgrade()) .filter_map(|pair| pair.1.upgrade())
.collect() .collect()
} }
pub fn set(&self, other: &Registry<T>) {
self.lock().clone_from(&other.lock());
}
pub fn take_valid_contents(&self) -> Vec<Arc<T>> { pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
self.0 self.0
.lock() .lock()
@@ -48,6 +72,14 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
.filter_map(|pair| pair.1.upgrade()) .filter_map(|pair| pair.1.upgrade())
.collect() .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) { pub fn remove(&self, t: &T) {
self.lock() self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize)); .remove(&(ptr::addr_of!(*t) as *const () as usize));
@@ -84,9 +116,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
OwnedRegistry(const_mutex(None)) OwnedRegistry(const_mutex(None))
} }
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> { fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
MutexGuard::map(self.0.lock(), |r| { MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
r.get_or_insert_with(|| FxHashMap::default())
})
} }
pub fn add(&self, t: T) -> Arc<T> pub fn add(&self, t: T) -> Arc<T>
where where

View File

@@ -1,13 +1,16 @@
use stardust_xr::values::ResourceID; use stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf}; use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use super::client::Client; use super::client::Client;
lazy_static::lazy_static! { 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() { if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension) extensions.contains(&path_extension)
} else { } else {

View File

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

View File

@@ -1,84 +1,86 @@
#![allow(clippy::empty_docs)]
mod core; mod core;
mod nodes; mod nodes;
mod objects; mod objects;
mod session;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
mod wayland; mod wayland;
use crate::core::client::CLIENTS;
use crate::core::client_state::ClientState;
use crate::core::destroy_queue; use crate::core::destroy_queue;
use crate::nodes::items::camera; use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, hmd, input}; use crate::nodes::{audio, drawable, 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 self::core::eventloop::EventLoop;
use clap::Parser; use clap::Parser;
use core::client::Client;
use core::task;
use directories::ProjectDirs; use directories::ProjectDirs;
use objects::ServerObjects;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use session::{launch_start, save_session};
use stardust_xr::server; use stardust_xr::server;
use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use stereokit::{ use stereokit_rust::material::Material;
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread, use stereokit_rust::shader::Shader;
TextureFormat, TextureType, use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
}; use stereokit_rust::system::{LogLevel, Renderer};
use stereokit::{DisplayBlend, Sk}; 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::sync::Notify;
use tokio::task::LocalSet;
use tokio::{runtime::Handle, sync::oneshot};
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info}; use tracing::{debug_span, error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter}; 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)] #[clap(author, version, about, long_about = None)]
struct CliArgs { struct CliArgs {
/// Force flatscreen mode and use the mouse pointer as a 3D pointer /// Force flatscreen mode and use the mouse pointer as a 3D pointer
#[clap(short, long, action)] #[clap(short, long, action)]
flatscreen: bool, 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 /// Run Stardust XR as an overlay with given priority
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)] #[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
overlay_priority: Option<u32>, overlay_priority: Option<u32>,
/// Don't create a tip input for controller because SOME RUNTIMES will lie /// Debug the clients started by the server
#[clap(long, action)] #[clap(short = 'd', long = "debug", action)]
disable_controller: bool, 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. /// 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)] #[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
startup_script: Option<PathBuf>, 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 STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
static STOP_NOTIFIER: Notify = Notify::const_new();
struct EventLoopInfo { // #[tokio::main]
tokio_handle: Handle, #[tokio::main(flavor = "current_thread")]
socket_path: PathBuf, async fn main() {
} color_eyre::install().unwrap();
fn main() {
ctrlc::set_handler(|| {
if atty::isnt(atty::Stream::Stdout) {
STOP_NOTIFIER.notify_waiters()
}
})
.unwrap();
let registry = tracing_subscriber::registry(); let registry = tracing_subscriber::registry();
#[cfg(feature = "profile_app")] #[cfg(feature = "profile_app")]
let 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")] #[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build(); let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
@@ -92,22 +94,93 @@ fn main() {
.with_filter(EnvFilter::from_default_env()); .with_filter(EnvFilter::from_default_env());
registry.with(log_layer).init(); 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"); let project_dirs = ProjectDirs::from("", "", "stardust");
if project_dirs.is_none() { if project_dirs.is_none() {
error!("Unable to get Stardust project directories, default skybox and startup script will not work."); 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 { let dbus_connection = Connection::session()
app_name: "Stardust XR".to_string(), .await
display_preference: if cli_args.flatscreen { .expect("Could not open dbus session");
DisplayMode::Flatscreen 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 { } else {
DisplayMode::MixedReality AppMode::XR
}, })
blend_preference: DisplayBlend::AnyTransparent, .depth_mode(DepthMode::D32)
depth_mode: DepthMode::D32, .log_filter(match EnvFilter::from_default_env().max_level_hint() {
log_filter: match EnvFilter::from_default_env().max_level_hint() {
Some(LevelFilter::ERROR) => LogLevel::Error, Some(LevelFilter::ERROR) => LogLevel::Error,
Some(LevelFilter::WARN) => LogLevel::Warning, Some(LevelFilter::WARN) => LogLevel::Warning,
Some(LevelFilter::INFO) => LogLevel::Inform, Some(LevelFilter::INFO) => LogLevel::Inform,
@@ -115,224 +188,90 @@ fn main() {
Some(LevelFilter::TRACE) => LogLevel::Diagnostic, Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
Some(LevelFilter::OFF) => LogLevel::None, Some(LevelFilter::OFF) => LogLevel::None,
None => LogLevel::Warning, None => LogLevel::Warning,
}, })
overlay_app: cli_args.overlay_priority.is_some(), .overlay_app(args.overlay_priority.is_some())
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX), .overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
disable_desktop_input_window: true, .disable_desktop_input_window(true)
render_scaling: 2.0, .origin(OriginMode::Local)
..Default::default()
}
.init() .init()
.expect("StereoKit failed to initialize"); .expect("StereoKit failed to initialize");
let _ = SK_MULTITHREAD.set(sk.multithreaded());
info!("Init StereoKit"); info!("Init StereoKit");
sk.render_set_multisample(0); Renderer::multisample(0);
Material::default().shader(Shader::pbr_clip());
sk.material_set_shader( Ui::enable_far_interact(false);
sk.material_find("default/material_pbr").unwrap(),
sk.shader_find("default/shader_pbr_clip").unwrap(),
);
// Skytex/light stuff // Skytex/light stuff
{ {
if let Some((light, tex)) = project_dirs if let Some(sky) = project_dirs
.as_ref() .as_ref()
.and_then(|dirs| { .map(|dirs| dirs.config_dir().join("skytex.hdr"))
let skytex_path = dirs.config_dir().join("skytex.hdr"); .filter(|f| f.exists())
skytex_path .and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok())
.exists()
.then(|| sk.tex_create_cubemap_file(&skytex_path, true, 100).ok())
})
.flatten()
{ {
sk.render_set_skytex(&tex); sky.render_as_sky();
sk.render_set_skylight(light);
} else { } else {
sk.render_set_skytex(sk.tex_gen_color( Renderer::skytex(Tex::gen_color(
BLACK, Color128::BLACK,
1, 1,
1, 1,
TextureType::CUBEMAP, TexType::Cubemap,
TextureFormat::RGBA32, 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")] #[cfg(feature = "wayland")]
let mut wayland = wayland::Wayland::new().expect("Could not initialize 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!"); info!("Stardust ready!");
let mut startup_child = (|| { let mut objects = ServerObjects::new(
let project_dirs = project_dirs.as_ref()?; dbus_connection.clone(),
let startup_script_path = cli_args &sk,
.startup_script args.disable_controllers,
.clone() args.disable_hands,
.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 last_frame_delta = Duration::ZERO; let mut last_frame_delta = Duration::ZERO;
let mut sleep_duration = Duration::ZERO; let mut sleep_duration = Duration::ZERO;
debug_span!("StereoKit").in_scope(|| { while let Some(token) = sk.step() {
sk.run(
|sk| {
let _span = debug_span!("StereoKit step"); let _span = debug_span!("StereoKit step");
let _span = _span.enter(); let _span = _span.enter();
hmd::frame(sk); camera::update(token);
camera::update(sk);
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
wayland.frame_event(sk); wayland.frame_event();
destroy_queue::clear(); destroy_queue::clear();
if let Some(mouse_pointer) = &mut mouse_pointer { objects.update(&sk, token);
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(); input::process_input();
nodes::root::Root::send_frame_events(sk.time_elapsed_unscaled()); nodes::root::Root::send_frame_events(Time::get_step_unscaled());
adaptive_sleep( adaptive_sleep(
sk,
&mut last_frame_delta, &mut last_frame_delta,
&mut sleep_duration, &mut sleep_duration,
Duration::from_micros(250), Duration::from_micros(250),
); );
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
wayland.update(sk); wayland.update();
drawable::draw(sk); drawable::draw(token);
audio::update(sk); audio::update();
#[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();
} }
},
)
});
info!("Cleanly shut down StereoKit");
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
drop(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( fn adaptive_sleep(
sk: &impl StereoKitMultiThread,
last_frame_delta: &mut Duration, last_frame_delta: &mut Duration,
sleep_duration: &mut Duration, sleep_duration: &mut Duration,
sleep_duration_increase: 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 *last_frame_delta < frame_delta {
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_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) { 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 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 super::{Aspect, Node};
use crate::core::client::Client; use crate::core::{client::Client, registry::Registry};
use color_eyre::eyre::{ensure, Result}; use color_eyre::eyre::Result;
use portable_atomic::AtomicBool; use std::{
use std::sync::{Arc, Weak}; ops::Add,
sync::{Arc, Weak},
};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct AliasInfo { pub struct AliasInfo {
pub(super) server_signals: Vec<&'static str>, pub(super) server_signals: Vec<u64>,
pub(super) server_methods: Vec<&'static str>, pub(super) server_methods: Vec<u64>,
pub(super) client_signals: Vec<&'static str>, 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 struct Alias {
pub enabled: Arc<AtomicBool>,
pub(super) node: Weak<Node>, pub(super) node: Weak<Node>,
pub original: Weak<Node>, pub(super) original: Weak<Node>,
pub(super) info: AliasInfo,
pub info: AliasInfo,
} }
impl Alias { impl Alias {
pub fn create( pub fn create(
client: &Arc<Client>,
parent: &str,
name: &str,
original: &Arc<Node>, original: &Arc<Node>,
client: &Arc<Client>,
info: AliasInfo, info: AliasInfo,
list: Option<&AliasList>,
) -> Result<Arc<Node>> { ) -> Result<Arc<Node>> {
ensure!( let node = Node::generate(client, true).add_to_scenegraph()?;
client Self::add_to(&node, original, info)?;
.scenegraph if let Some(list) = list {
.get_node(&(parent.to_string() + "/" + name)) list.add(&node);
.is_none(), }
"Node already exists" 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 { let alias = Alias {
enabled: Arc::new(AtomicBool::new(true)), node: Arc::downgrade(new_node),
node: Arc::downgrade(&node),
original: Arc::downgrade(original), original: Arc::downgrade(original),
info, info,
}; };
let alias = original.aliases.add(alias); let alias = original.aliases.add(alias);
node.add_aspect_raw(alias); new_node.add_aspect_raw(alias);
Ok(node) Ok(())
} }
} }
impl Aspect for Alias { impl Aspect for Alias {
const NAME: &'static str = "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::registry::Registry;
use crate::core::resource::get_resource_file; use crate::core::resource::get_resource_file;
use crate::create_interface; use crate::create_interface;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::{Spatial, Transform}; use crate::nodes::spatial::{Spatial, Transform};
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Vec4Swizzles}; use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use stardust_xr::values::ResourceID; use stardust_xr::values::ResourceID;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Arc; use std::sync::Arc;
use std::{ffi::OsStr, path::PathBuf}; 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(); static SOUND_REGISTRY: Registry<Sound> = Registry::new();
@@ -25,8 +25,8 @@ pub struct Sound {
volume: f32, volume: f32,
pending_audio_path: PathBuf, pending_audio_path: PathBuf,
sk_sound: OnceCell<SendWrapper<SkSound>>, sk_sound: OnceCell<SkSound>,
instance: Mutex<Option<SoundInstance>>, instance: Mutex<Option<SoundInst>>,
stop: Mutex<Option<()>>, stop: Mutex<Option<()>>,
play: Mutex<Option<()>>, play: Mutex<Option<()>>,
} }
@@ -53,24 +53,21 @@ impl Sound {
Ok(sound_arc) Ok(sound_arc)
} }
fn update(&self, sk: &impl StereoKitDraw) { fn update(&self) {
let sound = self.sk_sound.get_or_init(|| { let sound = self
SendWrapper::new(sk.sound_create(self.pending_audio_path.clone()).unwrap()) .sk_sound
}); .get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
if self.stop.lock().take().is_some() { if self.stop.lock().take().is_some() {
if let Some(instance) = self.instance.lock().take() { 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() { if self.instance.lock().is_none() && self.play.lock().take().is_some() {
self.instance.lock().replace(sk.sound_play( let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
sound.as_ref(), self.instance.lock().replace(instance);
vec3(0.0, 0.0, 0.0),
self.volume,
));
} }
if let Some(instance) = self.instance.lock().deref_mut() { 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() { for sound in SOUND_REGISTRY.get_valid_contents() {
sound.update(sk) sound.update()
} }
} }
create_interface!(AudioInterface, AudioInterfaceAspect, "/audio"); create_interface!(AudioInterface);
struct AudioInterface; struct AudioInterface;
impl AudioInterfaceAspect for AudioInterface { impl InterfaceAspect for AudioInterface {
#[doc = "Create a sound node. WAV and MP3 are supported."] #[doc = "Create a sound node. WAV and MP3 are supported."]
fn create_sound( fn create_sound(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
resource: ResourceID, resource: ResourceID,
) -> Result<()> { ) -> Result<()> {
let node = let node = Node::from_id(&calling_client, id, true);
Node::create_parent_name(&calling_client, Self::CREATE_SOUND_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true); let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?; 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::fields::Field;
use super::spatial::{parse_transform, Spatial}; use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Node}; use super::{Alias, Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::create_interface; use crate::create_interface;
use crate::nodes::fields::FIELD_ALIAS_INFO; use crate::nodes::fields::FIELD_ALIAS_INFO;
use crate::nodes::spatial::Transform; use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{bail, ensure, eyre, Result}; use color_eyre::eyre::{bail, ensure, eyre, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use nanoid::nanoid;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use stardust_xr::schemas::flex::flexbuffers; use stardust_xr::schemas::flex::flexbuffers;
use stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
lazy_static! { 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(); static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new(); pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
@@ -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)?; let greater_key = get_mask(mask_map_greater)?.index(key)?;
// otherwise zero-length vectors don't count the same as a single type vector // otherwise zero-length vectors don't count the same as a single type vector
if lesser_key.flexbuffer_type().is_heterogenous_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() && greater_key.flexbuffer_type().is_vector()
{ {
continue; continue;
@@ -58,14 +59,16 @@ stardust_xr_server_codegen::codegen_data_protocol!();
pub struct PulseSender { pub struct PulseSender {
node: Weak<Node>, node: Weak<Node>,
pub mask: Datamap, pub mask: Datamap,
aliases: LifeLinkedNodeMap<String>, aliases: AliasList,
field_aliases: AliasList,
} }
impl PulseSender { impl PulseSender {
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> { pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
let sender = PulseSender { let sender = PulseSender {
node: Arc::downgrade(node), node: Arc::downgrade(node),
mask, mask,
aliases: LifeLinkedNodeMap::default(), aliases: AliasList::default(),
field_aliases: AliasList::default(),
}; };
// <PulseSender as PulseSenderAspect>::add_node_members(node); // <PulseSender as PulseSenderAspect>::add_node_members(node);
@@ -91,44 +94,43 @@ impl PulseSender {
}; };
// Receiver itself // Receiver itself
let Ok(rx_alias) = Alias::create( let Ok(rx_alias) = Alias::create(
&tx_client,
tx_node.get_path(),
receiver.uid.as_str(),
&rx_node, &rx_node,
AliasInfo { &tx_client,
server_methods: vec!["send_data"], PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
..Default::default() Some(&self.aliases),
},
) else { ) else {
return; return;
}; };
self.aliases.add(receiver.uid.clone(), &rx_alias);
// Receiver's field // Receiver's field
let Ok(rx_field_alias) = Alias::create( let Ok(rx_field_alias) = Alias::create(
&rx_node
.get_aspect::<PulseReceiver>()
.unwrap()
.field
.spatial
.node()
.unwrap(),
&tx_client, &tx_client,
rx_alias.get_path(),
"field",
&rx_node.get_aspect::<PulseReceiver>().unwrap().field_node,
FIELD_ALIAS_INFO.clone(), FIELD_ALIAS_INFO.clone(),
Some(&self.aliases),
) else { ) else {
return; return;
}; };
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
let _ = let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
pulse_sender_client::new_receiver(&tx_node, &receiver.uid, &rx_alias, &rx_field_alias);
} }
fn handle_drop_receiver(&self, receiver: &PulseReceiver) { fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str(); let Some(node) = receiver.node.upgrade() else {
self.aliases.remove(uid); return;
self.aliases.remove(&(uid.to_string() + "-field")); };
self.aliases.remove_aspect(receiver);
self.field_aliases.remove_aspect(receiver.field.as_ref());
let Some(tx_node) = self.node.upgrade() else { let Some(tx_node) = self.node.upgrade() else {
return; 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 { impl Aspect for PulseSender {
@@ -142,21 +144,19 @@ impl Drop for PulseSender {
} }
pub struct PulseReceiver { pub struct PulseReceiver {
uid: String,
pub node: Weak<Node>, pub node: Weak<Node>,
pub field_node: Arc<Node>, pub field: Arc<Field>,
pub mask: Datamap, pub mask: Datamap,
} }
impl PulseReceiver { impl PulseReceiver {
pub fn add_to( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
field_node: Arc<Node>, field: Arc<Field>,
mask: Datamap, mask: Datamap,
) -> Result<Arc<PulseReceiver>> { ) -> Result<Arc<PulseReceiver>> {
let receiver = PulseReceiver { let receiver = PulseReceiver {
uid: nanoid!(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
field_node, field,
mask, mask,
}; };
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver); 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 ({:?})", "Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
this_receiver.mask this_receiver.mask
); );
pulse_receiver_client::data(&node, &sender.uid, &data)?; pulse_receiver_client::data(&node, &sender, &data)?;
Ok(()) Ok(())
} }
} }
@@ -199,24 +199,19 @@ impl Drop for PulseReceiver {
} }
} }
create_interface!(DataInterface, DataInterfaceAspect, "/data"); create_interface!(DataInterface);
struct DataInterface; struct DataInterface;
impl DataInterfaceAspect for DataInterface { impl InterfaceAspect for DataInterface {
fn create_pulse_sender( fn create_pulse_sender(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
mask: Datamap, mask: Datamap,
) -> Result<()> { ) -> Result<()> {
get_mask(&mask)?; get_mask(&mask)?;
let node = Node::create_parent_name( let node = Node::from_id(&calling_client, id, true);
&calling_client,
Self::CREATE_PULSE_SENDER_PARENT_PATH,
&name,
true,
);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false); let transform = transform.to_mat4(true, true, false);
@@ -229,22 +224,17 @@ impl DataInterfaceAspect for DataInterface {
fn create_pulse_receiver( fn create_pulse_receiver(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
field: Arc<Node>, field: Arc<Node>,
mask: Datamap, mask: Datamap,
) -> Result<()> { ) -> Result<()> {
get_mask(&mask)?; get_mask(&mask)?;
let node = Node::create_parent_name( let node = Node::from_id(&calling_client, id, true);
&calling_client,
Self::CREATE_PULSE_RECEIVER_PARENT_PATH,
&name,
true,
);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false); 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()?; let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false); Spatial::add_to(&node, Some(parent.clone()), transform, false);
@@ -256,7 +246,7 @@ impl DataInterfaceAspect for DataInterface {
_node: Arc<Node>, _node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
keymap: String, keymap: String,
) -> Result<String> { ) -> Result<u64> {
let mut keymaps = KEYMAPS.lock(); let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps if let Some(found_keymap_id) = keymaps
.iter() .iter()
@@ -264,22 +254,20 @@ impl DataInterfaceAspect for DataInterface {
.map(|(k, _v)| k) .map(|(k, _v)| k)
.last() .last()
{ {
return Ok(found_keymap_id.clone()); return Ok(found_keymap_id.data().as_ffi());
} }
let generated_id = nanoid!(); let key = keymaps.insert(keymap);
keymaps.insert(generated_id.clone(), keymap); Ok(key.data().as_ffi())
Ok(generated_id)
} }
async fn get_keymap( async fn get_keymap(
_node: Arc<Node>, _node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
keymap_id: String, keymap_id: u64,
) -> Result<String> { ) -> Result<String> {
let keymaps = KEYMAPS.lock(); 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") bail!("Could not find keymap. Try registering it")
}; };

View File

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

View File

@@ -1,5 +1,7 @@
pub mod lines; pub mod lines;
pub mod model; pub mod model;
#[cfg(feature = "wayland")]
pub mod shader_manipulation;
pub mod shaders; pub mod shaders;
pub mod text; pub mod text;
@@ -8,6 +10,7 @@ use super::{
spatial::{Spatial, Transform}, spatial::{Spatial, Transform},
Node, Node,
}; };
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::{ use crate::{
core::{client::Client, resource::get_resource_file}, core::{client::Client, resource::get_resource_file},
create_interface, create_interface,
@@ -16,22 +19,22 @@ use color_eyre::eyre::{self, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
use stardust_xr::values::ResourceID; use stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; 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))] // #[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) { pub fn draw(token: &MainThreadToken) {
lines::draw_all(sk); lines::draw_all(token);
model::draw_all(sk); model::draw_all(token);
text::draw_all(sk); text::draw_all(token);
if let Some(skytex) = QUEUED_SKYTEX.lock().take() { if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) { if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
sk.render_set_skytex(&skytex); Renderer::skytex(skytex.tex);
} }
} }
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() { if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) { if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
sk.render_set_skylight(skylight); 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); static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
stardust_xr_server_codegen::codegen_drawable_protocol!(); stardust_xr_server_codegen::codegen_drawable_protocol!();
create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable"); create_interface!(DrawableInterface);
pub struct 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<()> { fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")]) let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?; .ok_or(eyre::eyre!("Could not find resource"))?;
@@ -65,13 +68,12 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn create_lines( fn create_lines(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
lines: Vec<Line>, lines: Vec<Line>,
) -> Result<()> { ) -> Result<()> {
let node = let node = Node::from_id(&calling_client, id, true);
Node::create_parent_name(&calling_client, Self::CREATE_LINES_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true); let transform = transform.to_mat4(true, true, true);
@@ -84,13 +86,12 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn load_model( fn load_model(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
model: ResourceID, model: ResourceID,
) -> Result<()> { ) -> Result<()> {
let node = let node = Node::from_id(&calling_client, id, true);
Node::create_parent_name(&calling_client, Self::LOAD_MODEL_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true); let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
@@ -102,14 +103,13 @@ impl DrawableInterfaceAspect for DrawableInterface {
fn create_text( fn create_text(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
text: String, text: String,
style: TextStyle, style: TextStyle,
) -> Result<()> { ) -> Result<()> {
let node = let node = Node::from_id(&calling_client, id, true);
Node::create_parent_name(&calling_client, Self::CREATE_TEXT_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true); 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::client::Client;
use crate::core::destroy_queue;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::resource::get_resource_file; use crate::core::resource::get_resource_file;
use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::Spatial; use crate::nodes::spatial::Spatial;
use crate::nodes::Aspect; use crate::nodes::{Aspect, Node};
use crate::SK_MULTITHREAD; use color_eyre::eyre::{bail, eyre, Result};
use color_eyre::eyre::{eyre, Result}; use glam::{Mat4, Vec2, Vec3};
use once_cell::sync::OnceCell; use once_cell::sync::{Lazy, OnceCell};
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use stardust_xr::values::ResourceID; use stardust_xr::values::ResourceID;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::hash::{Hash, Hasher};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use stereokit::named_colors::WHITE; use stereokit_rust::material::Transparency;
use stereokit::{ use stereokit_rust::maths::Bounds;
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw, use stereokit_rust::sk::MainThreadToken;
StereoKitMultiThread, Transparency, 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 MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new(); static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
impl MaterialParameter { impl MaterialParameter {
fn apply_to_material( fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
&self, let mut params = material.get_all_param_info();
client: &Client,
sk: &impl StereoKitMultiThread,
material: &Material,
parameter_name: &str,
) {
match self { match self {
MaterialParameter::Bool(val) => { MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val); params.set_bool(parameter_name, *val);
} }
MaterialParameter::Int(val) => { MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *val); params.set_int(parameter_name, &[*val]);
} }
MaterialParameter::UInt(val) => { MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val); params.set_uint(parameter_name, &[*val]);
} }
MaterialParameter::Float(val) => { MaterialParameter::Float(val) => {
sk.material_set_float(material, parameter_name, *val); params.set_float(parameter_name, *val);
} }
MaterialParameter::Vec2(val) => { MaterialParameter::Vec2(val) => {
sk.material_set_vector2(material, parameter_name, *val); params.set_vec2(parameter_name, Vec2::from(*val));
} }
MaterialParameter::Vec3(val) => { MaterialParameter::Vec3(val) => {
sk.material_set_vector3(material, parameter_name, *val); params.set_vec3(parameter_name, Vec3::from(*val));
} }
MaterialParameter::Color(val) => { MaterialParameter::Color(val) => {
sk.material_set_color( params.set_color(
material,
parameter_name, parameter_name,
Color128::new(val.c.r, val.c.g, val.c.b, val.a), Color128::new(val.c.r, val.c.g, val.c.b, val.a),
); );
} }
MaterialParameter::Texture(resource) => { MaterialParameter::Texture(resource) => {
let Some(texture_path) = 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 { else {
return; return;
}; };
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) { if let Ok(tex) = Tex::from_file(texture_path, true, None) {
sk.material_set_texture(material, parameter_name, &tex); params.set_texture(parameter_name, &tex);
} }
} }
} }
@@ -76,129 +127,113 @@ impl MaterialParameter {
pub struct ModelPart { pub struct ModelPart {
id: i32, id: i32,
path: PathBuf, path: String,
space: Arc<Spatial>, space: Arc<Spatial>,
model: Weak<Model>, model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<Material>>>, pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
aliases: AliasList,
} }
impl ModelPart { 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(|| { HOLDOUT_MATERIAL.get_or_init(|| {
let mat = sk.material_copy(Material::UNLIT); let mut mat = Material::copy(&Material::unlit());
sk.material_set_transparency(&mat, Transparency::None); mat.transparency(Transparency::None);
sk.material_set_color( mat.color_tint(Color128::BLACK_TRANSPARENT);
&mat, Arc::new(MaterialWrapper(mat))
"color",
stereokit::sys::color128 {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
);
Arc::new(mat)
}); });
let first_root_part = sk.model_node_get_root(sk_model); let nodes = sk_model.get_nodes();
let mut current_option_part = Some(first_root_part); for part in nodes.all() {
ModelPart::create(model, &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);
}
}
} }
} }
fn create( fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
sk: &impl StereoKitMultiThread, let mut parts = model.parts.lock();
model: &Arc<Model>, let parent_part = part
sk_model: &SKModel, .get_parent()
id: i32, .and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
) -> 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());
let stardust_model_part = model.space.node()?; let stardust_model_part = model.space.node()?;
let client = stardust_model_part.get_client()?; let client = stardust_model_part.get_client()?;
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default(); let mut part_path = parent_part
part_path.push(sk.model_node_get_name(sk_model, id)?); .map(|n| n.path.clone() + "/")
let node = client.scenegraph.add_node(Node::create_parent_name( .unwrap_or_default();
&client, part_path += part.get_name().unwrap();
stardust_model_part.get_path(),
part_path.to_str()?, let node = client.scenegraph.add_node(Node::generate(&client, false));
false, let spatial_parent = parent_part
)); .map(|n| n.space.clone())
let spatial_parent = parent_node
.and_then(|n| n.get_aspect::<Spatial>().ok())
.unwrap_or_else(|| model.space.clone()); .unwrap_or_else(|| model.space.clone());
let local_transform = unsafe { part.get_local_transform().m };
let space = Spatial::add_to( let space = Spatial::add_to(
&node, &node,
Some(spatial_parent), Some(spatial_parent),
sk.model_node_get_transform_local(sk_model, id), Mat4::from_cols_array(&local_transform),
false, false,
); );
let _ = node let _ = space.bounding_box_calc.set(|node| {
.get_aspect::<Spatial>()
.unwrap()
.bounding_box_calc
.set(|node| {
let Ok(model_part) = node.get_aspect::<ModelPart>() else { let Ok(model_part) = node.get_aspect::<ModelPart>() else {
return Bounds::default(); return Bounds::default();
}; };
let Some(sk) = SK_MULTITHREAD.get() else {
return Bounds::default();
};
let Some(model) = model_part.model.upgrade() else { let Some(model) = model_part.model.upgrade() else {
return Bounds::default(); return Bounds::default();
}; };
let Some(sk_model) = model.sk_model.get() else { let Some(sk_model) = model.sk_model.get() else {
return Bounds::default(); return Bounds::default();
}; };
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else { let model_nodes = sk_model.get_nodes();
let Some(model_node) = model_nodes.get_index(model_part.id) else {
return Bounds::default(); return Bounds::default();
}; };
sk.mesh_get_bounds(sk_mesh) let Some(sk_mesh) = model_node.get_mesh() else {
return Bounds::default();
};
sk_mesh.get_bounds()
}); });
let model_part = Arc::new(ModelPart { let model_part = Arc::new(ModelPart {
id, id: *part.get_id(),
path: part_path, path: part_path,
space, space,
model: Arc::downgrade(model), model: Arc::downgrade(model),
pending_material_parameters: Mutex::new(FxHashMap::default()), pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None), pending_material_replacement: Mutex::new(None),
aliases: AliasList::default(),
}); });
<ModelPart as ModelPartAspect>::add_node_members(&node); <ModelPart as ModelPartAspect>::add_node_members(&node);
node.add_aspect_raw(model_part.clone()); node.add_aspect_raw(model_part.clone());
model.parts.add(id, &node); parts.push(model_part.clone());
Some(model_part) 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 self.pending_material_replacement
.lock() .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 { let Some(model) = self.model.upgrade() else {
return; return;
}; };
@@ -208,28 +243,39 @@ impl ModelPart {
let Some(node) = model.space.node() else { let Some(node) = model.space.node() else {
return; 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 { let Some(client) = node.get_client() else {
return; return;
}; };
if let Some(material_replacement) = self.pending_material_replacement.lock().take() { if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref()); part.material(&material_replacement.0);
} }
'mat_params: {
let mut material_parameters = self.pending_material_parameters.lock(); let mut material_parameters = self.pending_material_parameters.lock();
for (parameter_name, parameter_value) in material_parameters.drain() { if !material_parameters.is_empty() {
let Some(material) = sk.model_node_get_material(sk_model, self.id) else { let Some(material) = part.get_material() else {
continue; break 'mat_params;
}; };
let new_material = sk.material_copy(material); let new_material = material.copy();
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str()); for (parameter_name, parameter_value) in material_parameters.drain() {
sk.model_node_set_material(sk_model, self.id, &new_material); parameter_value.apply_to_material(&client, &new_material, &parameter_name);
} }
sk.model_node_set_transform_model( let shared_material =
sk_model, MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
self.id, part.material(&shared_material.0);
Spatial::space_to_space_matrix(Some(&self.space), Some(&model.space)), }
); }
} }
} }
impl Aspect for ModelPart { impl Aspect for ModelPart {
@@ -261,16 +307,11 @@ impl ModelPartAspect for ModelPart {
} }
pub struct Model { pub struct Model {
self_ref: Weak<Model>,
enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
_resource_id: ResourceID, _resource_id: ResourceID,
sk_model: OnceCell<SKModel>, sk_model: OnceCell<SKModel>,
parts: LifeLinkedNodeMap<i32>, parts: Mutex<Vec<Arc<ModelPart>>>,
} }
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
impl Model { impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> { pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
let pending_model_path = get_resource_file( let pending_model_path = get_resource_file(
@@ -280,61 +321,81 @@ impl Model {
) )
.ok_or_else(|| eyre!("Resource not found"))?; .ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model { let model = Arc::new(Model {
self_ref: self_ref.clone(),
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
_resource_id: resource_id, _resource_id: resource_id,
sk_model: OnceCell::new(), sk_model: OnceCell::new(),
parts: LifeLinkedNodeMap::default(), parts: Mutex::new(Vec::default()),
}); });
<Model as ModelAspect>::add_node_members(node);
MODEL_REGISTRY.add_raw(&model); MODEL_REGISTRY.add_raw(&model);
let sk = SK_MULTITHREAD.get().unwrap(); // 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 = sk.model_copy( let sk_model = SKModel::copy(SKModel::from_file(
sk.model_create_file(pending_model_path.to_str().unwrap(), None::<Shader>)?, pending_model_path.to_str().unwrap(),
); None,
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model); )?);
ModelPart::create_for_model(&model, &sk_model);
let _ = model.sk_model.set(sk_model); let _ = model.sk_model.set(sk_model);
node.add_aspect_raw(model.clone()); node.add_aspect_raw(model.clone());
Ok(model) Ok(model)
} }
fn draw(&self, sk: &impl StereoKitDraw) { fn draw(&self, token: &MainThreadToken) {
let Some(sk_model) = self.sk_model.get() else { let Some(sk_model) = self.sk_model.get() else {
return; return;
}; };
for model_node_node in self.parts.nodes() { let parts = self.parts.lock();
if let Ok(model_node) = model_node_node.get_aspect::<ModelPart>() { for model_node in &*parts {
model_node.update(sk); model_node.update();
};
} }
drop(parts);
sk.model_draw( if let Some(node) = self.space.node() {
sk_model, if node.enabled() {
self.space.global_transform(), sk_model.draw(token, self.space.global_transform(), None, None);
WHITE,
RenderLayer::LAYER0,
);
} }
} }
}
}
// 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 { impl Aspect for Model {
const NAME: &'static str = "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 { impl Drop for Model {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(sk_model) = self.sk_model.take() {
destroy_queue::add(sk_model);
}
MODEL_REGISTRY.remove(self); MODEL_REGISTRY.remove(self);
} }
} }
pub fn draw_all(sk: &impl StereoKitDraw) { pub fn draw_all(token: &MainThreadToken) {
for model in MODEL_REGISTRY.get_valid_contents() { for model in MODEL_REGISTRY.get_valid_contents() {
if model.enabled.load(Ordering::Relaxed) { model.draw(token);
model.draw(sk);
}
} }
} }

View File

@@ -3,16 +3,9 @@ use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER}, ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError, GlesError,
}; };
use std::mem::transmute; use stereokit_rust::shader::{Shader, _ShaderT};
use stereokit::Shader;
use tracing::error; use tracing::error;
// Simula shader with fancy lanzcos sampling
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
struct FfiAssetHeader { struct FfiAssetHeader {
asset_type: i32, asset_type: i32,
asset_state: i32, asset_state: i32,
@@ -133,7 +126,7 @@ pub unsafe fn shader_inject(
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?; let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
let gl_prog = link_program(c, gl_vert, gl_frag)?; 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() { if let Some(shader) = shader.as_mut() {
shader.shader.vertex = gl_vert; shader.shader.vertex = gl_vert;
shader.shader.pixel = gl_frag; 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 glam::{vec3, Mat4, Vec2};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{ use stereokit_rust::{
named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle as SkTextStyle, font::Font,
sk::MainThreadToken,
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
util::{Color128, Color32},
}; };
use super::{TextAspect, TextStyle}; use super::{TextAspect, TextStyle};
@@ -17,22 +19,20 @@ use super::{TextAspect, TextStyle};
static TEXT_REGISTRY: Registry<Text> = Registry::new(); static TEXT_REGISTRY: Registry<Text> = Registry::new();
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign { fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
let x_align = match x_align { match (x_align, y_align) {
super::XAlign::Left => TextAlign::XLeft, (super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
super::XAlign::Center => TextAlign::XCenter, (super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
super::XAlign::Right => TextAlign::XRight, (super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
} as u32; (super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
let y_align = match y_align { (super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
super::YAlign::Top => TextAlign::YTop, (super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter,
super::YAlign::Center => TextAlign::YCenter, (super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight,
super::YAlign::Bottom => TextAlign::YBottom, (super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight,
} as u32; (super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight,
}
unsafe { std::mem::transmute(x_align | y_align) }
} }
pub struct Text { pub struct Text {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
font_path: Option<PathBuf>, font_path: Option<PathBuf>,
style: OnceCell<SkTextStyle>, style: OnceCell<SkTextStyle>,
@@ -44,10 +44,9 @@ impl Text {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<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 client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text { let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
font_path: style.font.as_ref().and_then(|res| { 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(), style: OnceCell::new(),
@@ -60,16 +59,16 @@ impl Text {
Ok(text) Ok(text)
} }
fn draw(&self, sk: &impl StereoKitDraw) { fn draw(&self, token: &MainThreadToken) {
let style = let style =
self.style self.style
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> { .get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
let font = self let font = self
.font_path .font_path
.as_deref() .as_deref()
.and_then(|path| sk.font_create(path).ok()) .and_then(|path| Font::from_file(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap()); .unwrap_or_default();
Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) }) Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
}); });
if let Ok(style) = style { if let Ok(style) = style {
@@ -82,7 +81,8 @@ impl Text {
data.character_height, data.character_height,
)); ));
if let Some(bounds) = &data.bounds { if let Some(bounds) = &data.bounds {
sk.text_add_in( stereokit_rust::system::Text::add_in(
token,
&*text, &*text,
transform, transform,
Vec2::from(bounds.bounds) / data.character_height, Vec2::from(bounds.bounds) / data.character_height,
@@ -93,21 +93,40 @@ impl Text {
super::TextFit::Exact => TextFit::Exact, super::TextFit::Exact => TextFit::Exact,
super::TextFit::Overflow => TextFit::Overflow, super::TextFit::Overflow => TextFit::Overflow,
}, },
*style, Some(*style),
convert_align(bounds.anchor_align_x.clone(), bounds.anchor_align_y.clone()), Some(Color128::new(
convert_align(data.text_align_x.clone(), data.text_align_y.clone()), data.color.c.r,
vec3(0.0, 0.0, 0.0), data.color.c.g,
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]), 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 { } else {
sk.text_add_at( stereokit_rust::system::Text::add_at(
token,
&*text, &*text,
transform, transform,
*style, Some(*style),
TextAlign::Center, Some(Color128::new(
convert_align(data.text_align_x.clone(), data.text_align_y.clone()), data.color.c.r,
vec3(0.0, 0.0, 0.0), data.color.c.g,
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]), 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() { for text in TEXT_REGISTRY.get_valid_contents() {
if text.enabled.load(Ordering::Relaxed) { if let Some(node) = text.space.node() {
text.draw(sk); 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 crate::nodes::spatial::Spatial;
use glam::{vec3a, Mat4}; use glam::{vec3a, Mat4, Quat};
use stardust_xr::schemas::flat::{Hand as FlatHand, InputDataType, Joint};
use std::sync::Arc; use std::sync::Arc;
use super::{DistanceLink, InputSpecialization}; impl Default for Joint {
fn default() -> Self {
Joint {
position: [0.0; 3].into(),
rotation: Quat::IDENTITY.into(),
radius: 0.0,
distance: 0.0,
}
}
}
#[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(),
}
}
}
#[derive(Debug, Default)] impl InputDataTrait for Hand {
pub struct Hand { fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
pub base: FlatHand,
}
impl InputSpecialization for Hand {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
self.true_distance(space, field).abs()
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let mut min_distance = f32::MAX; let mut min_distance = f32::MAX;
for tip in [ for tip in [
&self.base.thumb.tip.position, &self.thumb.tip.position,
&self.base.index.tip.position, &self.index.tip.position,
&self.base.middle.tip.position, &self.middle.tip.position,
&self.base.ring.tip.position, &self.ring.tip.position,
&self.base.little.tip.position, &self.little.tip.position,
] { ] {
min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z))); min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z)));
} }
min_distance min_distance
} }
fn serialize( fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
&self, let local_to_handler_matrix =
distance_link: &DistanceLink, Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
local_to_handler_matrix: Mat4,
) -> InputDataType {
let mut hand = self.base;
let mut joints: Vec<&mut Joint> = Vec::new(); let mut joints: Vec<&mut Joint> = Vec::new();
joints.extend([&mut hand.palm, &mut hand.wrist]); joints.extend([&mut self.palm, &mut self.wrist]);
if let Some(elbow) = &mut hand.elbow { if let Some(elbow) = &mut self.elbow {
joints.push(elbow); joints.push(elbow);
} }
for finger in [ for finger in [
&mut hand.index, &mut self.index,
&mut hand.middle, &mut self.middle,
&mut hand.ring, &mut self.ring,
&mut hand.little, &mut self.little,
] { ] {
joints.extend([ joints.extend([
&mut finger.tip, &mut finger.tip,
@@ -56,10 +94,10 @@ impl InputSpecialization for Hand {
]); ]);
} }
joints.extend([ joints.extend([
&mut hand.thumb.tip, &mut self.thumb.tip,
&mut hand.thumb.distal, &mut self.thumb.distal,
&mut hand.thumb.proximal, &mut self.thumb.proximal,
&mut hand.thumb.metacarpal, &mut self.thumb.metacarpal,
]); ]);
for joint in joints { for joint in joints {
@@ -68,12 +106,7 @@ impl InputSpecialization for Hand {
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation(); let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into(); joint.position = position.into();
joint.rotation = rotation.into(); joint.rotation = rotation.into();
joint.distance = distance_link joint.distance = handler.field.distance(&handler.spatial, position.into());
.handler }
.field
.distance(&distance_link.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; #![allow(clippy::needless_question_mark)]
pub mod pointer;
pub mod tip;
use self::hand::Hand; mod hand;
use self::pointer::Pointer; mod handler;
use self::tip::Tip; mod method;
mod pointer;
mod tip;
use super::{ pub use handler::*;
alias::{Alias, AliasInfo}, pub use method::*;
fields::{find_field, Field, FIELD_ALIAS_INFO},
spatial::{parse_transform, Spatial}, use super::fields::Field;
Aspect, Message, Node, use super::spatial::Spatial;
}; use crate::create_interface;
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap}; 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 crate::{core::registry::Registry, nodes::spatial::Transform};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use stardust_xr::values::Datamap;
use once_cell::sync::OnceCell; use std::sync::Arc;
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};
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new(); 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 { stardust_xr_server_codegen::codegen_input_protocol!();
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32; pub trait InputDataTrait {
fn serialize( fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
&self, fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType;
} }
pub enum InputType { impl InputDataTrait for InputDataType {
Pointer(Pointer), fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
Hand(Box<Hand>),
Tip(Tip),
}
impl Deref for InputType {
type Target = dyn InputSpecialization;
fn deref(&self) -> &Self::Target {
match self { match self {
InputType::Pointer(p) => p, InputDataType::Pointer(i) => i.transform(method, handler),
InputType::Hand(h) => h.as_ref(), InputDataType::Hand(i) => i.transform(method, handler),
InputType::Tip(t) => t, 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 { create_interface!(InputInterface);
node: Weak<Node>, pub struct InputInterface;
uid: String, impl InterfaceAspect for InputInterface {
pub enabled: Mutex<bool>, #[doc = "Create an input method node"]
pub spatial: Arc<Spatial>, fn create_input_method(
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>,
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>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
message: Message, id: u64,
) -> Result<()> { parent: Arc<Node>,
#[derive(Deserialize)]
struct CreateInputHandlerInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
field_path: &'a str, initial_data: InputDataType,
} datamap: Datamap,
let info: CreateInputHandlerInfo = deserialize(message.as_ref())?; ) -> Result<()> {
let parent = calling_client let parent = parent.get_aspect::<Spatial>()?;
.get_node("Spatial parent", info.parent_path)? let transform = transform.to_mat4(true, true, true);
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, true);
let field = find_field(&calling_client, info.field_path)?;
let node = Node::create_parent_name(&calling_client, "/input/handler", info.name, true) let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
.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); Spatial::add_to(&node, Some(parent.clone()), transform, false);
InputHandler::add_to(&node, &field)?; InputHandler::add_to(&node, &field)?;
Ok(()) Ok(())
} }
}
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
pub fn process_input() { pub fn process_input() {
// Iterate over all valid input methods // Iterate over all valid input methods
let methods = debug_span!("Get valid methods").in_scope(|| { let methods = INPUT_METHOD_REGISTRY
INPUT_METHOD_REGISTRY
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter(|method| *method.enabled.lock()) .filter(|method| {
.filter(|method| method.datamap.lock().is_some()) let Some(node) = method.spatial.node() else {
return false;
};
node.enabled()
}); });
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents(); for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
const LIMIT: usize = 50; for method_alias in handler.method_aliases.get_aliases() {
for method in methods { method_alias.set_enabled(false);
for alias in method.node.upgrade().unwrap().aliases.get_valid_contents() {
alias.enabled.store(false, Ordering::Release);
} }
debug_span!("Process input method").in_scope(|| { let Some(handler_node) = handler.spatial.node() else {
// Get all valid input handlers and convert them to DistanceLink objects continue;
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links") };
.in_scope(|| { if !handler_node.enabled() {
if let Some(handler_order) = method.handler_order.get() { continue;
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
} }
}); if let Some(handler_field_node) = handler.field.spatial.node() {
if !handler_field_node.enabled() {
continue;
}
};
let captures = method.captures.take_valid_contents(); let (methods, datas) = methods
// Iterate over the distance links and send input to them .clone()
for (i, distance_link) in distance_links.into_iter().enumerate() { // filter out methods without the handler in their handler order
if let Some(method_alias) = distance_link .filter(|a| {
.handler 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 .method_aliases
.get(&(Arc::as_ptr(&distance_link.method) as usize)) .get_from_original_node(m.spatial.node.clone())?,
.and_then(|a| a.get_aspect::<Alias>().ok()) m,
{ ))
method_alias.enabled.store(true, Ordering::Release); })
} // make sure the input method alias is enabled
let captured = captures.contains(&distance_link.handler); .inspect(|(a, _)| {
distance_link.send_input( a.set_enabled(true);
i as u32, })
captured, // serialize the data
method.datamap.lock().clone().unwrap(), .map(|(a, m)| (a.clone(), m.serialize(a.get_id(), &handler)))
); .unzip::<_, _, Vec<_>, Vec<_>>();
// If the current distance link is in the list of captured input handlers, let _ = input_handler_client::input(&handler_node, &methods, &datas);
// break out of the loop to avoid sending input to the remaining distance links }
if captured { for method in methods {
break; method.internal_capture_requests.clear();
}
}
});
} }
} }

View File

@@ -1,96 +1,52 @@
use super::{DistanceLink, InputSpecialization}; use super::{InputDataTrait, InputHandler, InputMethod, Pointer};
use crate::core::client::Client; use crate::nodes::{
use crate::nodes::fields::{Field, Ray, RayMarchResult}; fields::{Field, FieldTrait, Ray, RayMarchResult},
use crate::nodes::input::{InputMethod, InputType}; spatial::Spatial,
use crate::nodes::spatial::{parse_transform, Spatial, Transform}; };
use crate::nodes::{Message, Node}; use glam::{vec3, Mat4, Quat};
use glam::{vec3, Mat4}; use std::sync::{Arc, Weak};
use serde::Deserialize;
use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Datamap;
use std::sync::Arc; impl Default for Pointer {
fn default() -> Self {
#[derive(Default)] Pointer {
pub struct Pointer; origin: [0.0; 3].into(),
// impl Default for Pointer { orientation: Quat::IDENTITY.into(),
// fn default() -> Self { deepest_point: [0.0; 3].into(),
// Pointer { }
// grab: Default::default(), }
// select: Default::default(), }
// }
// }
// }
impl Pointer { 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 { field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0), origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0), direction: vec3(0.0, 0.0, -1.0),
space: space.clone(), space: Spatial::new(
Weak::new(),
Some(method_space.clone()),
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into()),
),
}) })
} }
} }
impl InputDataTrait for Pointer {
impl InputSpecialization for Pointer { fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
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 {
let ray_info = self.ray_march(space, field); let ray_info = self.ray_march(space, field);
ray_info.min_distance ray_info.min_distance
} }
fn serialize( fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
&self, let local_to_handler_matrix =
distance_link: &DistanceLink, Mat4::from_rotation_translation(self.orientation.into(), self.origin.into())
local_to_handler_matrix: Mat4, * Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
) -> InputDataType {
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation(); 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; let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
InputDataType::Pointer(FlatPointer { self.origin = origin.into();
origin: origin.into(), self.orientation = orientation.into();
orientation: orientation.into(), self.deepest_point = deepest_point.into();
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 super::{InputDataTrait, InputHandler, InputMethod, Tip};
use crate::core::client::Client; use crate::nodes::{
use crate::nodes::fields::Field; fields::{Field, FieldTrait},
use crate::nodes::input::{InputMethod, InputType}; spatial::Spatial,
use crate::nodes::spatial::{parse_transform, Spatial, Transform}; };
use crate::nodes::{Message, Node}; use glam::{Mat4, Quat};
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 std::sync::Arc; use std::sync::Arc;
#[derive(Default)] impl Default for Tip {
pub struct Tip { fn default() -> Self {
pub radius: f32, Tip {
} origin: [0.0; 3].into(),
impl Tip { orientation: Quat::IDENTITY.into(),
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())?;
}
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()
} }
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 { impl InputDataTrait for Tip {
field.distance(space, vec3a(0.0, 0.0, 0.0)) fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, self.origin.into())
} }
fn serialize( fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
&self, let local_to_handler_matrix =
_distance_link: &DistanceLink, Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial))
local_to_handler_matrix: Mat4, * Mat4::from_rotation_translation(self.orientation.into(), self.origin.into());
) -> InputDataType {
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation(); let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
InputDataType::Tip(FlatTip { self.origin = origin.into();
origin: origin.into(), self.orientation = orientation.into();
orientation: orientation.into(),
radius: self.radius,
})
} }
} }
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::{ use crate::{
core::{ core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
client::{Client, INTERNAL_CLIENT}, create_interface,
registry::Registry,
scenegraph::MethodResponseSender,
},
nodes::{ nodes::{
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES}, drawable::{
model::{MaterialWrapper, ModelPart},
shaders::UNLIT_SHADER_BYTES,
},
items::TypeInfo, items::TypeInfo,
spatial::{parse_transform, Spatial, Transform}, spatial::{Spatial, Transform},
Message, Node, Message, Node,
}, },
}; };
use color_eyre::eyre::{bail, eyre, Result}; use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4; use glam::Mat4;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mint::{RowMatrix4, Vector2}; use mint::{ColumnMatrix4, Vector2};
use nanoid::nanoid;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc; use std::sync::Arc;
use stereokit::{ use stereokit_rust::{
Color128, Material, Rect, RenderLayer, StereoKitDraw, Tex, TextureType, Transparency, 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! { lazy_static! {
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo { pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
type_name: "camera", type_name: "camera",
aliased_local_signals: vec!["apply_preview_material", "frame"], alias_info: CAMERA_ITEM_ASPECT_ALIAS_INFO.clone(),
aliased_local_methods: vec![], ui_node_id: INTERFACE_NODE_ID,
aliased_remote_signals: vec![],
ui: Default::default(), ui: Default::default(),
items: Registry::new(), items: Registry::new(),
acceptors: 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 { pub struct CameraItem {
space: Arc<Spatial>, space: Arc<Spatial>,
frame_info: Mutex<FrameInfo>, frame_info: Mutex<FrameInfo>,
sk_tex: OnceCell<Tex>, sk_tex: OnceCell<TexWrapper>,
sk_mat: OnceCell<Arc<Material>>, sk_mat: OnceCell<Arc<MaterialWrapper>>,
applied_to: Registry<ModelPart>, applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>, apply_to: Registry<ModelPart>,
} }
#[allow(unused)]
impl CameraItem { impl CameraItem {
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) { pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
Item::add_to( Item::add_to(
node, node,
nanoid!(),
&ITEM_TYPE_INFO_CAMERA, &ITEM_TYPE_INFO_CAMERA,
ItemType::Camera(CameraItem { ItemType::Camera(CameraItem {
space: node.get_aspect::<Spatial>().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
@@ -69,11 +85,7 @@ impl CameraItem {
apply_to: Registry::new(), apply_to: Registry::new(),
}), }),
); );
node.add_local_method("frame", CameraItem::frame_flex); // <CameraItem as CameraItemAspect>::node_methods(node);
node.add_local_signal(
"apply_preview_material",
CameraItem::apply_preview_material_flex,
);
} }
fn frame_flex( fn frame_flex(
@@ -107,87 +119,113 @@ impl CameraItem {
Ok(()) Ok(())
} }
pub fn serialize_start_data(&self, id: &str) -> Result<Message> { pub fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
Ok(serialize(id)?.into()) 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 frame_info = self.frame_info.lock();
let sk_tex = self.sk_tex.get_or_init(|| { let sk_tex = self.sk_tex.get_or_init(|| {
sk.tex_gen_color( TexWrapper(Tex::gen_color(
Color128::default(), Color128::default(),
frame_info.px_size.x as i32, frame_info.px_size.x as i32,
frame_info.px_size.y as i32, frame_info.px_size.y as i32,
TextureType::RENDER_TARGET, TexType::Rendertarget,
stereokit::TextureFormat::RGBA32Linear, TexFormat::RGBA32Linear,
) ))
}); });
let sk_mat = self.sk_mat.get_or_init(|| { let sk_mat = self
let shader = sk.shader_create_mem(&UNLIT_SHADER_BYTES).unwrap(); .sk_mat
let mat = sk.material_create(&shader); .get_or_try_init(|| -> Result<Arc<MaterialWrapper>> {
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref()); let shader = Shader::from_memory(UNLIT_SHADER_BYTES)?;
sk.material_set_transparency(&mat, Transparency::Blend); let mut mat = Material::new(&shader, None);
Arc::new(mat) 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() { for model_part in self.apply_to.take_valid_contents() {
model_part.replace_material(sk_mat.clone()) model_part.replace_material(sk_mat.clone())
} }
if !self.applied_to.is_empty() { if !self.applied_to.is_empty() {
sk.render_to( Renderer::render_to(
sk_tex, token,
frame_info.proj_matrix, &sk_tex.0,
self.space.global_transform(), self.space.global_transform(),
RenderLayer::all(), frame_info.proj_matrix,
stereokit::RenderClear::All, None,
Rect { None,
x: 0.0, None,
y: 0.0, )
w: 0.0,
h: 0.0,
},
);
} }
} }
} }
impl CameraItemAspect for CameraItem {}
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(sk: &impl StereoKitDraw) { pub fn update(token: &MainThreadToken) {
for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() { for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() {
let ItemType::Camera(camera) = &camera.specialization else { let ItemType::Camera(camera) = &camera.specialization else {
continue; continue;
}; };
camera.update(sk); camera.update(token);
} }
} }
pub(super) fn create_camera_item_flex( create_interface!(ItemInterface);
impl InterfaceAspect for ItemInterface {
#[doc = "Create a camera item at a specific location"]
fn create_camera_item(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
message: Message, id: u64,
) -> Result<()> { parent: Arc<Node>,
#[derive(Deserialize)]
struct CreateCameraItemInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
proj_matrix: RowMatrix4<f32>, proj_matrix: ColumnMatrix4<f32>,
px_size: Vector2<u32>, px_size: Vector2<u32>,
} ) -> Result<()> {
let info: CreateCameraItemInfo = deserialize(message.as_ref())?; let space = parent.get_aspect::<Spatial>()?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_CAMERA.type_name); let transform = transform.to_mat4(true, true, false);
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) let node = Node::from_id(&calling_client, id, false).add_to_scenegraph()?;
.add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false); Spatial::add_to(&node, None, transform * space.global_transform(), false);
CameraItem::add_to(&node, info.proj_matrix.into(), info.px_size); CameraItem::add_to(&node, proj_matrix.into(), px_size);
node.get_aspect::<Item>().unwrap().make_alias_named(
&calling_client,
&parent_name,
info.name,
)?;
Ok(()) 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; pub mod camera;
mod environment;
pub mod panel; pub mod panel;
use self::camera::CameraItem; use self::camera::CameraItem;
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT}; use self::panel::PanelItemTrait;
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL}; use super::alias::AliasList;
use super::fields::Field; use super::fields::{Field, FIELD_ALIAS_INFO};
use super::spatial::{parse_transform, Spatial}; use super::spatial::Spatial;
use super::{Alias, Aspect, Message, Node}; use super::{Alias, Aspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo; use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field;
use crate::nodes::spatial::Transform; use crate::nodes::spatial::Transform;
use color_eyre::eyre::{ensure, eyre, Result}; use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use lazy_static::lazy_static; use color_eyre::eyre::{ensure, Result};
use nanoid::nanoid;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::Ordering;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::hash::Hash; use std::hash::Hash;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
lazy_static! { stardust_xr_server_codegen::codegen_item_protocol!();
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![];
}
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 { if item.captured_acceptor.lock().strong_count() > 0 {
release(item); release(item);
} }
@@ -55,19 +35,19 @@ fn release(item: &Item) {
*captured_acceptor = Weak::default(); *captured_acceptor = Weak::default();
acceptor.handle_release(item); acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() { if let Some(ui) = item.type_info.ui.lock().upgrade() {
ui.handle_release_item(item, &acceptor); ui.handle_release_item(item, acceptor);
} }
} }
} }
pub struct TypeInfo { pub struct TypeInfo {
pub type_name: &'static str, pub type_name: &'static str,
pub aliased_local_signals: Vec<&'static str>, pub alias_info: AliasInfo,
pub aliased_local_methods: Vec<&'static str>, pub ui_node_id: u64,
pub aliased_remote_signals: Vec<&'static str>,
pub ui: Mutex<Weak<ItemUI>>, pub ui: Mutex<Weak<ItemUI>>,
pub items: Registry<Item>, pub items: Registry<Item>,
pub acceptors: Registry<ItemAcceptor>, pub acceptors: Registry<ItemAcceptor>,
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
} }
impl Hash for TypeInfo { impl Hash for TypeInfo {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
@@ -82,8 +62,7 @@ impl PartialEq for TypeInfo {
impl Eq for TypeInfo {} impl Eq for TypeInfo {}
pub struct Item { pub struct Item {
node: Weak<Node>, spatial: Arc<Spatial>,
uid: String,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
captured_acceptor: Mutex<Weak<ItemAcceptor>>, captured_acceptor: Mutex<Weak<ItemAcceptor>>,
pub specialization: ItemType, pub specialization: ItemType,
@@ -91,20 +70,18 @@ pub struct Item {
impl Item { impl Item {
pub fn add_to( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
uid: String,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
specialization: ItemType, specialization: ItemType,
) -> Arc<Self> { ) -> Arc<Self> {
let item = Item { let item = Item {
node: Arc::downgrade(node), spatial: node.aspects.get::<Spatial>().unwrap(),
uid,
type_info, type_info,
captured_acceptor: Default::default(), captured_acceptor: Default::default(),
specialization, specialization,
}; };
let item = type_info.items.add(item); 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() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item); ui.handle_create_item(&item);
} }
@@ -122,54 +99,25 @@ impl Item {
item item
} }
fn make_alias_named( fn make_alias(&self, client: &Arc<Client>, alias_list: &AliasList) -> Result<Arc<Node>> {
&self,
client: &Arc<Client>,
parent: &str,
name: &str,
) -> Result<Arc<Node>> {
Alias::create( Alias::create(
&self.spatial.node().unwrap(),
client, client,
parent, self.type_info.alias_info.clone() + ITEM_ASPECT_ALIAS_INFO.clone(),
name, Some(alias_list),
&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(),
},
) )
} }
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 { impl Aspect for Item {
const NAME: &'static str = "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 { impl Drop for Item {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.items.remove(self); self.type_info.items.remove(self);
@@ -182,15 +130,19 @@ impl Drop for Item {
pub enum ItemType { pub enum ItemType {
Camera(CameraItem), Camera(CameraItem),
Environment(EnvironmentItem),
Panel(Arc<dyn PanelItemTrait>), Panel(Arc<dyn PanelItemTrait>),
} }
impl ItemType { impl ItemType {
fn serialize_start_data(&self, id: &str) -> Result<Message> { fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
match self { match self {
ItemType::Camera(c) => c.serialize_start_data(id), ItemType::Camera(c) => c.send_ui_item_created(node, item),
ItemType::Environment(e) => e.serialize_start_data(id), ItemType::Panel(p) => p.send_ui_item_created(node, item),
ItemType::Panel(p) => p.serialize_start_data(id), }
}
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 { pub struct ItemUI {
node: Weak<Node>, node: Weak<Node>,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
item_aliases: LifeLinkedNodeMap<String>, item_aliases: AliasList,
acceptor_aliases: LifeLinkedNodeMap<String>, acceptor_aliases: AliasList,
acceptor_field_aliases: LifeLinkedNodeMap<String>, acceptor_field_aliases: AliasList,
} }
impl ItemUI { impl ItemUI {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> { fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
@@ -222,9 +174,9 @@ impl ItemUI {
let ui = Arc::new(ItemUI { let ui = Arc::new(ItemUI {
node: Arc::downgrade(node), node: Arc::downgrade(node),
type_info, type_info,
item_aliases: Default::default(), item_aliases: AliasList::default(),
acceptor_aliases: Default::default(), acceptor_aliases: AliasList::default(),
acceptor_field_aliases: Default::default(), acceptor_field_aliases: AliasList::default(),
}); });
*type_info.ui.lock() = Arc::downgrade(&ui); *type_info.ui.lock() = Arc::downgrade(&ui);
node.add_aspect_raw(ui.clone()); node.add_aspect_raw(ui.clone());
@@ -237,16 +189,6 @@ impl ItemUI {
} }
Ok(()) 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) { fn handle_create_item(&self, item: &Item) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
@@ -256,38 +198,47 @@ impl ItemUI {
return; return;
}; };
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) { let Ok(item_alias) = item.make_alias(&client, &self.item_aliases) else {
self.item_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
return; return;
}; };
let _ = node.send_remote_signal("create_item", serialized_data);
} item.specialization.send_ui_item_created(&node, &item_alias);
fn handle_destroy_item(&self, item: &Item) {
self.item_aliases.remove(&item.uid);
self.send_state("destroy_item", item.uid.as_str());
} }
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) { fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else { let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
return; return;
}; };
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
return; 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) { 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; return;
}; };
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
return; 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) { fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
@@ -297,24 +248,40 @@ impl ItemUI {
return; 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, &client,
&format!("/item/{}/acceptor", self.type_info.type_name), ITEM_ACCEPTOR_ASPECT_ALIAS_INFO.clone(),
Some(&self.acceptor_aliases),
) else { ) else {
return; return;
}; };
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
self.acceptor_field_aliases let Some(acceptor_field_node) = acceptor.field.spatial.node() else {
.add(acceptor.uid.clone(), &field_alias);
let Ok(message) = serialize(&acceptor.uid) else {
return; 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) { fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
self.send_state("destroy_acceptor", acceptor.uid.as_str()); let acceptor_alias = self.acceptor_aliases.get_from_aspect(acceptor).unwrap();
self.acceptor_aliases.remove(&acceptor.uid); let _ = item_ui_client::destroy_acceptor(&self.node.upgrade().unwrap(), acceptor_alias.id);
self.acceptor_field_aliases.remove(&acceptor.uid);
self.acceptor_aliases
.remove_aspect(acceptor.spatial.as_ref());
self.acceptor_field_aliases
.remove_aspect(acceptor.field.as_ref());
} }
} }
impl Aspect for ItemUI { impl Aspect for ItemUI {
@@ -327,69 +294,29 @@ impl Drop for ItemUI {
} }
pub struct ItemAcceptor { pub struct ItemAcceptor {
uid: String, spatial: Arc<Spatial>,
node: Weak<Node>,
pub type_info: &'static TypeInfo, pub type_info: &'static TypeInfo,
field: Arc<Field>, field: Arc<Field>,
accepted_aliases: LifeLinkedNodeMap<String>, accepted_aliases: AliasList,
accepted_registry: Registry<Item>, accepted_registry: Registry<Item>,
} }
impl ItemAcceptor { impl ItemAcceptor {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) { fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
let acceptor = type_info.acceptors.add(ItemAcceptor { let acceptor = type_info.acceptors.add(ItemAcceptor {
uid: nanoid!(), spatial: node.get_aspect::<Spatial>().unwrap(),
node: Arc::downgrade(node),
type_info, type_info,
field, field,
accepted_aliases: Default::default(), accepted_aliases: AliasList::default(),
accepted_registry: Registry::new(), accepted_registry: Registry::new(),
}); });
node.add_local_signal("capture", ItemAcceptor::capture_flex);
if let Some(ui) = type_info.ui.lock().upgrade() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor); ui.handle_create_acceptor(&acceptor);
} }
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>) { fn handle_capture(&self, item: &Arc<Item>) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.spatial.node() else {
return; return;
}; };
let Some(client) = node.get_client() else { let Some(client) = node.get_client() else {
@@ -397,31 +324,28 @@ impl ItemAcceptor {
}; };
self.accepted_registry.add_raw(item); self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) { let Ok(alias_node) = item.make_alias(&client, &self.accepted_aliases) else {
self.accepted_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
return; return;
}; };
let _ = node.send_remote_signal("capture", serialized_data);
item.specialization
.send_acceptor_item_created(&node, &alias_node);
} }
fn handle_release(&self, item: &Item) { fn handle_release(&self, item: &Item) {
let Some(node) = self.node.upgrade() else {
return;
};
self.accepted_registry.remove(item); self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid); self.accepted_aliases.remove_aspect(item);
let Ok(message) = serialize(&item.uid) else {
let Some(node) = self.spatial.node() else {
return; 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 { impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor"; const NAME: &'static str = "ItemAcceptor";
} }
impl ItemAcceptorAspect for ItemAcceptor {}
impl Drop for ItemAcceptor { impl Drop for ItemAcceptor {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.acceptors.remove(self); self.type_info.acceptors.remove(self);
@@ -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( pub fn register_item_ui_flex(
_node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
message: Message, type_info: &'static TypeInfo,
) -> Result<()> { ) -> Result<()> {
#[derive(Deserialize)] let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
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()?;
ItemUI::add_to(&ui, type_info)?; ItemUI::add_to(&ui, type_info)?;
Ok(()) Ok(())
} }
fn create_item_acceptor_flex( fn create_item_acceptor_flex(
_node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
message: Message, id: u64,
) -> Result<()> { parent: Arc<Node>,
#[derive(Deserialize)]
struct CreateItemAcceptorInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform, transform: Transform,
field_path: &'a str, type_info: &'static TypeInfo,
item_type: &'a str, field: Arc<Node>,
} ) -> Result<()> {
let info: CreateItemAcceptorInfo = deserialize(message.as_ref())?; let space = parent.get_aspect::<Spatial>()?;
let space = calling_client let field = field.get_aspect::<Field>()?;
.get_node("Reference space", info.parent_path)? let transform = transform.to_mat4(true, true, false);
.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 node = Node::create_parent_name( let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
&calling_client,
&format!("/item/{}/acceptor", type_info.type_name),
info.name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(space.clone()), transform, false); Spatial::add_to(&node, Some(space.clone()), transform, false);
ItemAcceptor::add_to(&node, type_info, field); ItemAcceptor::add_to(&node, type_info, field);
Ok(()) Ok(())
} }
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::{ use crate::{
core::{ core::{
client::{get_env, state, Client, INTERNAL_CLIENT}, client::{get_env, state, Client, INTERNAL_CLIENT},
registry::Registry, registry::Registry,
}, },
create_interface,
nodes::{ nodes::{
drawable::model::ModelPart, drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo}, items::{Item, ItemType, TypeInfo},
spatial::Spatial, spatial::{Spatial, Transform},
Message, Node, Node,
}, },
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mint::Vector2; 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 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! { lazy_static! {
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel", type_name: "panel",
aliased_local_signals: vec![ alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
"apply_surface_material", ui_node_id: INTERFACE_NODE_ID,
"close_toplevel",
"auto_size_toplevel",
"set_toplevel_size",
"set_toplevel_focused_visuals",
"pointer_motion",
"pointer_button",
"pointer_scroll",
"keyboard_keymap",
"keyboard_key",
"touch_down",
"touch_move",
"touch_up",
"reset_touches",
],
aliased_local_methods: vec![],
aliased_remote_signals: vec![
"toplevel_parent_changed",
"toplevel_title_changed",
"toplevel_app_id_changed",
"toplevel_fullscreen_active",
"toplevel_move_request",
"toplevel_resize_request",
"toplevel_size_changed",
"set_cursor",
"new_child",
"reposition_child",
"drop_child",
],
ui: Default::default(), ui: Default::default(),
items: Registry::new(), items: Registry::new(),
acceptors: 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 { pub trait Backend: Send + Sync + 'static {
fn start_data(&self) -> Result<PanelItemInitData>; 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 close_toplevel(&self);
fn auto_size_toplevel(&self); fn auto_size_toplevel(&self);
fn set_toplevel_size(&self, size: Vector2<u32>); fn set_toplevel_size(&self, size: Vector2<u32>);
fn set_toplevel_focused_visuals(&self, focused: bool); fn set_toplevel_focused_visuals(&self, focused: bool);
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>); fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>);
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool); fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool);
fn pointer_scroll( fn pointer_scroll(
&self, &self,
surface: &SurfaceID, surface: &SurfaceId,
scroll_distance: Option<Vector2<f32>>, scroll_distance: Option<Vector2<f32>>,
scroll_steps: 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_move(&self, id: u32, position: Vector2<f32>);
fn touch_up(&self, id: u32); 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>> { 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()) Some(panel_item.clone())
} }
pub trait PanelItemTrait: Backend + Send + Sync + 'static { pub trait PanelItemTrait: Send + Sync + 'static {
fn uid(&self) -> &str; fn backend(&self) -> &dyn Backend;
fn serialize_start_data(&self, id: &str) -> Result<Message>; 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 struct PanelItem<B: Backend> {
pub uid: String, pub node: Weak<Node>,
node: Weak<Node>,
pub backend: Box<B>, pub backend: Box<B>,
} }
impl<B: Backend + ?Sized> PanelItem<B> { impl<B: Backend> PanelItem<B> {
pub fn create(backend: Box<B>, pid: Option<i32>) -> Arc<PanelItem<B>> { pub fn create(backend: Box<B>, pid: Option<i32>) -> (Arc<Node>, Arc<PanelItem<B>>) {
debug!(?pid, "Create panel item"); debug!(?pid, "Create panel item");
let startup_settings = pid let startup_settings = pid
.and_then(|pid| get_env(pid).ok()) .and_then(|pid| get_env(pid).ok())
.and_then(|env| state(&env)); .and_then(|env| state(&env));
let uid = nanoid!(); let node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
let node = Node::create_parent_name(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
.add_to_scenegraph()
.unwrap();
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
if let Some(startup_settings) = &startup_settings { if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(startup_settings.root); spatial.set_local_transform(startup_settings.root);
} }
let panel_item = Arc::new(PanelItem { let panel_item = Arc::new(PanelItem {
uid: uid.clone(),
node: Arc::downgrade(&node), node: Arc::downgrade(&node),
backend, backend,
}); });
@@ -245,353 +112,352 @@ impl<B: Backend + ?Sized> PanelItem<B> {
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone(); let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
Item::add_to( Item::add_to(
&node, &node,
uid,
&ITEM_TYPE_INFO_PANEL, &ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item), 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, panel_item)
node.add_local_signal("close_toplevel", Self::close_toplevel_flex);
node.add_local_signal("auto_size_toplevel", Self::auto_size_toplevel_flex);
node.add_local_signal("set_toplevel_size", Self::set_toplevel_size_flex);
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
node.add_local_signal("pointer_button", Self::pointer_button_flex);
node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex);
node.add_local_signal("keyboard_key", Self::keyboard_keys_flex);
node.add_local_signal("touch_down", Self::touch_down_flex);
node.add_local_signal("touch_move", Self::touch_move_flex);
node.add_local_signal("touch_up", Self::touch_up_flex);
node.add_local_signal("reset_touches", Self::reset_touches_flex);
panel_item
}
pub fn drop_toplevel(&self) {
let Some(node) = self.node.upgrade() else {
return;
};
node.destroy();
} }
} }
// Remote signals // Remote signals
impl<B: Backend + ?Sized> PanelItem<B> { #[allow(unused)]
pub fn toplevel_parent_changed(&self, parent: &str) { impl<B: Backend> PanelItem<B> {
pub fn toplevel_parent_changed(&self, parent: u64) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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) { pub fn toplevel_title_changed(&self, title: &str) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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) { pub fn toplevel_app_id_changed(&self, app_id: &str) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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) { pub fn toplevel_fullscreen_active(&self, active: bool) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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) { pub fn toplevel_move_request(&self) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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) { pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; return;
}; };
let _ = node.send_remote_signal( panel_item_client::toplevel_resize_request(&node, up, down, left, right);
"toplevel_resize_request",
serialize((up, down, left, right)).unwrap(),
);
} }
pub fn toplevel_size_changed(&self, size: Vector2<u32>) { pub fn toplevel_size_changed(&self, size: Vector2<u32>) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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>) { pub fn set_cursor(&self, geometry: Option<Geometry>) {
let Some(node) = self.node.upgrade() else { let Some(node) = self.node.upgrade() else {
return; 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 { let Some(node) = self.node.upgrade() else {
return; 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 { let Some(node) = self.node.upgrade() else {
return; 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 { let Some(node) = self.node.upgrade() else {
return; 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 { // make these stupid vectors u32 in the protocol somehow!!!!!!!1
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name( #[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>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
_message: Message, model_part: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
panel_item.$trait_fn(); let model_part = model_part.get_aspect::<ModelPart>()?;
panel_item.backend().apply_cursor_material(&model_part);
Ok(()) Ok(())
} }
};
} #[doc = "Apply a surface's visuals as a material to a model."]
macro_rules! flex_deserialize { fn apply_surface_material(
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.$trait_fn(deserialize(message.as_ref())?);
Ok(())
}
};
}
impl<B: Backend + ?Sized> PanelItem<B> {
fn apply_surface_material_flex(
node: Arc<Node>, node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
message: Message, surface: SurfaceId,
model_part: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
let model_part = model_part.get_aspect::<ModelPart>()?;
#[derive(Debug, Deserialize)] panel_item
struct SurfaceMaterialInfo<'a> { .backend()
surface: SurfaceID, .apply_surface_material(surface, &model_part);
model_node_path: &'a str,
}
let info: SurfaceMaterialInfo = deserialize(message.as_ref())?;
let model_node = calling_client
.scenegraph
.get_node(info.model_node_path)
.ok_or_else(|| eyre!("Model node not found"))?;
let model_part = model_node.get_aspect::<ModelPart>()?;
debug!(?info, "Apply surface material");
panel_item.apply_surface_material(info.surface, &model_part);
Ok(()) Ok(())
} }
flex_no_args!(close_toplevel_flex, close_toplevel); #[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."]
flex_no_args!(auto_size_toplevel_flex, auto_size_toplevel); fn close_toplevel(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
flex_deserialize!(set_toplevel_size_flex, set_toplevel_size); 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>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
message: Message, size: mint::Vector2<u32>,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
panel_item.backend().set_toplevel_size(size);
let (surface_id, position): (SurfaceID, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?surface_id, ?position, "Pointer deactivate");
panel_item.pointer_motion(&surface_id, position);
Ok(()) 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>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
message: Message, focused: bool,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
panel_item.backend().set_toplevel_focused_visuals(focused);
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(?surface_id, button, state, "Pointer button");
panel_item.pointer_button(&surface_id, button, state != 0);
Ok(()) 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>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
message: Message, surface: SurfaceId,
position: mint::Vector2<f32>,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
panel_item.backend().pointer_motion(&surface, position);
#[derive(Debug, Deserialize)]
struct PointerScrollInfo {
surface_id: SurfaceID,
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
}
let info: PointerScrollInfo = deserialize(message.as_ref())?;
debug!(?info, "Pointer scroll");
panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete);
Ok(()) 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>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
message: Message, surface: SurfaceId,
button: u32,
pressed: bool,
) -> Result<()> { ) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else { let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(()); return Ok(());
}; };
let (surface_id, keymap_id, keys): (SurfaceID, &str, Vec<i32>) = panel_item
deserialize(message.as_ref())?; .backend()
debug!(?keys, "Set keyboard key state"); .pointer_button(&surface, button, pressed);
panel_item.keyboard_keys(&surface_id, keymap_id, keys);
Ok(()) 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; return;
}; };
let _ = panel_item_ui_client::create_item(node, item, init_data);
let Ok(message) = serialize(sid) else { return };
let _ = node.send_remote_signal("grab_keyboard", message);
} }
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
fn touch_down_flex( let Ok(init_data) = self.backend.start_data() else {
node: Arc<Node>, return;
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
}; };
let _ = panel_item_acceptor_client::capture_item(node, item, init_data);
let (surface_id, id, position): (SurfaceID, u32, Vector2<f32>) =
deserialize(message.as_ref())?;
debug!(?surface_id, id, ?position, "Touch down");
panel_item.touch_down(&surface_id, id, position);
Ok(())
}
fn touch_move_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (id, position): (u32, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?position, "Touch move");
panel_item.touch_move(id, position);
Ok(())
}
flex_deserialize!(touch_up_flex, touch_up);
flex_no_args!(reset_touches_flex, reset_touches);
}
impl<B: Backend + ?Sized> PanelItemTrait for PanelItem<B> {
fn uid(&self) -> &str {
&self.uid
}
fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize((id, self.start_data()?))?.into())
} }
} }
impl<B: Backend + ?Sized> Backend for PanelItem<B> { impl<B: Backend> Drop for PanelItem<B> {
fn start_data(&self) -> Result<PanelItemInitData> {
self.backend.start_data()
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
self.backend.apply_surface_material(surface, model_part)
}
fn close_toplevel(&self) {
self.backend.close_toplevel()
}
fn auto_size_toplevel(&self) {
self.backend.auto_size_toplevel()
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
self.backend.set_toplevel_size(size)
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
self.backend.set_toplevel_focused_visuals(focused)
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
self.backend.pointer_motion(surface, position)
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
self.backend.pointer_button(surface, button, pressed)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
self.backend
.pointer_scroll(surface, scroll_distance, scroll_steps)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
self.backend.keyboard_keys(surface, keymap_id, keys)
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
self.backend.touch_down(surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.backend.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.backend.touch_up(id)
}
fn reset_touches(&self) {
self.backend.reset_touches()
}
}
impl<B: Backend + ?Sized> Drop for PanelItem<B> {
fn drop(&mut self) { fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place // 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 data;
pub mod drawable; pub mod drawable;
pub mod fields; pub mod fields;
pub mod hmd;
pub mod input; pub mod input;
pub mod items; pub mod items;
pub mod root; pub mod root;
pub mod spatial; 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 color_eyre::eyre::{eyre, Result};
use nanoid::nanoid;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use spatial::Spatial;
use stardust_xr::messenger::MessageSenderHandle; use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
@@ -24,12 +27,6 @@ use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::vec::Vec; 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)] #[derive(Default)]
pub struct Message { pub struct Message {
pub data: Vec<u8>, 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!(); 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 struct Node {
pub enabled: Arc<AtomicBool>, enabled: AtomicBool,
pub(super) uid: String, id: u64,
path: String,
client: Weak<Client>, client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>, message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize,
local_signals: Mutex<FxHashMap<String, Signal>>, local_signals: Mutex<FxHashMap<u64, Signal>>,
local_methods: Mutex<FxHashMap<String, Method>>, local_methods: Mutex<FxHashMap<u64, Method>>,
aliases: Registry<Alias>, aliases: Registry<Alias>,
aspects: Aspects, aspects: Aspects,
destroyable: bool, destroyable: bool,
@@ -71,39 +74,26 @@ impl Node {
pub fn get_client(&self) -> Option<Arc<Client>> { pub fn get_client(&self) -> Option<Arc<Client>> {
self.client.upgrade() self.client.upgrade()
} }
// pub fn get_name(&self) -> &str { pub fn get_id(&self) -> u64 {
// &self.path[self.trailing_slash_pos + 1..] self.id
// }
pub fn get_path(&self) -> &str {
self.path.as_str()
} }
pub fn create_parent_name( pub fn generate(client: &Arc<Client>, destroyable: bool) -> Self {
client: &Arc<Client>, Self::from_id(client, client.generate_id(), destroyable)
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 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 { let node = Node {
enabled: Arc::new(AtomicBool::new(true)), enabled: AtomicBool::new(true),
uid: nanoid!(),
client: Arc::downgrade(client), client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(), message_sender_handle: client.message_sender_handle.clone(),
path: path.to_string(), id,
// trailing_slash_pos: parent.len(),
local_signals: Default::default(), local_signals: Default::default(),
local_methods: Default::default(), local_methods: Default::default(),
aliases: Default::default(), aliases: Default::default(),
aspects: Default::default(), aspects: Default::default(),
destroyable, destroyable,
}; };
<Node as NodeAspect>::add_node_members(&node); <Node as OwnedAspect>::add_node_members(&node);
node node
} }
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> { pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
@@ -113,9 +103,32 @@ impl Node {
.scenegraph .scenegraph
.add_node(self)) .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) { pub fn destroy(&self) {
if let Some(client) = self.get_client() { 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()) // Ok(serialize(pid)?.into())
// } // }
pub fn add_local_signal(&self, name: &str, signal: Signal) { pub fn add_local_signal(&self, id: u64, signal: Signal) {
self.local_signals.lock().insert(name.to_string(), signal); self.local_signals.lock().insert(id, signal);
} }
pub fn add_local_method(&self, name: &str, method: Method) { pub fn add_local_method(&self, id: u64, method: Method) {
self.local_methods.lock().insert(name.to_string(), method); self.local_methods.lock().insert(id, method);
} }
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> { pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
@@ -153,11 +166,11 @@ impl Node {
pub fn send_local_signal( pub fn send_local_signal(
self: Arc<Self>, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
method: &str, method: u64,
message: Message, message: Message,
) -> Result<(), ScenegraphError> { ) -> Result<(), ScenegraphError> {
if let Ok(alias) = self.get_aspect::<Alias>() { if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_signals.iter().any(|e| e == &method) { if !alias.info.server_signals.iter().any(|e| *e == method) {
return Err(ScenegraphError::SignalNotFound); return Err(ScenegraphError::SignalNotFound);
} }
alias alias
@@ -169,7 +182,7 @@ impl Node {
let signal = self let signal = self
.local_signals .local_signals
.lock() .lock()
.get(method) .get(&method)
.cloned() .cloned()
.ok_or(ScenegraphError::SignalNotFound)?; .ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError { signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
@@ -180,12 +193,12 @@ impl Node {
pub fn execute_local_method( pub fn execute_local_method(
self: Arc<Self>, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
method: &str, method: u64,
message: Message, message: Message,
response: MethodResponseSender, response: MethodResponseSender,
) { ) {
if let Ok(alias) = self.get_aspect::<Alias>() { 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)); response.send(Err(ScenegraphError::MethodNotFound));
return; return;
} }
@@ -203,14 +216,14 @@ impl Node {
response, response,
) )
} else { } 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)); response.send(Err(ScenegraphError::MethodNotFound));
return; return;
}; };
method(self, calling_client, message, response); 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(); let message = message.into();
self.aliases self.aliases
.get_valid_contents() .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() { 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(()) Ok(())
} }
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>( pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
&self, &self,
method: &str, method: u64,
input: S, input: S,
fds: Vec<OwnedFd>, fds: Vec<OwnedFd>,
) -> Result<(D, Vec<OwnedFd>)> { ) -> Result<(D, Vec<OwnedFd>)> {
@@ -247,7 +258,7 @@ impl Node {
let serialized = serialize(input)?; let serialized = serialize(input)?;
let result = message_sender_handle let result = message_sender_handle
.method(self.path.as_str(), method, &serialized, fds)? .method(self.id, method, &serialized, fds)?
.await .await
.map_err(|e| eyre!(e))?; .map_err(|e| eyre!(e))?;
@@ -259,14 +270,16 @@ impl Node {
impl Debug for Node { impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node") f.debug_struct("Node")
.field("uid", &self.uid) .field("id", &self.id)
.field("path", &self.path) .field("local_signals", &self.local_signals.lock().keys())
.field("local_methods", &self.local_methods.lock().keys())
.field("destroyable", &self.destroyable)
.finish() .finish()
} }
} }
impl NodeAspect for Node { impl OwnedAspect for Node {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> { fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.enabled.store(enabled, Ordering::Relaxed); node.set_enabled(enabled);
Ok(()) Ok(())
} }
@@ -289,6 +302,7 @@ pub trait Aspect: Any + Send + Sync + 'static {
#[derive(Default)] #[derive(Default)]
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>); struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
impl Aspects { impl Aspects {
fn add<A: Aspect>(&self, t: A) -> Arc<A> { fn add<A: Aspect>(&self, t: A) -> Arc<A> {
let aspect = Arc::new(t); let aspect = Arc::new(t);
@@ -298,7 +312,7 @@ impl Aspects {
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) { fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
self.0.lock().insert(Self::type_key::<A>(), aspect); 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 self.0
.lock() .lock()
.get(&Self::type_key::<A>()) .get(&Self::type_key::<A>())

View File

@@ -1,143 +1,104 @@
use super::spatial::Spatial; use super::spatial::Spatial;
use super::{Message, Node}; use super::Node;
use crate::core::client::Client; 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::registry::Registry;
use crate::core::scenegraph::MethodResponseSender; use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
#[cfg(feature = "wayland")] use crate::session::connection_env;
use crate::wayland::WAYLAND_DISPLAY; use color_eyre::eyre::{bail, Result};
#[cfg(feature = "xwayland")]
use crate::wayland::X_DISPLAY;
use crate::STARDUST_INSTANCE;
use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use rustc_hash::FxHashMap; use std::path::PathBuf;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use tracing::instrument; use std::time::Instant;
use tracing::info;
static ROOT_REGISTRY: Registry<Root> = Registry::new(); static ROOT_REGISTRY: Registry<Root> = Registry::new();
stardust_xr_server_codegen::codegen_root_protocol!();
pub struct Root { pub struct Root {
pub node: Arc<Node>, node: Arc<Node>,
send_frame_event: AtomicBool, connect_instant: Instant,
} }
impl Root { impl Root {
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> { pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
let node = Node::create_parent_name(client, "", "", false); let node = Node::from_id(client, 0, false);
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex); <Self as RootAspect>::add_node_members(&node);
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,
);
let node = node.add_to_scenegraph()?; 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 { Ok(ROOT_REGISTRY.add(Root {
node, 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) { pub fn send_frame_events(delta: f64) {
if let Ok(data) = serialize((delta, 0.0)) {
for root in ROOT_REGISTRY.get_valid_contents() { for root in ROOT_REGISTRY.get_valid_contents() {
if root.send_frame_event.load(Ordering::Relaxed) { let _ = root_client::frame(
let _ = root.node.send_remote_signal("frame", data.clone()); &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) { pub fn set_transform(&self, transform: Mat4) {
let spatial = self.node.get_aspect::<Spatial>().unwrap(); let spatial = self.node.get_aspect::<Spatial>().unwrap();
spatial.set_spatial_parent(None).unwrap(); spatial.set_spatial_parent(None).unwrap();
spatial.set_local_transform(transform); spatial.set_local_transform(transform);
} }
pub async fn save_state(&self) -> Result<ClientStateInternal> { pub async fn save_state(&self) -> Result<ClientState> {
Ok(self Ok(root_client::save_state(&self.node).await?.0)
.node
.execute_remote_method_typed("save_state", (), Vec::new())
.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 { impl Drop for Root {
fn drop(&mut self) { fn drop(&mut self) {
ROOT_REGISTRY.remove(self); ROOT_REGISTRY.remove(self);
} }
} }
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
env.insert(
"DISPLAY".to_string(),
format!(":{}", X_DISPLAY.get().unwrap()),
);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
});
}

View File

@@ -1,25 +1,27 @@
pub mod zone; pub mod zone;
use self::zone::Zone; use self::zone::Zone;
use super::fields::Field; use super::alias::Alias;
use super::{Aspect, Node}; use super::fields::{Field, FieldTrait};
use super::Aspect;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::create_interface; use crate::create_interface;
use color_eyre::eyre::{eyre, Result}; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use glam::{vec3a, Mat4, Quat}; use color_eyre::eyre::{eyre, OptionExt, Result};
use glam::{vec3a, Mat4, Quat, Vec3};
use mint::Vector3; use mint::Vector3;
use nanoid::nanoid;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::ptr; use std::ptr;
use std::sync::{Arc, Weak}; 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!(); stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform { 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 let position = position
.then_some(self.translation) .then_some(self.translation)
.flatten() .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(); static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial { pub struct Spatial {
uid: String, pub node: Weak<Node>,
pub(super) node: Weak<Node>,
parent: Mutex<Option<Arc<Spatial>>>, parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>, old_parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>, transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>, zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub(super) bounding_box_calc: OnceCell<fn(&Node) -> Bounds>, pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
} }
impl Spatial { impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> { pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new(Spatial { Arc::new(Spatial {
uid: nanoid!(),
node, node,
parent: Mutex::new(parent), parent: Mutex::new(parent),
old_parent: Mutex::new(None), old_parent: Mutex::new(None),
@@ -77,6 +81,7 @@ impl Spatial {
if let Some(parent) = parent { if let Some(parent) = parent {
parent.children.add_raw(&spatial); parent.children.add_raw(&spatial);
} }
<Spatial as SpatialRefAspect>::add_node_members(node);
<Spatial as SpatialAspect>::add_node_members(node); <Spatial as SpatialAspect>::add_node_members(node);
node.add_aspect_raw(spatial.clone()); node.add_aspect_raw(spatial.clone());
spatial spatial
@@ -103,11 +108,7 @@ impl Spatial {
.map(|b| (b)(&node)) .map(|b| (b)(&node))
.unwrap_or_default(); .unwrap_or_default();
for child in self.children.get_valid_contents() { for child in self.children.get_valid_contents() {
bounds = bounds_grow_to_fit_box( bounds.grown_box(child.get_bounding_box(), child.local_transform());
bounds,
child.get_bounding_box(),
Some(child.local_transform()),
);
} }
bounds bounds
} }
@@ -189,7 +190,7 @@ impl Spatial {
} }
fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) { fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) {
if let Some(parent) = self.get_parent() { if let Some(parent) = self.get_parent() {
parent.children.remove(&self); parent.children.remove(self);
} }
if let Some(new_parent) = &new_parent { if let Some(new_parent) = &new_parent {
new_parent.children.add_raw(self); new_parent.children.add_raw(self);
@@ -223,7 +224,7 @@ impl Spatial {
} }
self.set_local_transform(Spatial::space_to_space_matrix( self.set_local_transform(Spatial::space_to_space_matrix(
Some(&self), Some(self),
parent.map(AsRef::as_ref), parent.map(AsRef::as_ref),
)); ));
self.set_parent(parent); self.set_parent(parent);
@@ -235,7 +236,7 @@ impl Spatial {
self.zone self.zone
.lock() .lock()
.upgrade() .upgrade()
.and_then(|zone| zone.field.upgrade()) .map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0))) .map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX) .unwrap_or(f32::MAX)
} }
@@ -243,7 +244,7 @@ impl Spatial {
impl Aspect for Spatial { impl Aspect for Spatial {
const NAME: &'static str = "Spatial"; const NAME: &'static str = "Spatial";
} }
impl SpatialAspect for Spatial { impl SpatialRefAspect for Spatial {
async fn get_local_bounding_box( async fn get_local_bounding_box(
node: Arc<Node>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
@@ -252,8 +253,8 @@ impl SpatialAspect for Spatial {
let bounds = this_spatial.get_bounding_box(); let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox { Ok(BoundingBox {
center: mint::Vector3::from(bounds.center), center: Vec3::from(bounds.center).into(),
size: mint::Vector3::from(bounds.dimensions), size: Vec3::from(bounds.dimensions).into(),
}) })
} }
@@ -266,21 +267,18 @@ impl SpatialAspect for Spatial {
let relative_spatial = relative_to.get_aspect::<Spatial>()?; let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)) let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into()); .transform_point3([0.0; 3].into());
let bounds = bounds_grow_to_fit_box( let mut bounds = Bounds {
Bounds { center: center.into(),
center,
dimensions: [0.0; 3].into(), dimensions: [0.0; 3].into(),
}, };
bounds.grown_box(
this_spatial.get_bounding_box(), this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix( Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
Some(&this_spatial),
Some(&relative_spatial),
)),
); );
Ok(BoundingBox { Ok(BoundingBox {
center: mint::Vector3::from(bounds.center), center: Vec3::from(bounds.center).into(),
size: mint::Vector3::from(bounds.dimensions), size: Vec3::from(bounds.dimensions).into(),
}) })
} }
@@ -304,7 +302,8 @@ impl SpatialAspect for Spatial {
scale: Some(scale.into()), scale: Some(scale.into()),
}) })
} }
}
impl SpatialAspect for Spatial {
fn set_local_transform( fn set_local_transform(
node: Arc<Node>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
@@ -361,16 +360,22 @@ impl SpatialAspect for Spatial {
} }
Ok(()) 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 { impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.uid == other.uid self.node.as_ptr() == other.node.as_ptr()
} }
} }
impl Debug for Spatial { impl Debug for Spatial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Spatial") f.debug_struct("Spatial")
.field("uid", &self.uid)
.field("parent", &self.parent) .field("parent", &self.parent)
.field("old_parent", &self.old_parent) .field("old_parent", &self.old_parent)
.field("transform", &self.transform) .field("transform", &self.transform)
@@ -379,7 +384,7 @@ impl Debug for Spatial {
} }
impl Drop for Spatial { impl Drop for Spatial {
fn drop(&mut self) { fn drop(&mut self) {
zone::release_drop(self); zone::release(self);
ZONEABLE_REGISTRY.remove(self); ZONEABLE_REGISTRY.remove(self);
} }
} }
@@ -402,26 +407,25 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
} }
pub struct SpatialInterface; pub struct SpatialInterface;
impl SpatialInterfaceAspect for SpatialInterface { impl InterfaceAspect for SpatialInterface {
fn create_spatial( fn create_spatial(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
zoneable: bool, zoneable: bool,
) -> Result<()> { ) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, true); let transform = parse_transform(transform, true, true, true);
let node = Node::create_parent_name(&calling_client, "/spatial/spatial", &name, true) let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable); Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(()) Ok(())
} }
fn create_zone( fn create_zone(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
name: String, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
field: Arc<Node>, field: Arc<Node>,
@@ -430,12 +434,31 @@ impl SpatialInterfaceAspect for SpatialInterface {
let transform = parse_transform(transform, true, true, false); let transform = parse_transform(transform, true, true, false);
let field = field.get_aspect::<Field>()?; let field = field.get_aspect::<Field>()?;
let node = Node::create_parent_name(&calling_client, "/spatial/zone", &name, true) let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
.add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent.clone()), transform, false); let space = Spatial::add_to(&node, Some(parent.clone()), transform, false);
Zone::add_to(&node, space, &field); Zone::add_to(&node, space, field);
Ok(()) 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::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, registry::Registry},
nodes::{ nodes::{
alias::{Alias, AliasInfo}, alias::{get_original, Alias, AliasList},
fields::Field, fields::{Field, FieldTrait},
Aspect, Node, Aspect, Node,
}, },
}; };
use color_eyre::eyre::Result;
use glam::vec3a; use glam::vec3a;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) { pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
let old_distance = spatial.zone_distance(); let old_distance = spatial.zone_distance();
let new_distance = zone let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
.field
.upgrade()
.map(|field| field.distance(spatial, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX);
if new_distance.abs() < old_distance.abs() { if new_distance.abs() < old_distance.abs() {
release(spatial); release(spatial);
*spatial.old_parent.lock() = spatial.get_parent(); *spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone); *spatial.zone.lock() = Arc::downgrade(zone);
zone.captured.add_raw(spatial); let Some(zone_node) = zone.spatial.node.upgrade() else {
let Some(node) = zone.spatial.node.upgrade() else {
return; 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 _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref());
let mut spatial_zone = spatial.zone.lock(); let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() { if let Some(spatial_zone) = spatial_zone.upgrade() {
spatial_zone.captured.remove_aspect(spatial.as_ref());
let Some(node) = spatial_zone.spatial.node.upgrade() else { let Some(node) = spatial_zone.spatial.node.upgrade() else {
return; return;
}; };
spatial_zone.captured.remove(spatial); let _ = super::zone_client::release(&node, spatial_node.id);
let _ = super::zone_client::release(&node, &spatial.uid);
} }
*spatial_zone = Weak::new(); *spatial_zone = Weak::new();
} }
pub(super) fn release_drop(spatial: &Spatial) {
let spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
spatial_zone.captured.remove(spatial);
let _ = super::zone_client::release(&node, &spatial.uid);
}
}
pub struct Zone { pub struct Zone {
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
pub field: Weak<Field>, pub field: Arc<Field>,
zoneables: Mutex<FxHashMap<String, Arc<Node>>>, intersecting_spatials: Registry<Spatial>,
captured: Registry<Spatial>, intersecting: AliasList,
captured: AliasList,
} }
impl Zone { 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 { let zone = Arc::new(Zone {
spatial, spatial,
field: Arc::downgrade(field), field,
zoneables: Mutex::new(FxHashMap::default()), intersecting_spatials: Registry::default(),
captured: Registry::new(), intersecting: AliasList::default(),
captured: AliasList::default(),
}); });
<Zone as ZoneAspect>::add_node_members(node); <Zone as ZoneAspect>::add_node_members(node);
node.add_aspect_raw(zone.clone()); node.add_aspect_raw(zone.clone());
zone 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 { impl Aspect for Zone {
const NAME: &'static str = "Zone"; const NAME: &'static str = "Zone";
} }
impl ZoneAspect for 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 zone = node.get_aspect::<Zone>()?;
let Some(field) = zone.field.upgrade() else { let _ = zone.update();
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;
Ok(()) Ok(())
} }
fn capture( fn capture(node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> Result<()> {
node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let zone = node.get_aspect::<Zone>()?; let zone = node.get_aspect::<Zone>()?;
let spatial = spatial.get_aspect()?; let spatial = spatial.get_aspect()?;
capture(&spatial, &zone); capture(&spatial, &zone);
Ok(()) Ok(())
} }
fn release( fn release(_node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> Result<()> {
_node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let spatial = spatial.get_aspect()?; let spatial = spatial.get_aspect()?;
release(&spatial); release(&spatial);
Ok(()) Ok(())
@@ -168,7 +149,13 @@ impl ZoneAspect for Zone {
} }
impl Drop for Zone { impl Drop for Zone {
fn drop(&mut self) { 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); release(&captured);
} }
} }

View File

@@ -1,18 +1,23 @@
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
input::{pointer::Pointer, InputMethod, InputType}, fields::{FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial, spatial::Spatial,
Node, Node, OwnedNode,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use glam::{vec3, Mat4};
use nanoid::nanoid; use serde::{Deserialize, Serialize};
use serde::Serialize; use stardust_xr::values::Datamap;
use stardust_xr::{schemas::flex::flexbuffers, values::Datamap};
use std::sync::Arc; 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)] #[derive(Debug, Clone, Serialize)]
pub struct KeyboardEvent { pub struct KeyboardEvent {
@@ -23,33 +28,79 @@ pub struct KeyboardEvent {
} }
pub struct EyePointer { pub struct EyePointer {
node: OwnedNode,
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
} }
impl EyePointer { impl EyePointer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false) let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
.add_to_scenegraph()?; let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false); let pointer = InputMethod::add_to(
let pointer = &node.0,
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap(); 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) { pub fn update(&self) {
let ray = sk.input_eyes(); let ray = Input::get_eyes();
self.spatial self.spatial
.set_local_transform(Mat4::from_rotation_translation( .set_local_transform(Mat4::from_rotation_translation(
ray.orientation, ray.orientation.into(),
ray.position, ray.position.into(),
)); ));
{ {
// Set pointer input datamap // Set pointer input datamap
let mut fbb = flexbuffers::Builder::default(); *self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
let mut map = fbb.start_map(); }
map.push("eye", 2);
map.end_map(); // send input to all the input handlers that are the closest to the ray as possible
*self.pointer.datamap.lock() = Datamap::from_raw(fbb.take_buffer()).ok(); 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 mouse_pointer;
pub mod sk_controller; pub mod sk_controller;
pub mod sk_hand; 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::{ data::{
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY, mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
}, },
fields::{Field, Ray}, fields::{FieldTrait, Ray},
input::{pointer::Pointer, InputMethod, InputType}, input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial, spatial::Spatial,
Node, Node, OwnedNode,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec2, vec3, Mat4, Vec2, Vec3}; use glam::{vec3, Mat4, Vec3};
use nanoid::nanoid; use mint::Vector2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::{convert::TryFrom, sync::Arc}; use std::sync::Arc;
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread}; use stereokit_rust::system::{Input, Key};
use xkbcommon::xkb::{Context, Keymap, FORMAT_TEXT_V1}; 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 { struct MouseEvent {
select: f32, select: f32,
middle: f32, middle: f32,
context: f32, context: f32,
grab: f32, grab: f32,
scroll_continuous: Vec2, scroll_continuous: Vector2<f32>,
scroll_discrete: Vec2, scroll_discrete: Vector2<f32>,
raw_input_events: Vec<u32>, raw_input_events: Vec<u32>,
} }
impl Default for MouseEvent {
fn default() -> Self {
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)] #[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct KeyboardEvent { pub struct KeyboardEvent {
pub keyboard: (), pub keyboard: (),
pub xkbv1: (), pub xkbv1: (),
pub keymap_id: String, pub keymap_id: u64,
pub keys: Vec<i32>, pub keys: Vec<i32>,
} }
impl Default for KeyboardEvent {
fn default() -> Self {
Self {
keyboard: (),
xkbv1: (),
keymap_id: "flatscreen".to_string(),
keys: Default::default(),
}
}
}
#[allow(unused)]
pub struct MousePointer { pub struct MousePointer {
node: Arc<Node>, node: OwnedNode,
keymap: DefaultKey,
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
capture_manager: CaptureManager,
mouse_datamap: MouseEvent, mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent, keyboard_datamap: KeyboardEvent,
keyboard_sender: Arc<PulseSender>, keyboard_sender: Arc<PulseSender>,
} }
impl MousePointer { impl MousePointer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false) let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
.add_to_scenegraph()?; let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false); let pointer = InputMethod::add_to(
let pointer = &node.0,
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap(); InputDataType::Pointer(Pointer::default()),
Datamap::from_typed(MouseEvent::default())?,
)?;
KEYMAPS.lock().insert( let context = Context::new(0).unwrap();
"flatscreen".to_string(), let keymap = KEYMAPS.lock().insert(
Keymap::new_from_names(&Context::new(0), "evdev", "", "", "", None, 0) Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
.unwrap() .unwrap()
.get_as_string(FORMAT_TEXT_V1), .get_as_string(KeymapFormat::TextV1)
.unwrap(),
); );
let keyboard_sender = PulseSender::add_to( let keyboard_sender = PulseSender::add_to(
&node, &node.0,
Datamap::from_typed(KeyboardEvent::default()).unwrap(), Datamap::from_typed(KeyboardEvent::default()).unwrap(),
) )
.unwrap(); .unwrap();
@@ -81,66 +93,78 @@ impl MousePointer {
node, node,
spatial, spatial,
pointer, pointer,
capture_manager: CaptureManager::default(),
mouse_datamap: Default::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, keyboard_sender,
}) })
} }
pub fn update(&mut self, sk: &impl StereoKitMultiThread) { pub fn update(&mut self) {
let mouse = sk.input_mouse(); let mouse = Input::get_mouse();
let ray = ray_from_mouse(mouse.pos).unwrap(); let ray = mouse.get_ray();
self.spatial.set_local_transform( self.spatial.set_local_transform(
Mat4::look_to_rh( Mat4::look_to_rh(
Vec3::from(ray.pos), Vec3::from(ray.position),
Vec3::from(ray.dir), Vec3::from(ray.direction),
vec3(0.0, 1.0, 0.0), vec3(0.0, 1.0, 0.0),
) )
.inverse(), .inverse(),
); );
{ {
// Set pointer input datamap // Set pointer input datamap
self.mouse_datamap.select = self.mouse_datamap = MouseEvent {
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) { select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
1.0f32 middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
} else { context: Input::key(Key::MouseRight).is_active() as u32 as f32,
0.0f32 grab: (Input::key(Key::Backtick).is_active() && Input::key(Key::Shift).is_active()) as u32 as f32, // Was Mouse 5
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.middle = *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
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.scroll_continuous = vec2(0.0, mouse.scroll_change / 120.0);
self.mouse_datamap.scroll_discrete = vec2(0.0, mouse.scroll_change / 120.0);
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).ok();
} }
self.send_keyboard_input(sk); self.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;
} }
fn send_keyboard_input(&mut self, sk: &impl StereoKitMultiThread) { let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
self.pointer.set_handler_order(sorted_handlers.iter());
}
fn send_keyboard_input(&mut self) {
let rx = PULSE_RECEIVER_REGISTRY let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask)) .filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| { .map(|rx| {
let result = rx.field_node.get_aspect::<Field>().unwrap().ray_march(Ray { let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0), origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0), direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(), space: self.spatial.clone(),
@@ -161,12 +185,12 @@ impl MousePointer {
if let Some(rx) = rx { if let Some(rx) = rx {
let keys = (8_u32..254) let keys = (8_u32..254)
.filter_map(|i| Key::try_from(i).ok()) .map(|i| unsafe { std::mem::transmute(i) })
.filter_map(|k| Some((map_key(k)?, sk.input_key(k)))) .filter_map(|k| Some((map_key(k)?, Input::key(k))))
.filter_map(|(i, k)| { .filter_map(|(i, k)| {
if k.contains(ButtonState::JUST_ACTIVE) { if k.is_just_active() {
Some(i as i32) Some(i as i32)
} else if k.contains(ButtonState::JUST_INACTIVE) { } else if k.is_just_inactive() {
Some(-(i as i32)) Some(-(i as i32))
} else { } else {
None None
@@ -178,7 +202,7 @@ impl MousePointer {
if !self.keyboard_datamap.keys.is_empty() { if !self.keyboard_datamap.keys.is_empty() {
pulse_receiver_client::data( pulse_receiver_client::data(
&rx.node.upgrade().unwrap(), &rx.node.upgrade().unwrap(),
&self.node.uid, &self.node.0,
&Datamap::from_typed(&self.keyboard_datamap).unwrap(), &Datamap::from_typed(&self.keyboard_datamap).unwrap(),
) )
.unwrap(); .unwrap();
@@ -260,9 +284,9 @@ fn map_key(key: Key) -> Option<u32> {
Key::F3 => Some(input_event_codes::KEY_F3!()), Key::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()), Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()), Key::F5 => Some(input_event_codes::KEY_F5!()),
Key::F6 => Some(input_event_codes::KEY_F6!()), // Key::F6 => Some(input_event_codes::KEY_F6!()),
Key::F7 => Some(input_event_codes::KEY_F7!()), // Key::F7 => Some(input_event_codes::KEY_F7!()),
Key::F8 => Some(input_event_codes::KEY_F8!()), // Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()), Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()), Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()), 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::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
Key::Minus => Some(input_event_codes::KEY_MINUS!()), Key::Minus => Some(input_event_codes::KEY_MINUS!()),
Key::Equals => Some(input_event_codes::KEY_EQUAL!()), 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::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()), Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()), Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),

View File

@@ -1,79 +1,128 @@
use super::{get_sorted_handlers, CaptureManager};
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
input::{tip::Tip, InputMethod, InputType}, fields::{Field, FieldTrait},
input::{InputDataType, InputHandler, InputMethod, Tip, INPUT_HANDLER_REGISTRY},
spatial::Spatial, spatial::Spatial,
Node, Node, OwnedNode,
}, },
objects::{ObjectHandle, SpatialRef},
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{Mat4, Vec2, Vec3}; use glam::{Mat4, Vec2, Vec3};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::sync::Arc; use std::sync::Arc;
use stereokit::{ use stereokit_rust::{
named_colors::WHITE, ButtonState, Handed, Model, RenderLayer, StereoKitDraw, material::Material,
StereoKitMultiThread, model::Model,
sk::MainThreadToken,
system::{Handed, Input},
util::Color128,
}; };
use zbus::Connection;
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Debug, Deserialize, Serialize)]
struct ControllerDatamap { struct ControllerDatamap {
select: f32, select: f32,
middle: f32,
context: f32,
grab: f32, grab: f32,
scroll: Vec2, scroll: Vec2,
} }
pub struct SkController { pub struct SkController {
_node: Arc<Node>, object_handle: ObjectHandle<SpatialRef>,
input: Arc<InputMethod>, input: Arc<InputMethod>,
model: Model,
handed: Handed, handed: Handed,
model: Model,
material: Material,
capture_manager: CaptureManager,
datamap: ControllerDatamap, datamap: ControllerDatamap,
} }
impl SkController { impl SkController {
pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> { pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name( let (spatial, object_handle) = SpatialRef::create(
&INTERNAL_CLIENT, connection,
"", &("/org/stardustxr/Controller/".to_string()
if handed == Handed::Left { + match handed {
"controller_left" Handed::Left => "left",
} else { _ => "right",
"controller_right" }),
}, );
false, let model = Model::copy(Model::from_memory(
) "cursor.glb",
.add_to_scenegraph()?; include_bytes!("cursor.glb"),
Spatial::add_to(&_node, None, Mat4::IDENTITY, false); None,
let model = sk.model_create_mem("cursor.glb", include_bytes!("cursor.glb"), None)?; )?);
let tip = InputType::Tip(Tip::default()); let model_nodes = model.get_nodes();
let input = InputMethod::add_to(&_node, tip, None)?; 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 { Ok(SkController {
_node, object_handle,
input, input,
handed, handed,
model, model,
material,
capture_manager: CaptureManager::default(),
datamap: Default::default(), datamap: Default::default(),
}) })
} }
pub fn update(&mut self, sk: &impl StereoKitDraw) { pub fn update(&mut self, token: &MainThreadToken) {
let controller = sk.input_controller(self.handed); let controller = Input::controller(self.handed);
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE); let input_node = self.input.spatial.node().unwrap();
if *self.input.enabled.lock() { input_node.set_enabled(controller.tracked.is_active());
if input_node.enabled() {
let world_transform = Mat4::from_rotation_translation( let world_transform = Mat4::from_rotation_translation(
controller.aim.orientation, controller.aim.orientation.into(),
controller.aim.position, controller.aim.position.into(),
); );
sk.model_draw( self.material
&self.model, .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), world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
WHITE, None,
RenderLayer::LAYER0, None,
); );
self.input.spatial.set_local_transform(world_transform); self.input.spatial.set_local_transform(world_transform);
} }
self.datamap.select = controller.trigger;
self.datamap.grab = controller.grip; self.datamap = ControllerDatamap {
self.datamap.scroll = controller.stick; select: controller.trigger,
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok(); 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::{ use crate::core::client::INTERNAL_CLIENT;
core::client::INTERNAL_CLIENT, use crate::nodes::fields::{Field, FieldTrait};
nodes::{ use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
input::{hand::Hand, InputMethod, InputType}, use crate::nodes::OwnedNode;
use crate::nodes::{
input::{Hand, InputMethod, Joint},
spatial::Spatial, spatial::Spatial,
Node, Node,
},
}; };
use crate::objects::{ObjectHandle, SpatialRef};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Mat4; use glam::{Mat4, Quat, Vec3};
use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stardust_xr::{ use stardust_xr::values::Datamap;
schemas::flat::{Hand as FlatHand, Joint}, use std::f32::INFINITY;
values::Datamap,
};
use std::sync::Arc; 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 { fn convert_joint(joint: HandJoint) -> Joint {
Joint { Joint {
position: joint.position.into(), position: Vec3::from(joint.position).into(),
rotation: joint.orientation.into(), rotation: Quat::from(joint.orientation).into(),
radius: joint.radius, radius: joint.radius,
distance: 0.0, distance: 0.0,
} }
@@ -33,53 +37,66 @@ struct HandDatamap {
} }
pub struct SkHand { pub struct SkHand {
_node: Arc<Node>, _node: OwnedNode,
input: Arc<InputMethod>, palm_spatial: Arc<Spatial>,
palm_object: ObjectHandle<SpatialRef>,
handed: Handed, handed: Handed,
input: Arc<InputMethod>,
capture_manager: CaptureManager,
datamap: HandDatamap, datamap: HandDatamap,
} }
impl SkHand { impl SkHand {
pub fn new(handed: Handed) -> Result<Self> { pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false) let (palm_spatial, palm_object) = SpatialRef::create(
.add_to_scenegraph()?; connection,
Spatial::add_to(&_node, None, Mat4::IDENTITY, false); &("/org/stardustxr/Hand/".to_string()
let hand = InputType::Hand(Box::new(Hand { + match handed {
base: FlatHand { 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, right: handed == Handed::Right,
..Default::default() ..Default::default()
}, });
})); let datamap = Datamap::from_typed(HandDatamap::default())?;
let input = InputMethod::add_to(&_node, hand, None)?; let input = InputMethod::add_to(&_node.0, hand, datamap)?;
Input::hand_visible(handed, false);
Ok(SkHand { Ok(SkHand {
_node, _node,
input, palm_spatial,
palm_object,
handed, handed,
input,
capture_manager: CaptureManager::default(),
datamap: Default::default(), datamap: Default::default(),
}) })
} }
pub fn update(&mut self, controller_enabled: bool, sk: &impl StereoKitMultiThread) { pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
let sk_hand = sk.input_hand(self.handed); let sk_hand = Input::hand(self.handed);
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() { let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
let controller_active = controller_enabled if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
&& sk let input_node = self.input.spatial.node().unwrap();
.input_controller(self.handed) input_node.set_enabled(
.tracked (real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
.contains(ButtonState::ACTIVE); && sk_hand.tracked.is_active(),
*self.input.enabled.lock() = );
!controller_active && sk_hand.tracked_state.contains(ButtonState::ACTIVE); if input_node.enabled() {
sk.input_hand_visible(self.handed, *self.input.enabled.lock()); hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
if *self.input.enabled.lock() { hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]); hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]); hand.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
hand.base.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
hand.base.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
for (finger, sk_finger) in [ for (finger, mut sk_finger) in [
(&mut hand.base.index, sk_hand.fingers[1]), (&mut hand.index, sk_hand.fingers[1]),
(&mut hand.base.middle, sk_hand.fingers[2]), (&mut hand.middle, sk_hand.fingers[2]),
(&mut hand.base.ring, sk_hand.fingers[3]), (&mut hand.ring, sk_hand.fingers[3]),
(&mut hand.base.little, sk_hand.fingers[4]), (&mut hand.little, sk_hand.fingers[4]),
] { ] {
sk_finger[4].radius = 0.0;
finger.tip = convert_joint(sk_finger[4]); finger.tip = convert_joint(sk_finger[4]);
finger.distal = convert_joint(sk_finger[3]); finger.distal = convert_joint(sk_finger[3]);
finger.intermediate = convert_joint(sk_finger[2]); finger.intermediate = convert_joint(sk_finger[2]);
@@ -87,21 +104,134 @@ impl SkHand {
finger.metacarpal = convert_joint(sk_finger[0]); finger.metacarpal = convert_joint(sk_finger[0]);
} }
hand.base.palm.position = sk_hand.palm.position.into(); hand.palm.position = Vec3::from(sk_hand.palm.position).into();
hand.base.palm.rotation = sk_hand.palm.orientation.into(); hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
hand.base.palm.radius = hand.palm.radius =
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5; (sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
hand.base.wrist.position = sk_hand.wrist.position.into(); self.palm_spatial
hand.base.wrist.rotation = sk_hand.wrist.orientation.into(); .set_local_transform(Mat4::from_rotation_translation(
hand.base.wrist.radius = 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; (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.pinch_strength = sk_hand.pinch_activation;
self.datamap.grab_strength = sk_hand.grip_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 input;
pub mod play_space; 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; pub struct PlaySpaceBounds;
use glam::Mat4; impl PlaySpaceBounds {
use mint::Vector2; pub async fn create(connection: &Connection) {
use nanoid::nanoid; connection
use serde::{Deserialize, Serialize}; .object_server()
use stardust_xr::values::Datamap; .at("/org/stardustxr/PlaySpace", Self)
use stereokit::StereoKitMultiThread; .await
.unwrap();
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(),
} }
} }
} #[interface(name = "org.stardustxr.PlaySpace")]
impl PlaySpaceBounds {
pub struct PlaySpace { #[zbus(property)]
_node: Arc<Node>, fn bounds(&self) -> Vec<(f64, f64)> {
spatial: Arc<Spatial>, let bounds = World::get_bounds_size();
field: Arc<Field>, vec![
_pulse_rx: Arc<PulseReceiver>, ((bounds.x).into(), (bounds.y).into()),
} ((bounds.x).into(), (-bounds.y).into()),
impl PlaySpace { ((-bounds.x).into(), (-bounds.y).into()),
pub fn new() -> Result<Self> { ((-bounds.x).into(), (bounds.y).into()),
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(),
);
} }
} }

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}; use portable_atomic::{AtomicU32, Ordering};
#[cfg(feature = "xwayland")] use rand::Rng;
use smithay::xwayland::XWaylandClientData;
use smithay::{ use smithay::{
backend::renderer::utils::{on_commit_buffer_handler, RendererSurfaceStateUserData},
delegate_compositor, delegate_compositor,
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, 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 std::sync::Arc;
use tracing::debug; use tracing::debug;
@@ -19,8 +28,10 @@ impl CompositorHandler for WaylandState {
fn commit(&mut self, surface: &WlSurface) { fn commit(&mut self, surface: &WlSurface) {
debug!(?surface, "Surface commit"); debug!(?surface, "Surface commit");
on_commit_buffer_handler::<WaylandState>(surface);
let mut count = 0; let mut count = 0;
let core_surface = compositor::with_states(surface, |data| { compositor::with_states(surface, |data| {
let count_new = data let count_new = data
.data_map .data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0)); .insert_if_missing_threadsafe(|| AtomicU32::new(0));
@@ -32,20 +43,83 @@ impl CompositorHandler for WaylandState {
data.data_map.get::<Arc<CoreSurface>>().cloned() 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 { fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
if let Some(client_state) = client.get_data::<ClientState>() { &client.get_data::<ClientState>().unwrap().compositor_state
&client_state.compositor_state
} else {
#[cfg(feature = "xwayland")]
if let Some(xwayland_client_data) = client.get_data::<XWaylandClientData>() {
return &xwayland_client_data.compositor_state;
} }
unimplemented!()
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;
}
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

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

View File

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

View File

@@ -1,471 +1,82 @@
use super::{ use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER,
};
use crate::{ use crate::{
core::task, core::task,
nodes::items::panel::{Backend, Geometry, PanelItem}, nodes::{
data::KEYMAPS,
items::panel::{Backend, Geometry, PanelItem},
},
}; };
use color_eyre::eyre::{bail, eyre, Result};
use mint::Vector2; use mint::Vector2;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::{seq::IteratorRandom, thread_rng}; use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet}; use slotmap::KeyData;
use smithay::{ use smithay::{
input::keyboard::{KeymapFile, ModifiersState}, backend::input::{AxisRelativeDirection, ButtonState, KeyState},
reexports::wayland_server::{ delegate_seat,
backend::{ClientId, GlobalId, ObjectId}, input::{
protocol::{ keyboard::{FilterResult, LedState},
wl_keyboard::{self, KeyState, WlKeyboard}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent},
wl_pointer::{self, Axis, ButtonState, WlPointer}, touch::{self, DownEvent, UpEvent},
wl_seat::{self, Capability, WlSeat, EVT_NAME_SINCE}, Seat, SeatHandler,
wl_surface::WlSurface,
wl_touch::{self, WlTouch},
},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak as WlWeak,
}, },
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak as WlWeak},
utils::SERIAL_COUNTER,
wayland::compositor, wayland::compositor,
}; };
use std::{ use std::sync::{Arc, Weak};
collections::VecDeque,
sync::Arc,
time::{Duration, Instant},
};
use tokio::sync::watch; 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>( pub fn handle_cursor<B: Backend>(
panel_item: &Arc<PanelItem<B>>, 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 panel_item_weak = Arc::downgrade(panel_item);
let _ = task::new(|| "cursor handler", async move { let _ = task::new(|| "cursor handler", async move {
while cursor.changed().await.is_ok() { 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 {
let cursor_info = cursor.borrow();
panel_item.set_cursor(cursor_info.as_ref().and_then(CursorInfo::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; continue;
};
let cursor_info = cursor.borrow();
panel_item.set_cursor(cursor_info.cursor_data());
} }
});
} }
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 struct CursorInfo {
pub surface: WlWeak<WlSurface>, pub surface: Option<WlWeak<WlSurface>>,
pub hotspot_x: i32, pub hotspot_x: i32,
pub hotspot_y: i32, pub hotspot_y: i32,
} }
impl CursorInfo { impl CursorInfo {
pub fn cursor_data(&self) -> Option<Geometry> { 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 { Some(Geometry {
origin: [self.hotspot_x, self.hotspot_y].into(), origin: [self.hotspot_x, self.hotspot_y].into(),
size: cursor_size, size: cursor_size,
@@ -473,134 +84,224 @@ impl CursorInfo {
} }
} }
impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState { pub struct SeatWrapper {
fn bind( wayland_state: Weak<Mutex<WaylandState>>,
_state: &mut WaylandState, cursor_info_tx: watch::Sender<CursorInfo>,
_handle: &DisplayHandle, pub cursor_info_rx: watch::Receiver<CursorInfo>,
_client: &Client, seat: Seat<WaylandState>,
resource: New<WlSeat>, touches: Mutex<FxHashMap<u32, WlWeak<WlSurface>>>,
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!());
} }
impl SeatWrapper {
resource.capabilities(Capability::Pointer | Capability::Keyboard | Capability::Touch); pub fn new(wayland_state: Weak<Mutex<WaylandState>>, seat: Seat<WaylandState>) -> Self {
} let (cursor_info_tx, cursor_info_rx) = watch::channel(CursorInfo {
surface: None,
fn can_view(client: Client, data: &Arc<SeatData>) -> bool { hotspot_x: 0,
let Some(seat_client) = data.client.get().cloned() else {return false}; hotspot_y: 0,
client.id() == seat_client
}
}
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())));
}
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>,
) {
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((_, 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); SeatWrapper {
wayland_state,
cursor_info_tx,
cursor_info_rx,
seat,
touches: Mutex::new(FxHashMap::default()),
} }
wl_pointer::Request::Release => (), }
_ => unreachable!(), 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);
} }
} }
} }
impl Dispatch<WlKeyboard, Arc<SeatData>, WaylandState> for WaylandState { pub fn pointer_motion(&self, surface: WlSurface, position: Vector2<f32>) {
fn request( let Some(state) = self.wayland_state.upgrade() else {
_state: &mut WaylandState, return;
_client: &Client, };
_resource: &WlKeyboard, let mut state = state.lock();
request: <WlKeyboard as Resource>::Request, let Some(pointer) = self.seat.get_pointer() else {
_data: &Arc<SeatData>, return;
_dh: &DisplayHandle, };
_data_init: &mut DataInit<'_, WaylandState>, pointer.motion(
) { &mut state,
match request { Some((surface, (0.0, 0.0).into())),
wl_keyboard::Request::Release => (), &MotionEvent {
_ => unreachable!(), 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>>,
) {
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);
}
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::<()>,
);
} }
} }
impl Dispatch<WlTouch, Arc<SeatData>, WaylandState> for WaylandState { pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
fn request( let Some(state) = self.wayland_state.upgrade() else {
_state: &mut WaylandState, return;
_client: &Client, };
_resource: &WlTouch, let Some(touch) = self.seat.get_touch() else {
request: <WlTouch as Resource>::Request, return;
_data: &Arc<SeatData>, };
_dh: &DisplayHandle, touch.down(
_data_init: &mut DataInit<'_, WaylandState>, &mut state.lock(),
) { Some((surface, (0.0, 0.0).into())),
match request { &DownEvent {
wl_touch::Request::Release => (), slot: Some(id).into(),
_ => unreachable!(), 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 super::seat::SeatWrapper;
use crate::wayland::{drm::wl_drm::WlDrm, seat::SeatData}; use crate::wayland::drm::wl_drm::WlDrm;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use smithay::{ use smithay::{
@@ -9,16 +9,20 @@ use smithay::{
renderer::gles::GlesRenderer, renderer::gles::GlesRenderer,
}, },
delegate_dmabuf, delegate_output, delegate_shm, delegate_dmabuf, delegate_output, delegate_shm,
input::{keyboard::XkbConfig, SeatState},
output::{Mode, Output, Scale, Subpixel}, output::{Mode, Output, Scale, Subpixel},
reexports::{ reexports::{
wayland_protocols::xdg::{ wayland_protocols::xdg::{
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, 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_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
wayland_server::{ wayland_server::{
backend::{ClientData, ClientId, DisconnectReason}, backend::{ClientData, ClientId, DisconnectReason},
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager}, protocol::{
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
wl_output::WlOutput,
},
DisplayHandle, DisplayHandle,
}, },
}, },
@@ -29,27 +33,23 @@ use smithay::{
dmabuf::{ dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
}, },
shell::kde::decoration::KdeDecorationState, output::OutputHandler,
shell::{
kde::decoration::KdeDecorationState,
xdg::{WmCapabilitySet, XdgShellState},
},
shm::{ShmHandler, ShmState}, shm::{ShmHandler, ShmState},
}, },
}; };
use std::sync::{Arc, Weak}; use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn}; use tracing::{info, warn};
pub struct ClientState { pub struct ClientState {
pub pid: Option<i32>,
pub id: OnceCell<ClientId>, pub id: OnceCell<ClientId>,
pub compositor_state: CompositorClientState, pub compositor_state: CompositorClientState,
pub display: Weak<DisplayWrapper>, pub seat: Arc<SeatWrapper>,
pub seat: Arc<SeatData>,
}
impl ClientState {
pub fn flush(&self) {
let Some(display) = self.display.upgrade() else {
return;
};
let _ = display.flush_clients(self.id.get().cloned());
}
} }
impl ClientData for ClientState { impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) { fn initialized(&self, client_id: ClientId) {
@@ -66,9 +66,6 @@ impl ClientData for ClientState {
} }
pub struct WaylandState { pub struct WaylandState {
pub weak_ref: Weak<Mutex<WaylandState>>,
pub display_handle: DisplayHandle,
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
// pub xdg_activation_state: XdgActivationState, // pub xdg_activation_state: XdgActivationState,
pub kde_decoration_state: KdeDecorationState, pub kde_decoration_state: KdeDecorationState,
@@ -76,6 +73,9 @@ pub struct WaylandState {
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>), dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub drm_formats: Vec<Fourcc>, pub drm_formats: Vec<Fourcc>,
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>, pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
pub seat_state: SeatState<Self>,
pub seat: Arc<SeatWrapper>,
pub xdg_shell: XdgShellState,
pub output: Output, pub output: Output,
} }
@@ -129,6 +129,12 @@ impl WaylandState {
(dmabuf_state, dmabuf_global, None) (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( let output = Output::new(
"1x".to_owned(), "1x".to_owned(),
smithay::output::PhysicalProperties { smithay::output::PhysicalProperties {
@@ -140,7 +146,7 @@ impl WaylandState {
); );
let _output_global = output.create_global::<Self>(&display_handle); let _output_global = output.create_global::<Self>(&display_handle);
let mode = Mode { let mode = Mode {
size: (2048, 2048).into(), size: (1024, 1024).into(),
refresh: 60000, refresh: 60000,
}; };
output.change_current_state( output.change_current_state(
@@ -150,8 +156,15 @@ impl WaylandState {
None, None,
); );
output.set_preferred(mode); 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, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ()); display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
display_handle.create_global::<Self, WlDrm, _>(2, ()); display_handle.create_global::<Self, WlDrm, _>(2, ());
@@ -159,9 +172,6 @@ impl WaylandState {
Arc::new_cyclic(|weak| { Arc::new_cyclic(|weak| {
Mutex::new(WaylandState { Mutex::new(WaylandState {
weak_ref: weak.clone(),
display_handle,
compositor_state, compositor_state,
// xdg_activation_state, // xdg_activation_state,
kde_decoration_state, kde_decoration_state,
@@ -169,6 +179,9 @@ impl WaylandState {
drm_formats, drm_formats,
dmabuf_state, dmabuf_state,
dmabuf_tx, dmabuf_tx,
seat_state,
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
xdg_shell,
output, output,
}) })
}) })
@@ -201,6 +214,9 @@ impl DmabufHandler for WaylandState {
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap(); 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_dmabuf!(WaylandState);
delegate_shm!(WaylandState); delegate_shm!(WaylandState);
delegate_output!(WaylandState); delegate_output!(WaylandState);

View File

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