86 Commits

Author SHA1 Message Date
Nova
fd1c6ed0cf fix: make spatial parenting more stable 2025-06-13 18:43:08 -07:00
Nova
13c6dbfd4d fix(input): send proper handler IDs to method client 2025-06-05 22:10:41 -07:00
Nova
4fb7c3df84 fix(input): send input method request/release to client 2025-06-05 20:49:15 -07:00
Nova
9d0e1ce021 fix: root sending frame events to dead clients 2025-05-15 21:21:14 -07:00
Nova
dd38b590c1 ci: remove artifacts 2025-05-15 18:24:03 -07:00
Nova
24b7195297 fix: ci (hopefully) 2025-05-15 18:15:58 -07:00
Nova
7d8993b640 refactor: set default log level to warn 2025-05-15 01:13:47 -07:00
Nova
4c70ded2b0 fix: io safety error 2025-05-15 00:59:59 -07:00
Nova
7f7a8b5264 fix: cargo.lock 2025-05-14 20:00:33 -07:00
Cyberneticmelon
43246900db Updated dependencies 2025-05-14 19:58:24 -07:00
Nova
b7a123f9c9 rewrite(README): tell to use release 2025-05-14 19:58:24 -07:00
Nova
900316968a fix: some zone weirdness ig 2025-05-14 19:58:24 -07:00
Schmarni
db30f8e61b fix(wayland): fix keyboard holding onto surfaces without causing visual or functional issues
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
0a005b9864 Revert "fix: panel items being grabbed by keyboard seat"
This reverts commit a58ab46f4a.
2025-05-14 19:58:00 -07:00
Nova
f4ed8bc37d refactor(zone): use zoneable zone distance 2025-05-14 19:58:00 -07:00
Nova
49ee4d3b67 fix(zones): don't add ancestors of zone to be captured 2025-05-14 19:58:00 -07:00
Nova
c2f1f737a0 fix: panel items being grabbed by keyboard seat 2025-05-14 19:58:00 -07:00
Nova
c9a57773d1 fix: flatscreen keyboard 2025-05-14 19:58:00 -07:00
Nova
68a7c06b9e fix: cursor hotspot positionind 2025-05-14 19:58:00 -07:00
Nova
b196cbfa3a chore: clippy 2025-05-14 19:58:00 -07:00
Nova
7067d048d6 fix(input): cull capture *attempts*, not captures 2025-05-14 19:58:00 -07:00
Nova
ef09b69378 fix(wayland): _ prefix to viewporter state 2025-05-14 19:58:00 -07:00
Nova
c5dea3b7c9 fix(input): don't limit to closest handler 2025-05-14 19:58:00 -07:00
Cyberneticmelon
5ea147f9fe Added cli update 2025-05-14 19:58:00 -07:00
Cyberneticmelon
3d6fceb0dd Fixing syntax 2025-05-14 19:58:00 -07:00
Cyberneticmelon
b1900de652 Updated dependency documentation 2025-05-14 19:58:00 -07:00
Cyberneticmelon
76ff476112 Updated documentation 2025-05-14 19:58:00 -07:00
Schmarni
57f9516a81 feat(wayland): implement wp_viewporter
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
fe6ed81255 fix(input): dropped input handlers properly release methods 2025-05-14 19:58:00 -07:00
Nova
173b033963 fix(input): unresponsive clients get uncaptured 2025-05-14 19:58:00 -07:00
Nova
fe9ae8225c feat(input): retained mode capture system 2025-05-14 19:58:00 -07:00
Nova
a149098044 feat: unset sky 2025-05-14 19:58:00 -07:00
Nova
2a5bddbb5a feat: support right click drag 2025-05-14 19:58:00 -07:00
Schmarni
a7d5992b6b refactor: remove unused macro
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
94b9b9ddcf chore: remove unneeded cargo patch
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
cfb193251f refactor: change errors to warnings and fix unions/enums in protocol
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
14e899db0e feat: add better logging to codegen
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
42fc3c3f44 feat: wip debugging improvements and protocol change
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
9bfbade9a2 refactor: make cylinders go on the XZ plane by default 2025-05-14 19:58:00 -07:00
Nova
3f4002881c fix(codegen): make serde use tagged type for enums 2025-05-14 19:58:00 -07:00
Schmarni
8a8121f1a8 feat: make stage tracking space always available
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
8fc017a6fc refactor: switch to dashmap for Aspects and Registries
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
7016904adb feat: add --nvidia flag
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
93692f365e feat(wayland): logging 2025-05-14 19:58:00 -07:00
Nova
b765b68d41 fix: force stereokit revision 2025-05-14 19:58:00 -07:00
Schmarni
5d82e42820 refactor: remove unneeded AtomicBool
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
15fe997237 feat: add Tracked Interface to dbus to allow clients to query the tracking state of controllers/hands
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
44a3480022 refactor: minimize dependencies 2025-05-14 19:58:00 -07:00
Nova
f0c50ba237 refactor: remove portable_atomic 2025-05-14 19:58:00 -07:00
Nova
30a05a3218 refactor: remove once_cell dependency 2025-05-14 19:58:00 -07:00
Nova
779706d792 refactor: upgrade to rust 2024 2025-05-14 19:58:00 -07:00
Schmarni
d65163553e fix: remove stereokits controller models
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Schmarni
33ccc66411 fix(session): remove unneeded wayland environment variables
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
fb1627dccc fix(session): set $XDG_SESSION_TYPE properly
Author:    Nova <technobaboo@gmail.com>
Date:      Fri Jan 31 12:44:36 2025 -0800
2025-05-14 19:58:00 -07:00
Schmarni
9f49ba729d fix: material batching/wrong texture issue
* fix: incorrect material batching

Signed-off-by: Schmarni <marnistromer@gmail.com>

* fix: prevent memory leak with batched materials

Signed-off-by: Schmarni <marnistromer@gmail.com>

---------

Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-05-14 19:58:00 -07:00
Nova
a44f36641e fix: wayland inconsistencies 2025-05-14 19:58:00 -07:00
6543
34fd7e6e49 format flake with nixpkgs#nixfmt-rfc-style 2025-05-14 19:58:00 -07:00
ash lea
a5f087d29f start to convert ad-hoc errors to explicit types 2025-04-01 14:24:06 -07:00
Nova
3b996c46e2 fix(ci): libfuse2 for appimages 2025-04-01 14:24:06 -07:00
Nova
2e50491144 fix(ci): appimage path 2025-04-01 14:24:06 -07:00
Nova
c3e4b2ed2a fix(ci): update to use v4 of stuff 2025-04-01 14:24:06 -07:00
Nova
c0141da88b refactor: optimize get aspect hotpath 2025-04-01 14:24:06 -07:00
Nova
8f18d83694 refactor: delete data protocol 2025-04-01 14:24:06 -07:00
Nova
6822e4bdb7 feat: remove reference to unnecessary pulse receivers 2025-04-01 14:24:06 -07:00
Nova
3c66109c45 refactor: use new xkb input code 2025-04-01 14:24:06 -07:00
Nova
d27ec84496 fix: use old stereokit hand 2025-04-01 14:24:06 -07:00
Nova
239e0c0318 feat: openxr transparency when available 2025-04-01 14:24:06 -07:00
Nova
ab913a8d84 fix(lockfile): update 2025-04-01 14:24:06 -07:00
Nova
a5653853f8 feat: switch back to dev branch of core 2025-04-01 14:24:06 -07:00
Nova
58a17fedba refactor: justfile 2025-04-01 14:24:06 -07:00
Nova
be709efbdd fix: scenegraph errors 2025-04-01 14:24:06 -07:00
Nova
4f01bd5eec feat: update zbus 2025-04-01 14:24:06 -07:00
Nova
242eed37fe clippy: cleanup 2025-04-01 14:24:06 -07:00
Nova
fe22d3954a feat: justfile 2025-04-01 14:24:06 -07:00
Nova
96b4e22e10 clippy 2025-04-01 14:24:06 -07:00
Nova
a51db703fd fix(audio): stop sound when dropped 2025-04-01 14:24:06 -07:00
Nova
7e755a44b8 fix(objects/sk_hand): draw pinky 2025-04-01 14:24:06 -07:00
Nova
80c9386f79 fix(items): add the proper aspects 2025-04-01 14:24:06 -07:00
Nova
4730f0732b refactor: alias_id 2025-04-01 14:24:06 -07:00
Nova
79935befb7 refactor: upgrade packages 2025-04-01 14:24:06 -07:00
Nova
b2e452326b fix(cargo.toml): build settings that don't deadlock 2025-04-01 14:24:06 -07:00
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
60 changed files with 2235 additions and 2016 deletions

View File

@@ -3,7 +3,7 @@ name: Build
on: on:
push: push:
branches: branches:
- '*' - "*"
jobs: jobs:
build_and_package: build_and_package:
@@ -11,13 +11,13 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Install runtime dependencies - name: Install runtime dependencies
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
- name: Install build dependencies - name: Install build dependencies
run: sudo apt install -y --no-install-recommends cmake ninja-build run: sudo apt install -y --no-install-recommends cmake ninja-build libfuse2
- name: Set up Rust - name: Set up Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -26,21 +26,3 @@ jobs:
- name: Build server - name: Build server
run: cargo build --release run: cargo build --release
- name: Install appimagetool
run: |
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
chmod +x /usr/local/bin/appimagetool; \
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
- name: Install cargo-appimage
run: cargo install cargo-appimage
- name: Generate AppImage
run: cargo appimage
- name: Upload AppImage
uses: actions/upload-artifact@v2
with:
name: appimage
path: '*.AppImage'

373
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -50,7 +50,7 @@ dependencies = [
"ndk-context", "ndk-context",
"ndk-sys", "ndk-sys",
"num_enum 0.7.3", "num_enum 0.7.3",
"thiserror", "thiserror 1.0.63",
] ]
[[package]] [[package]]
@@ -87,16 +87,6 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "angular-units"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1c36cde4b75aa8518ad38880fdc7b649d7bf22b359a296964756e2319d598d"
dependencies = [
"approx 0.3.2",
"num-traits",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.15" version = "0.6.15"
@@ -158,15 +148,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2" checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2"
[[package]]
name = "approx"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "approx" name = "approx"
version = "0.4.0" version = "0.4.0"
@@ -217,7 +198,7 @@ checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
dependencies = [ dependencies = [
"async-task", "async-task",
"concurrent-queue", "concurrent-queue",
"fastrand 2.1.1", "fastrand",
"futures-lite", "futures-lite",
"slab", "slab",
] ]
@@ -291,7 +272,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -331,7 +312,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -348,7 +329,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -470,15 +451,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.6.1" version = "1.6.1"
@@ -509,7 +481,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -578,7 +550,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
dependencies = [ dependencies = [
"approx 0.4.0", "approx",
"num-traits", "num-traits",
] ]
@@ -613,7 +585,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -648,10 +620,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"color-spantrace",
"eyre", "eyre",
"indenter", "indenter",
"once_cell", "once_cell",
"owo-colors", "owo-colors",
"tracing-error",
]
[[package]]
name = "color-spantrace"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
dependencies = [
"once_cell",
"owo-colors",
"tracing-core",
"tracing-error",
] ]
[[package]] [[package]]
@@ -727,15 +713,6 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "cpufeatures"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@@ -766,16 +743,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "cty" name = "cty"
version = "0.2.2" version = "0.2.2"
@@ -789,13 +756,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[package]] [[package]]
name = "digest" name = "dashmap"
version = "0.10.7" version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [ dependencies = [
"block-buffer", "cfg-if",
"crypto-common", "crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core 0.9.10",
] ]
[[package]] [[package]]
@@ -866,22 +837,23 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]] [[package]]
name = "drm" name = "drm"
version = "0.12.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"bytemuck", "bytemuck",
"drm-ffi", "drm-ffi",
"drm-fourcc", "drm-fourcc",
"libc",
"rustix", "rustix",
] ]
[[package]] [[package]]
name = "drm-ffi" name = "drm-ffi"
version = "0.8.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b"
dependencies = [ dependencies = [
"drm-sys", "drm-sys",
"rustix", "rustix",
@@ -895,9 +867,9 @@ checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
[[package]] [[package]]
name = "drm-sys" name = "drm-sys"
version = "0.7.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c"
dependencies = [ dependencies = [
"libc", "libc",
"linux-raw-sys 0.6.5", "linux-raw-sys 0.6.5",
@@ -942,7 +914,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -1002,15 +974,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.1.1" version = "2.1.1"
@@ -1085,11 +1048,11 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.3.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [ dependencies = [
"fastrand 2.1.1", "fastrand",
"futures-core", "futures-core",
"futures-io", "futures-io",
"parking", "parking",
@@ -1104,7 +1067,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -1126,11 +1089,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io",
"futures-macro", "futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab", "slab",
@@ -1149,16 +1109,6 @@ dependencies = [
"windows", "windows",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@@ -1189,9 +1139,9 @@ dependencies = [
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.28.0" version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a"
dependencies = [ dependencies = [
"mint", "mint",
"serde", "serde",
@@ -1475,7 +1425,7 @@ dependencies = [
"combine", "combine",
"jni-sys", "jni-sys",
"log", "log",
"thiserror", "thiserror 1.0.63",
"walkdir", "walkdir",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@@ -1503,7 +1453,7 @@ checksum = "062c875482ccb676fd40c804a40e3824d4464c18c364547456d1c8e8e951ae47"
dependencies = [ dependencies = [
"miette", "miette",
"nom", "nom",
"thiserror", "thiserror 1.0.63",
] ]
[[package]] [[package]]
@@ -1624,7 +1574,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex-syntax 0.8.4", "regex-syntax 0.8.4",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -1658,7 +1608,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -1708,7 +1658,7 @@ checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
dependencies = [ dependencies = [
"miette-derive", "miette-derive",
"once_cell", "once_cell",
"thiserror", "thiserror 1.0.63",
"unicode-width", "unicode-width",
] ]
@@ -1720,7 +1670,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -1797,7 +1747,7 @@ dependencies = [
"raw-window-handle 0.4.3", "raw-window-handle 0.4.3",
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"raw-window-handle 0.6.2", "raw-window-handle 0.6.2",
"thiserror", "thiserror 1.0.63",
] ]
[[package]] [[package]]
@@ -1926,7 +1876,7 @@ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2091,7 +2041,7 @@ dependencies = [
"phf_shared 0.11.2", "phf_shared 0.11.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
"unicase", "unicase",
] ]
@@ -2137,7 +2087,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2159,7 +2109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"fastrand 2.1.1", "fastrand",
"futures-io", "futures-io",
] ]
@@ -2184,12 +2134,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "portable-atomic"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@@ -2205,17 +2149,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "prisma"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5640533116b656900156ef15e22d3789edb9a71f36ec04a2678a307be243495"
dependencies = [
"angular-units",
"approx 0.3.2",
"num-traits",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@@ -2260,7 +2193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2283,7 +2216,7 @@ dependencies = [
"itertools 0.13.0", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2343,15 +2276,6 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "random-string"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f70fd13c3024ae3f17381bb5c4d409c6dc9ea6895c08fa2147aba305bea3c4af"
dependencies = [
"fastrand 1.9.0",
]
[[package]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.4.3" version = "0.4.3"
@@ -2399,7 +2323,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"libredox", "libredox",
"thiserror", "thiserror 1.0.63",
] ]
[[package]] [[package]]
@@ -2501,12 +2425,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "scan_fmt"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248"
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@@ -2548,7 +2466,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2571,7 +2489,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2583,17 +2501,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@@ -2663,8 +2570,8 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "smithay" name = "smithay"
version = "0.3.0" version = "0.4.0"
source = "git+https://github.com/smithay/smithay.git#656178be0a19ae4c577c9c93a3d4ebfdb80e649c" source = "git+https://github.com/smithay/smithay.git#0c2230f858580b52d628087d6dae1795278b8756"
dependencies = [ dependencies = [
"appendlist", "appendlist",
"bitflags 2.6.0", "bitflags 2.6.0",
@@ -2678,17 +2585,14 @@ dependencies = [
"errno", "errno",
"gl_generator", "gl_generator",
"indexmap 2.5.0", "indexmap 2.5.0",
"lazy_static",
"libc", "libc",
"libloading", "libloading",
"once_cell",
"profiling", "profiling",
"rand", "rand",
"rustix", "rustix",
"scan_fmt",
"smallvec", "smallvec",
"tempfile", "tempfile",
"thiserror", "thiserror 1.0.63",
"tracing", "tracing",
"wayland-protocols", "wayland-protocols",
"wayland-protocols-misc", "wayland-protocols-misc",
@@ -2716,9 +2620,10 @@ checksum = "2f2b15926089e5526bb2dd738a2eb0e59034356e06eb71e1cd912358c0e62c4d"
[[package]] [[package]]
name = "stardust-xr" name = "stardust-xr"
version = "0.45.0" version = "0.45.0"
source = "git+https://github.com/StardustXR/core.git?branch=dev#c5591c713e85cc2df9c63583fb3026e6c93ccef7" source = "git+https://github.com/StardustXR/core.git?branch=dev#bce6ec660f026c577156b3cff41f9312c1caa1d3"
dependencies = [ dependencies = [
"cluFlock", "cluFlock",
"color-eyre",
"dirs", "dirs",
"global_counter", "global_counter",
"mint", "mint",
@@ -2728,7 +2633,7 @@ dependencies = [
"serde", "serde",
"shiva-color-rs", "shiva-color-rs",
"stardust-xr-schemas", "stardust-xr-schemas",
"thiserror", "thiserror 1.0.63",
"tokio", "tokio",
"tracing", "tracing",
] ]
@@ -2736,7 +2641,7 @@ dependencies = [
[[package]] [[package]]
name = "stardust-xr-schemas" name = "stardust-xr-schemas"
version = "1.5.3" version = "1.5.3"
source = "git+https://github.com/StardustXR/core.git?branch=dev#c5591c713e85cc2df9c63583fb3026e6c93ccef7" source = "git+https://github.com/StardustXR/core.git?branch=dev#bce6ec660f026c577156b3cff41f9312c1caa1d3"
dependencies = [ dependencies = [
"flatbuffers", "flatbuffers",
"flexbuffers", "flexbuffers",
@@ -2744,10 +2649,10 @@ dependencies = [
"futures-util", "futures-util",
"kdl", "kdl",
"manifest-dir-macros", "manifest-dir-macros",
"random-string", "nanoid",
"serde", "serde",
"serde_repr", "serde_repr",
"thiserror", "thiserror 1.0.63",
"tokio", "tokio",
"zbus", "zbus",
] ]
@@ -2759,19 +2664,15 @@ dependencies = [
"clap", "clap",
"color-eyre", "color-eyre",
"console-subscriber", "console-subscriber",
"dashmap",
"directories", "directories",
"glam", "glam",
"global_counter", "global_counter",
"input-event-codes", "input-event-codes",
"lazy_static", "lazy_static",
"libc",
"mint", "mint",
"nanoid", "nanoid",
"nix 0.29.0",
"once_cell",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"portable-atomic",
"prisma",
"rand", "rand",
"rustc-hash", "rustc-hash",
"send_wrapper", "send_wrapper",
@@ -2782,6 +2683,7 @@ dependencies = [
"stardust-xr", "stardust-xr",
"stardust-xr-server-codegen", "stardust-xr-server-codegen",
"stereokit-rust", "stereokit-rust",
"thiserror 2.0.9",
"tokio", "tokio",
"toml", "toml",
"tracing", "tracing",
@@ -2802,7 +2704,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"split-iter", "split-iter",
"stardust-xr-schemas", "stardust-xr",
] ]
[[package]] [[package]]
@@ -2814,12 +2716,12 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "stereokit-macros" name = "stereokit-macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/technobaboo/StereoKit-rust.git#93829181d5eea954e15dba9d9bfb0ec62e98d400" source = "git+https://github.com/mvvvv/StereoKit-rust.git?rev=73ffaae6f42aa369e599a6ea0391f77840d682d8#73ffaae6f42aa369e599a6ea0391f77840d682d8"
[[package]] [[package]]
name = "stereokit-rust" name = "stereokit-rust"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/technobaboo/StereoKit-rust.git#93829181d5eea954e15dba9d9bfb0ec62e98d400" source = "git+https://github.com/mvvvv/StereoKit-rust.git?rev=73ffaae6f42aa369e599a6ea0391f77840d682d8#73ffaae6f42aa369e599a6ea0391f77840d682d8"
dependencies = [ dependencies = [
"android-activity", "android-activity",
"android_logger", "android_logger",
@@ -2833,7 +2735,7 @@ dependencies = [
"ndk-sys", "ndk-sys",
"openxr-sys", "openxr-sys",
"stereokit-macros", "stereokit-macros",
"thiserror", "thiserror 2.0.9",
] ]
[[package]] [[package]]
@@ -2871,7 +2773,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2887,9 +2789,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.77" version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2915,7 +2817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand 2.1.1", "fastrand",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@@ -2938,7 +2840,16 @@ version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 1.0.63",
]
[[package]]
name = "thiserror"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
"thiserror-impl 2.0.9",
] ]
[[package]] [[package]]
@@ -2949,7 +2860,18 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
]
[[package]]
name = "thiserror-impl"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
] ]
[[package]] [[package]]
@@ -2998,7 +2920,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -3151,7 +3073,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -3164,6 +3086,16 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.2.0" version = "0.2.0"
@@ -3230,12 +3162,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "uds_windows" name = "uds_windows"
version = "1.1.0" version = "1.1.0"
@@ -3339,9 +3265,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.32.4" version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"wayland-backend", "wayland-backend",
@@ -3388,9 +3314,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-server" name = "wayland-server"
version = "0.31.5" version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f18d47038c0b10479e695d99ed073e400ccd9bdbb60e6e503c96f62adcb12b6" checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"downcast-rs", "downcast-rs",
@@ -3473,7 +3399,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -3484,7 +3410,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
@@ -3738,6 +3664,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winnow"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "xdg-home" name = "xdg-home"
version = "1.3.0" version = "1.3.0"
@@ -3776,7 +3711,7 @@ dependencies = [
"phf_shared 0.11.2", "phf_shared 0.11.2",
"strum", "strum",
"strum_macros", "strum_macros",
"thiserror", "thiserror 1.0.63",
"unicase", "unicase",
"xkbcommon-rs-codegen", "xkbcommon-rs-codegen",
"xkeysym", "xkeysym",
@@ -3809,9 +3744,9 @@ checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601"
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "4.4.0" version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
@@ -3826,20 +3761,18 @@ dependencies = [
"enumflags2", "enumflags2",
"event-listener", "event-listener",
"futures-core", "futures-core",
"futures-sink", "futures-lite",
"futures-util",
"hex", "hex",
"nix 0.29.0", "nix 0.29.0",
"ordered-stream", "ordered-stream",
"rand",
"serde", "serde",
"serde_repr", "serde_repr",
"sha1",
"static_assertions", "static_assertions",
"tokio", "tokio",
"tracing", "tracing",
"uds_windows", "uds_windows",
"windows-sys 0.52.0", "windows-sys 0.59.0",
"winnow 0.7.4",
"xdg-home", "xdg-home",
"zbus_macros", "zbus_macros",
"zbus_names", "zbus_names",
@@ -3848,22 +3781,24 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "4.4.0" version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
dependencies = [ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
"zbus_names",
"zvariant",
"zvariant_utils", "zvariant_utils",
] ]
[[package]] [[package]]
name = "zbus_names" name = "zbus_names"
version = "3.0.0" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" checksum = "cdc27fbd3593ff015cef906527a2ec4115e2e3dbf6204a24d952ac4975c80614"
dependencies = [ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
@@ -3888,42 +3823,46 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
] ]
[[package]] [[package]]
name = "zvariant" name = "zvariant"
version = "4.2.0" version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" checksum = "c690a1da8858fd4377b8cc3134a753b0bea1d8ebd78ad6e5897fab821c5e184e"
dependencies = [ dependencies = [
"endi", "endi",
"enumflags2", "enumflags2",
"serde", "serde",
"static_assertions", "static_assertions",
"zvariant_derive", "zvariant_derive",
"zvariant_utils",
] ]
[[package]] [[package]]
name = "zvariant_derive" name = "zvariant_derive"
version = "4.2.0" version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" checksum = "83b6ddc1fed08493e4f2bd9350e7d00a3383467228735f3f169a9f8820fde755"
dependencies = [ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.87",
"zvariant_utils", "zvariant_utils",
] ]
[[package]] [[package]]
name = "zvariant_utils" name = "zvariant_utils"
version = "2.1.0" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "serde",
"static_assertions",
"syn 2.0.87",
"winnow 0.7.4",
] ]

View File

@@ -1,6 +1,6 @@
[package] [package]
edition = "2021" edition = "2024"
rust-version = "1.75" rust-version = "1.85"
name = "stardust-xr-server" name = "stardust-xr-server"
version = "0.45.0" version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"] authors = ["Nova King <technobaboo@proton.me>"]
@@ -12,6 +12,10 @@ homepage = "https://stardustxr.org"
[workspace] [workspace]
members = ["codegen"] members = ["codegen"]
[workspace.dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
branch = "dev"
[[bin]] [[bin]]
name = "stardust-xr-server" name = "stardust-xr-server"
path = "src/main.rs" path = "src/main.rs"
@@ -37,25 +41,24 @@ auto_link_exclude_list = [
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
debug = true debug = true
strip = "none" strip = false
debug-assertions = true debug-assertions = true
overflow-checks = true overflow-checks = true
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
debug = "line-tables-only" debug = "line-tables-only"
strip = "none" strip = true
debug-assertions = true debug-assertions = true
overflow-checks = false overflow-checks = false
lto = "thin"
[dependencies] [dependencies]
# small utility thingys # small utility thingys
once_cell = "1.19.0"
nanoid = "0.4.0" nanoid = "0.4.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
rand = "0.8.5" rand = "0.8.5"
rustc-hash = "2.0.0" rustc-hash = "2.0.0"
portable-atomic = { version = "1.7.0", features = ["float", "std"] }
send_wrapper = "0.6.0" send_wrapper = "0.6.0"
slotmap = "1.0.7" slotmap = "1.0.7"
global_counter = "=0.2.2" global_counter = "=0.2.2"
@@ -65,6 +68,7 @@ parking_lot = "0.12.3"
color-eyre = { version = "0.6.3", default-features = false } color-eyre = { version = "0.6.3", default-features = false }
clap = { version = "4.5.13", features = ["derive"] } clap = { version = "4.5.13", features = ["derive"] }
console-subscriber = { version = "0.4.0", optional = true } console-subscriber = { version = "0.4.0", optional = true }
thiserror = "2.0.9"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true } tracing-tracy = { version = "0.11.1", optional = true }
@@ -75,43 +79,36 @@ serde_repr = "0.1.19"
toml = "0.8.19" toml = "0.8.19"
# mathy stuffs # mathy stuffs
glam = { version = "0.28.0", features = ["mint", "serde"] } glam = { version = "0.29.0", features = ["mint", "serde"] }
mint = "0.5.9" mint = "0.5.9"
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] } tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
prisma = "0.1.1"
# linux stuffs # linux stuffs
libc = "0.2.155"
nix = "0.29.0"
input-event-codes = "6.2.0" input-event-codes = "6.2.0"
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] } zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
directories = "5.0.1" directories = "5.0.1"
xkbcommon-rs = "0.1.0" xkbcommon-rs = "0.1.0"
# wayland # wayland
wayland-backend = { version = "0.3.7", optional = true, default-features = false } wayland-backend = { version = "0.3.7", optional = true, default-features = false }
wayland-scanner = { version = "0.31.4", optional = true } wayland-scanner = { version = "0.31.4", optional = true }
dashmap = "6.1.0"
[dependencies.smithay] [dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git"
# git = "https://github.com/colinmarc/smithay.git"
git = "https://github.com/smithay/smithay.git" git = "https://github.com/smithay/smithay.git"
# path = "../smithay"
default-features = false default-features = false
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"] features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
optional = true optional = true
[dependencies.stereokit-rust] [dependencies.stereokit-rust]
# path = "../StereoKit-rust" git = "https://github.com/mvvvv/StereoKit-rust.git"
# git = "https://github.com/mvvvv/StereoKit-rust.git" rev = "73ffaae6f42aa369e599a6ea0391f77840d682d8"
git = "https://github.com/technobaboo/StereoKit-rust.git"
features = ["no-event-loop"] features = ["no-event-loop"]
default-features = false default-features = false
[dependencies.stardust-xr] [dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git" workspace = true
branch = "dev"
[dependencies.stardust-xr-server-codegen] [dependencies.stardust-xr-server-codegen]
path = "codegen" path = "codegen"

154
README.md
View File

@@ -1,85 +1,113 @@
# 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 Command line installation of core & dynamic dependencies are provided below:
3. EGL+GLES 3.2 <details>
4. GLX+Xlib <summary>Ubuntu/Debian</summary>
5. fontconfig <pre><code class="language-bash">
6. dlopen sudo apt update && sudo apt install \
7. OpenXR Loader (required even if run in flatscreen mode) build-essential \
cargo \
cmake \
libxkbcommon-dev libudev1 libinput10 libcap2 libmtdev1 libevdev2 libwacom9 libgudev-1.0-0 \
libglib2.0-dev libffi8 libpcre2-dev libxkbcommon-x11-dev libxcb-dev libxcb-xkb-dev libxau-dev \
libstdc++-dev libx11-dev libxfixes-dev libegl-dev libgbm-dev libfontconfig1-dev libgl-dev \
libdrm-dev libexpat1-dev libfreetype6-dev libxml2-dev zlib1g-dev libbz2-dev libpng-dev \
libharfbuzz-dev libbrotli-dev liblzma-dev libraphite2-dev
</code></pre>
</details>
<details>
<summary>Fedora</summary>
<pre><code class="language-bash">
sudo apt update && sudo apt install \
cargo \
cmake \
libxkbcommon-devel systemd-devel libinput-devel libcap-devel mtdev-devel libevdev-devel glib2-devel \
libffi-devel pcre2-devel libxkbcommon-x11-devel libxcb-devel libXau-devel libstdc++-devel libx11-devel libxfixes-devel \
mesa-libEGL-devel mesa-libgbm-devel fontconfig-devel libdrm-devel expat-devel freetype-devel libxml2-devel zlib-devel \
bzip2-devel libpng-devel harfbuzz-devel brotli-devel xz-devel graphite2-devel
</code></pre>
</details>
<details>
<summary>Arch Linux</summary>
<pre><code class="language-bash">
sudo pacman -Syu --needed \
cargo \
cmake \
libxkbcommon systemd libinput libcap mtdev libevdev libwacom glib2 libffi pcre2 libxkbcommon-x11 \
libxcb libxau libx11 libxfixes mesa fontconfig libdrm expat freetype2 libxml2 zlib bzip2 \
libpng harfbuzz brotli xz graphite
</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:
## Build
```bash ```bash
cargo build 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
cargo build --release # this is needed to skip validation layers
``` ```
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,6 +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"
branch = "dev"

View File

@@ -2,7 +2,7 @@ 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)
@@ -25,10 +25,6 @@ pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::To
codegen_protocol(FIELD_PROTOCOL) codegen_protocol(FIELD_PROTOCOL)
} }
#[proc_macro] #[proc_macro]
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DATA_PROTOCOL)
}
#[proc_macro]
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(AUDIO_PROTOCOL) codegen_protocol(AUDIO_PROTOCOL)
} }
@@ -64,11 +60,28 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
}; };
let aspect = generate_aspect(&Aspect { let aspect = generate_aspect(&Aspect {
name: "interface".to_string(), name: "interface".to_string(),
id: 0,
description: protocol.description.clone(), description: protocol.description.clone(),
inherits: vec![], inherits: vec![],
members: p.members, members: p.members,
}); });
quote!(#node_id #aspect) quote! {
#node_id
#aspect
pub struct Interface;
impl crate::nodes::AspectIdentifier for Interface {
impl_aspect_for_interface_aspect_id!{}
}
impl crate::nodes::Aspect for Interface {
impl_aspect_for_interface_aspect!{}
}
pub fn create_interface(client: &std::sync::Arc<crate::core::client::Client>) -> crate::core::error::Result<()>{
let node = crate::nodes::Node::from_id(client,INTERFACE_NODE_ID,false);
node.add_aspect(Interface);
node.add_to_scenegraph()?;
Ok(())
}
}
}) })
.unwrap_or_default(); .unwrap_or_default();
let custom_enums = protocol let custom_enums = protocol
@@ -130,7 +143,7 @@ fn generate_custom_union(custom_union: &CustomUnion) -> TokenStream {
quote! { quote! {
#[doc = #description] #[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(untagged)] #[serde(tag = "t", content = "c")]
pub enum #name {#option_decls} pub enum #name {#option_decls}
} }
} }
@@ -177,11 +190,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
Span::call_site(), Span::call_site(),
); );
let client_side_members = client_members let client_side_members = client_members
.map(generate_member) .map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.map(|t| { .map(|t| {
// TODO: properly import all dependencies // TODO: properly import all dependencies
quote! { quote! {
#[allow(clippy::all)]
pub mod #client_mod_name { pub mod #client_mod_name {
use super::*; use super::*;
#t #t
@@ -190,11 +204,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
}) })
.unwrap_or_default(); .unwrap_or_default();
let aspect_trait_name = Ident::new(
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let opcodes = aspect let opcodes = aspect
.members .members
.iter() .iter()
@@ -219,31 +228,95 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
let alias_info = generate_alias_info(aspect); let alias_info = generate_alias_info(aspect);
let server_side_members = server_members let server_side_members = server_members
.map(generate_member) .map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .unwrap_or_default();
let add_node_members = aspect let aspect_trait_name = Ident::new(
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let run_signals = aspect
.members .members
.iter() .iter()
.filter(|m| m.side == Side::Server) .filter(|m| m.side == Side::Server)
.map(generate_handler) .filter(|m| m._type == MemberType::Signal)
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Signal, m))
.reduce(fold_tokens)
.unwrap_or_default();
let run_methods = aspect
.members
.iter()
.filter(|m| m.side == Side::Server)
.filter(|m| m._type == MemberType::Method)
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Method, m))
.reduce(fold_tokens) .reduce(fold_tokens)
.map(|members| {
quote! {
fn add_node_members(node: &crate::nodes::Node) {
#members
}
}
})
.unwrap_or_default(); .unwrap_or_default();
let server_side_members = quote! { let server_side_members = quote! {
#[allow(clippy::all)]
#[doc = #description] #[doc = #description]
pub trait #aspect_trait_name { pub trait #aspect_trait_name {
#add_node_members
#server_side_members #server_side_members
} }
}; };
quote!(#opcodes #alias_info #client_side_members #server_side_members) let aspect_id_macro_name = Ident::new(
&format!(
"impl_aspect_for_{}_aspect_id",
aspect.name.to_case(Case::Snake)
),
Span::call_site(),
);
let aspect_macro_name = Ident::new(
&format!(
"impl_aspect_for_{}_aspect",
aspect.name.to_case(Case::Snake)
),
Span::call_site(),
);
let aspect_id = aspect.id;
let aspect_macro = quote! {
macro_rules! #aspect_id_macro_name {
() => {
const ID: u64 = #aspect_id;
}
}
macro_rules! #aspect_macro_name {
() => {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
#[allow(clippy::all)]
fn run_signal(
&self,
_calling_client: std::sync::Arc<crate::core::client::Client>,
_node: std::sync::Arc<crate::nodes::Node>,
_signal: u64,
_message: crate::nodes::Message
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
match _signal {
#run_signals
_ => Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound)
}
}
#[allow(clippy::all)]
fn run_method(
&self,
_calling_client: std::sync::Arc<crate::core::client::Client>,
_node: std::sync::Arc<crate::nodes::Node>,
_method: u64,
_message: crate::nodes::Message,
_method_response: crate::nodes::MethodResponseSender,
) {
match _method {
#run_methods
_ => {
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound));
}
}
}
};
}
};
quote!(#opcodes #alias_info #client_side_members #server_side_members #aspect_macro)
} }
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream { fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
@@ -283,6 +356,7 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
quote! { quote! {
lazy_static::lazy_static! { lazy_static::lazy_static! {
#[allow(clippy::all)]
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo { pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
server_signals: vec![#local_signals], server_signals: vec![#local_signals],
server_methods: vec![#local_methods], server_methods: vec![#local_methods],
@@ -293,8 +367,8 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
} }
} }
fn generate_member(member: &Member) -> TokenStream { fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenStream {
let id = member.opcode; let opcode = 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;
@@ -324,40 +398,53 @@ fn generate_member(member: &Member) -> TokenStream {
.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!(()));
let name_str = name.to_string();
match (side, _type) { match (side, _type) {
(Side::Client, MemberType::Method) => {
quote! {
#[doc = #description]
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
_node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
}
}
}
(Side::Client, MemberType::Signal) => { (Side::Client, MemberType::Signal) => {
quote! { quote! {
#[doc = #description] #[doc = #description]
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> { pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
_node.send_remote_signal(#id, serialized) let result = stardust_xr::schemas::flex::serialize((#argument_uses)).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to send remote signal: {}::{}, error: {}",#aspect_name,#name_str,err);
} else {
::tracing::trace!("sent remote signal: {}::{}",#aspect_name,#name_str);
}
result
} }
} }
} }
(Side::Server, MemberType::Method) => { (Side::Client, MemberType::Method) => {
quote! { quote! {
#[doc = #description] #[doc = #description]
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static; pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await;
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err);
} else {
::tracing::trace!("called remote method: {}::{}",#aspect_name,#name_str);
}
result
}
} }
} }
(Side::Server, MemberType::Signal) => { (Side::Server, MemberType::Signal) => {
quote! { quote! {
#[doc = #description] #[doc = #description]
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>; fn #name(#argument_decls) -> crate::core::error::Result<()>;
}
}
(Side::Server, MemberType::Method) => {
quote! {
#[doc = #description]
fn #name(#argument_decls) -> impl std::future::Future<Output = crate::core::error::Result<#return_type>> + Send + Sync + 'static;
} }
} }
} }
} }
fn generate_handler(member: &Member) -> TokenStream { fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
let opcode = member.opcode; 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());
@@ -379,7 +466,10 @@ fn generate_handler(member: &Member) -> TokenStream {
.clone() .clone()
.zip(argument_types) .zip(argument_types)
.map(|(argument_names, argument_types)| { .map(|(argument_names, argument_types)| {
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;) quote!{
#[allow(unused_parens)]
let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;
}
}) })
.unwrap_or_default(); .unwrap_or_default();
let serialize = generate_argument_serialize( let serialize = generate_argument_serialize(
@@ -393,21 +483,34 @@ fn generate_handler(member: &Member) -> TokenStream {
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional)) .map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b)) .reduce(|a, b| quote!(#a, #b))
.unwrap_or_default(); .unwrap_or_default();
match member._type { let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
match _type {
MemberType::Signal => quote! { MemberType::Signal => quote! {
node.add_local_signal(#opcode, |_node, _calling_client, _message| { #opcode => { let result = (move || {
#deserialize #deserialize
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses) <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
}); })().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() });
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to receive local signal: {}::{}, error: {}",#aspect_name_str,#member_name,err);
} else {
::tracing::trace!("received local signal: {}::{}",#aspect_name_str,#member_name);
}
result
},
}, },
MemberType::Method => quote! { MemberType::Method => quote! {
node.add_local_method(#opcode, |_node, _calling_client, _message, _method_response| { #opcode => _method_response.wrap_async(async move {
_method_response.wrap_async(async move {
#deserialize #deserialize
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?; let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
Ok((#serialize, Vec::new())) if let Err(err) = result.as_ref() {
}); ::tracing::warn!("failed to call local method: {}::{}, error: {}",#aspect_name_str,#member_name,err);
}); } else {
::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name);
};
let result = result?;
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
}),
}, },
} }
} }
@@ -441,18 +544,18 @@ fn generate_argument_deserialize(
} }
if optional { if optional {
let mapping = generate_argument_deserialize("o", argument_type, false); let mapping = generate_argument_deserialize("o", argument_type, false);
return quote!(#name.map(|o| Ok::<_, color_eyre::eyre::Report>(#mapping)).transpose()?); return quote!(#name.map(|o| Ok::<_, crate::core::error::ServerError>(#mapping)).transpose()?);
} }
match argument_type { match argument_type {
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);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?) quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
} }
ArgumentType::Map(v) => { ArgumentType::Map(v) => {
let mapping = generate_argument_deserialize("a", v, false); let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?) quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
} }
_ => quote!(#name), _ => quote!(#name),
} }
@@ -474,11 +577,11 @@ fn generate_argument_serialize(
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) => {
let mapping = generate_argument_serialize("a", v, false); let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?) quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
} }
ArgumentType::Map(v) => { ArgumentType::Map(v) => {
let mapping = generate_argument_serialize("a", v, false); let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?) quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
} }
_ => quote!(#name), _ => quote!(#name),
} }
@@ -511,6 +614,7 @@ fn argument_type_option_name(argument_type: &ArgumentType) -> String {
ArgumentType::Union(u) => u.clone(), ArgumentType::Union(u) => u.clone(),
ArgumentType::Struct(s) => s.clone(), ArgumentType::Struct(s) => s.clone(),
ArgumentType::Node { _type, .. } => _type.clone(), ArgumentType::Node { _type, .. } => _type.clone(),
ArgumentType::Fd => "File Descriptor".to_string(),
} }
} }
fn generate_argument_type( fn generate_argument_type(
@@ -607,6 +711,9 @@ fn generate_argument_type(
quote!(std::sync::Arc<crate::nodes::Node>) quote!(std::sync::Arc<crate::nodes::Node>)
} }
} }
ArgumentType::Fd => {
quote!(&std::os::fd::OwnedFd)
}
}; };
if optional { if optional {

135
flake.nix
View File

@@ -15,13 +15,21 @@
flatland.url = "github:StardustXR/flatland"; flatland.url = "github:StardustXR/flatland";
}; };
outputs = outputs =
inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }: 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 (n: builtins.baseNameOf path != n) [ nixpkgs.lib.all (n: builtins.baseNameOf path != n) [
"flake.nix" "flake.nix"
"flake.lock" "flake.lock"
@@ -29,63 +37,84 @@
"README.md" "README.md"
]; ];
}; };
in flake-parts.lib.mkFlake { inherit inputs; } { in
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 = [
perSystem = { config, self', inputs', pkgs, system, ... }: { "aarch64-linux"
_module.args.pkgs = import inputs.nixpkgs { "x86_64-linux"
inherit system; "riscv64-linux"
overlays = [ inputs.self.overlays.default ]; ];
}; perSystem =
overlayAttrs = config.packages; {
packages = let sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { }; config,
in { self',
default = self'.packages.${name}; inputs',
gnome-graphical-test = self'.checks.gnome-graphical-test; pkgs,
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix { system,
inherit name src sk_gpu; ...
}:
{
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ inputs.self.overlays.default ];
};
overlayAttrs = config.packages;
packages =
let
sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
in
{
default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
inherit name src sk_gpu;
};
};
apps.default = {
type = "app";
program = self'.packages.${name} + "/bin/stardust-xr-server";
};
checks.gnome-graphical-test = pkgs.nixosTest (
import ./nix/gnome-graphical-test.nix { inherit pkgs self; }
);
devShells.default = pkgs.mkShell {
inputsFrom = [ self'.packages.default ];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
}; };
}; };
apps.default = {
type = "app";
program = self'.packages.${name} + "/bin/stardust-xr-server";
};
checks.gnome-graphical-test = pkgs.nixosTest
(import ./nix/gnome-graphical-test.nix { inherit pkgs self; });
devShells.default = pkgs.mkShell {
inputsFrom = [ self'.packages.default ];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
};
};
flake = { flake = {
herculesCI.ciSystems = [ "x86_64-linux" ]; herculesCI.ciSystems = [ "x86_64-linux" ];
effects = let effects =
pkgs = nixpkgs.legacyPackages.x86_64-linux; let
hci-effects = hercules-ci-effects.lib.withPkgs pkgs; pkgs = nixpkgs.legacyPackages.x86_64-linux;
in { ref, rev, ... }: { hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
gnome-graphical-test = hci-effects.mkEffect { in
secretsMap."stardustxrDiscord" = "stardustxrDiscord"; { ref, rev, ... }:
secretsMap."stardustxrIpfs" = "stardustxrIpfs"; {
effectScript = '' gnome-graphical-test = hci-effects.mkEffect {
readSecretString stardustxrDiscord .webhook > .webhook secretsMap."stardustxrDiscord" = "stardustxrDiscord";
readSecretString stardustxrIpfs .basicauth > .basicauth secretsMap."stardustxrIpfs" = "stardustxrIpfs";
set -x effectScript = ''
export RESPONSE=$(curl -H @.basicauth -F file=@${ readSecretString stardustxrDiscord .webhook > .webhook
self.packages."x86_64-linux".gnome-graphical-test readSecretString stardustxrIpfs .basicauth > .basicauth
}/screen.png https://ipfs-api.stardustxr.org/api/v0/add) set -x
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash) export RESPONSE=$(curl -H @.basicauth -F file=@${
set +x self.packages."x86_64-linux".gnome-graphical-test
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID" }/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
${pkgs.discord-sh}/bin/discord.sh \ export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \ set +x
--field "Ref;${ref}" \ export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
--field "Commit ID;${rev}" \ ${pkgs.discord-sh}/bin/discord.sh \
--field "Flatland Revision;${flatland.rev}" \ --description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \ --field "Ref;${ref}" \
--image "$ADDRESS" --field "Commit ID;${rev}" \
''; --field "Flatland Revision;${flatland.rev}" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
--image "$ADDRESS"
'';
};
}; };
};
}; };
}; };
} }

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

12
justfile Normal file
View File

@@ -0,0 +1,12 @@
PREFIX := "usr"
BINARY := PREFIX / "bin"
DESTDIR := "/"
build:
cargo build --release
test:
cargo test
install:
install -Dm755 target/release/stardust-xr-server {{ DESTDIR }}{{ BINARY }}/stardust-xr-server

View File

@@ -1,46 +1,57 @@
use super::{ use super::{
client_state::{ClientStateParsed, CLIENT_STATES}, client_state::{CLIENT_STATES, ClientStateParsed},
destroy_queue, destroy_queue,
scenegraph::Scenegraph, scenegraph::Scenegraph,
}; };
use crate::{ use crate::{
core::{registry::OwnedRegistry, task}, core::{registry::OwnedRegistry, task},
nodes::{ nodes::{
audio, data, drawable, fields, input, items, Node, audio, drawable, fields, input, items,
root::{ClientState, Root}, root::{ClientState, Root},
spatial, Node, spatial,
}, },
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{Result, eyre};
use global_counter::primitive::exact::CounterU32; use global_counter::primitive::exact::CounterU32;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use stardust_xr::messenger::{self, MessageSenderHandle}; use stardust_xr::messenger::{self, MessageSenderHandle};
use std::{fmt::Debug, fs, iter::FromIterator, path::PathBuf, sync::Arc}; use std::{
use tokio::{net::UnixStream, task::JoinHandle}; fmt::Debug,
fs,
iter::FromIterator,
path::PathBuf,
sync::{Arc, OnceLock},
time::Instant,
};
use tokio::{net::UnixStream, sync::watch, task::JoinHandle};
use tracing::info; use tracing::info;
lazy_static! { lazy_static! {
pub static ref CLIENTS: OwnedRegistry<Client> = OwnedRegistry::new(); pub static ref CLIENTS: OwnedRegistry<Client> = OwnedRegistry::new();
static ref INTERNAL_CLIENT_MESSAGE_TIMES: (watch::Sender<Instant>, watch::Receiver<Instant>) = watch::channel(Instant::now());
pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client { pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client {
pid: None, pid: None,
// env: None, // env: None,
exe: None, exe: None,
dispatch_join_handle: OnceCell::new(), dispatch_join_handle: OnceLock::new(),
flush_join_handle: OnceCell::new(), flush_join_handle: OnceLock::new(),
disconnect_status: OnceCell::new(), disconnect_status: OnceLock::new(),
message_sender_handle: None,
id_counter: CounterU32::new(0), id_counter: CounterU32::new(0),
message_last_received: INTERNAL_CLIENT_MESSAGE_TIMES.1.clone(),
message_sender_handle: None,
scenegraph: Default::default(), scenegraph: Default::default(),
root: OnceCell::new(), root: OnceLock::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
state: OnceCell::default(), state: OnceLock::default(),
}); });
} }
pub fn tick_internal_client() {
let _ = INTERNAL_CLIENT_MESSAGE_TIMES.0.send(Instant::now());
}
pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> { pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
let env = fs::read_to_string(format!("/proc/{pid}/environ"))?; let env = fs::read_to_string(format!("/proc/{pid}/environ"))?;
@@ -59,16 +70,17 @@ pub struct Client {
pub pid: Option<i32>, pub pid: Option<i32>,
// env: Option<FxHashMap<String, String>>, // env: Option<FxHashMap<String, String>>,
exe: Option<PathBuf>, exe: Option<PathBuf>,
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>, dispatch_join_handle: OnceLock<JoinHandle<Result<()>>>,
flush_join_handle: OnceCell<JoinHandle<Result<()>>>, flush_join_handle: OnceLock<JoinHandle<Result<()>>>,
disconnect_status: OnceCell<Result<()>>, disconnect_status: OnceLock<Result<()>>,
id_counter: CounterU32, id_counter: CounterU32,
message_last_received: watch::Receiver<Instant>,
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: OnceLock<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>, pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub state: OnceCell<ClientState>, pub state: OnceLock<ClientState>,
} }
impl Client { impl Client {
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> { pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
@@ -90,21 +102,23 @@ impl Client {
.and_then(state) .and_then(state)
.unwrap_or_else(|| Arc::new(ClientStateParsed::default())); .unwrap_or_else(|| Arc::new(ClientStateParsed::default()));
let (message_time_tx, message_last_received) = watch::channel(Instant::now());
let client = CLIENTS.add(Client { let client = CLIENTS.add(Client {
pid, pid,
// env, // env,
exe: exe.clone(), exe: exe.clone(),
dispatch_join_handle: OnceCell::new(), dispatch_join_handle: OnceLock::new(),
flush_join_handle: OnceCell::new(), flush_join_handle: OnceLock::new(),
disconnect_status: OnceCell::new(), disconnect_status: OnceLock::new(),
id_counter: CounterU32::new(256), id_counter: CounterU32::new(256),
message_last_received,
message_sender_handle: Some(messenger_tx.handle()), message_sender_handle: Some(messenger_tx.handle()),
scenegraph: scenegraph.clone(), scenegraph: scenegraph.clone(),
root: OnceCell::new(), root: OnceLock::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
state: OnceCell::default(), state: OnceLock::default(),
}); });
let _ = client.scenegraph.client.set(Arc::downgrade(&client)); let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client, state.root)?); let _ = client.root.set(Root::create(&client, state.root)?);
@@ -112,7 +126,6 @@ impl 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)?;
input::create_interface(&client)?; input::create_interface(&client)?;
items::camera::create_interface(&client)?; items::camera::create_interface(&client)?;
items::panel::create_interface(&client)?; items::panel::create_interface(&client)?;
@@ -129,7 +142,7 @@ impl Client {
.map(|exe| exe.to_string()) .map(|exe| exe.to_string())
}) })
.unwrap_or_else(|| "??".to_string()); .unwrap_or_else(|| "??".to_string());
let _ = client.dispatch_join_handle.get_or_try_init(|| { let _ = client.dispatch_join_handle.get_or_init(|| {
task::new( task::new(
|| { || {
format!( format!(
@@ -144,12 +157,14 @@ impl Client {
if let Err(e) = messenger_rx.dispatch(&*scenegraph).await { if let Err(e) = messenger_rx.dispatch(&*scenegraph).await {
client.disconnect(Err(e.into())); client.disconnect(Err(e.into()));
} }
let _ = message_time_tx.send(Instant::now());
} }
} }
}, },
) )
.unwrap()
}); });
let _ = client.flush_join_handle.get_or_try_init(|| { let _ = client.flush_join_handle.get_or_init(|| {
task::new( task::new(
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,), || format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
{ {
@@ -163,6 +178,7 @@ impl Client {
} }
}, },
) )
.unwrap()
}); });
Ok(client) Ok(client)
@@ -200,6 +216,11 @@ impl Client {
.ok_or_else(|| eyre!("{} not found", name)) .ok_or_else(|| eyre!("{} not found", name))
} }
pub fn unresponsive(&self) -> bool {
let time_since_last_message = self.message_last_received.borrow().elapsed();
time_since_last_message.as_millis() > 500
}
pub fn disconnect(&self, reason: Result<()>) { pub fn disconnect(&self, reason: Result<()>) {
let _ = self.disconnect_status.set(reason); let _ = self.disconnect_status.set(reason);
if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() { if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() {

View File

@@ -1,5 +1,5 @@
use super::client::{get_env, Client}; use super::client::{Client, get_env};
use crate::nodes::{root::ClientState, spatial::Spatial, Node}; use crate::nodes::{Node, root::ClientState, spatial::Spatial};
use glam::Mat4; use glam::Mat4;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@@ -69,7 +69,7 @@ impl ClientStateParsed {
let app_name = self let app_name = self
.launch_info .launch_info
.as_ref() .as_ref()
.map(|l| l.cmdline.first().unwrap().split('/').last().unwrap()) .map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap())
.unwrap_or("unknown"); .unwrap_or("unknown");
let state_file_path = directory let state_file_path = directory
.join(format!("{app_name}-{}", nanoid::nanoid!())) .join(format!("{app_name}-{}", nanoid::nanoid!()))

View File

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

75
src/core/error.rs Normal file
View File

@@ -0,0 +1,75 @@
use std::any::TypeId;
use color_eyre::eyre::Report;
use stardust_xr::{
messenger::MessengerError,
schemas::flex::{
FlexSerializeError,
flexbuffers::{DeserializationError, ReaderError},
},
};
use stereokit_rust::StereoKitError;
use thiserror::Error;
pub type Result<T, E = ServerError> = std::result::Result<T, E>;
#[derive(Error, Debug)]
pub enum ServerError {
#[error("Internal: Unable to get client")]
NoClient,
#[error("Messenger does not exist for this node")]
NoMessenger,
#[error("Messenger error: {0}")]
MessengerError(#[from] MessengerError),
#[error("Remote method error: {0}")]
RemoteMethodError(String),
#[error("Serialization error: {0}")]
SerializationError(#[from] FlexSerializeError),
#[error("Deserialization error: {0}")]
DeserializationError(#[from] DeserializationError),
#[error("Reader error: {0}")]
ReaderError(#[from] ReaderError),
#[error("StereoKit error: {0}")]
StereoKitError(#[from] StereoKitError),
#[error("Aspect {} does not exist for node", 0.to_string())]
NoAspect(TypeId),
#[error("{0}")]
Report(#[from] Report),
}
#[macro_export]
macro_rules! bail {
($msg:literal $(,)?) => {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
};
($err:expr $(,)?) => {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
};
($fmt:expr, $($arg:tt)*) => {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
};
}
#[macro_export]
macro_rules! ensure {
($cond:expr $(,)?) => {
if !$cond {
$crate::ensure!($cond, concat!("Condition failed: `", stringify!($cond), "`"))
}
};
($cond:expr, $msg:literal $(,)?) => {
if !$cond {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
}
};
($cond:expr, $err:expr $(,)?) => {
if !$cond {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if !$cond {
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
}
};
}

View File

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

View File

@@ -2,7 +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 idl_utils; pub mod error;
pub mod registry; pub mod registry;
pub mod resource; pub mod resource;
pub mod scenegraph; pub mod scenegraph;

View File

@@ -1,19 +1,18 @@
#![allow(dead_code)] #![allow(dead_code)]
use parking_lot::{const_mutex, MappedMutexGuard, Mutex, MutexGuard}; use dashmap::DashMap;
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard, const_mutex};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::ops::Deref;
use std::ptr; use std::ptr;
use std::sync::{Arc, Weak}; use std::sync::{Arc, LazyLock, Weak};
#[derive(Debug)] #[derive(Debug)]
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>); pub struct Registry<T: Send + Sync + ?Sized>(MaybeLazy<DashMap<usize, Weak<T>>>);
impl<T: Send + Sync + ?Sized> Registry<T> { impl<T: Send + Sync + ?Sized> Registry<T> {
pub const fn new() -> Self { pub const fn new() -> Self {
Registry(const_mutex(None)) Registry(MaybeLazy::Lazy(LazyLock::new(DashMap::default)))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
MutexGuard::map(self.0.lock(), |r| 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
@@ -24,30 +23,29 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
t_arc t_arc
} }
pub fn add_raw(&self, t: &Arc<T>) { pub fn add_raw(&self, t: &Arc<T>) {
self.lock() self.0
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t)); .insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t));
} }
pub fn contains(&self, t: &T) -> bool { pub fn contains(&self, t: &T) -> bool {
self.lock() self.0
.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>>) { 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 added = Vec::new();
let mut removed = Vec::new(); let mut removed = Vec::new();
for (id, entry) in new.iter() { for pair in new.0.iter() {
let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade() { if let Some(entry) = entry.upgrade() {
if !old.contains_key(id) { if !old.0.contains_key(id) {
added.push(entry); added.push(entry);
} }
} }
} }
for (id, entry) in old.iter() { for pair in old.0.iter() {
let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade() { if let Some(entry) = entry.upgrade() {
if !new.contains_key(id) { if !new.0.contains_key(id) {
removed.push(entry); removed.push(entry);
} }
} }
@@ -55,52 +53,48 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
(added, removed) (added, removed)
} }
pub fn get_valid_contents(&self) -> Vec<Arc<T>> { pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.lock() self.0
.iter() .iter()
.filter_map(|pair| pair.1.upgrade()) .filter_map(|pair| pair.value().upgrade())
.collect() .collect()
} }
pub fn set(&self, other: &Registry<T>) { pub fn set(&self, other: &Registry<T>) {
self.lock().clone_from(&other.lock()); self.clear();
for (key, value) in other.0.deref().clone().into_iter() {
self.0.insert(key, value);
}
} }
pub fn take_valid_contents(&self) -> Vec<Arc<T>> { pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
self.0 let contents = self.get_valid_contents();
.lock() self.0.clear();
.take() contents
.unwrap_or_default()
.into_iter()
.filter_map(|pair| pair.1.upgrade())
.collect()
} }
pub fn retain<F: Fn(&Arc<T>) -> bool>(&self, f: F) { pub fn retain<F: Fn(&Arc<T>) -> bool>(&self, f: F) {
self.lock().retain(|_, v| { self.0.retain(|_, v| {
let Some(v) = v.upgrade() else { let Some(v) = v.upgrade() else {
// why would we want to retain things we can't upgrade?
return true; return true;
}; };
(f)(&v) (f)(&v)
}) })
} }
pub fn remove(&self, t: &T) { pub fn remove(&self, t: &T) {
self.lock() self.0.remove(&(ptr::addr_of!(*t) as *const () as usize));
.remove(&(ptr::addr_of!(*t) as *const () as usize));
} }
pub fn clear(&self) { pub fn clear(&self) {
self.lock().clear(); self.0.clear();
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
let registry = self.0.lock(); if self.0.is_empty() {
let Some(registry) = &*registry else {
return true;
};
if registry.is_empty() {
return true; return true;
} }
registry.values().all(|v| v.strong_count() == 0) self.0.iter().all(|v| v.value().strong_count() == 0)
} }
} }
impl<T: Send + Sync + ?Sized> Clone for Registry<T> { impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone())) Self(self.0.clone())
} }
} }
impl<T: Send + Sync + ?Sized> Default for Registry<T> { impl<T: Send + Sync + ?Sized> Default for Registry<T> {
@@ -109,6 +103,40 @@ impl<T: Send + Sync + ?Sized> Default for Registry<T> {
} }
} }
impl<T: Send + Sync + Sized> FromIterator<Arc<T>> for Registry<T> {
fn from_iter<I: IntoIterator<Item = Arc<T>>>(iter: I) -> Self {
Registry(MaybeLazy::NonLazy(
iter.into_iter()
.map(|i| (Arc::as_ptr(&i) as usize, Arc::downgrade(&i)))
.collect(),
))
}
}
#[derive(Debug)]
enum MaybeLazy<T> {
Lazy(LazyLock<T>),
NonLazy(T),
}
impl<T: Clone> Clone for MaybeLazy<T> {
fn clone(&self) -> Self {
match self {
MaybeLazy::Lazy(lazy_lock) => Self::NonLazy(lazy_lock.deref().clone()),
MaybeLazy::NonLazy(v) => Self::NonLazy(v.clone()),
}
}
}
impl<T> Deref for MaybeLazy<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
MaybeLazy::Lazy(lazy_lock) => lazy_lock,
MaybeLazy::NonLazy(v) => v,
}
}
}
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>); pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
impl<T: Send + Sync + ?Sized> OwnedRegistry<T> { impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {

View File

@@ -1,8 +1,7 @@
use crate::nodes::alias::get_original; use crate::core::error::Result;
use crate::nodes::Node; use crate::nodes::Node;
use crate::nodes::alias::get_original;
use crate::{core::client::Client, nodes::Message}; use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Serialize; use serde::Serialize;
@@ -11,13 +10,13 @@ use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::serialize; use stardust_xr::schemas::flex::serialize;
use std::future::Future; use std::future::Future;
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak}; use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tracing::{debug, debug_span}; use tracing::{debug, debug_span};
#[derive(Default)] #[derive(Default)]
pub struct Scenegraph { pub struct Scenegraph {
pub(super) client: OnceCell<Weak<Client>>, pub(super) client: OnceLock<Weak<Client>>,
nodes: Mutex<FxHashMap<u64, Arc<Node>>>, nodes: Mutex<FxHashMap<u64, Arc<Node>>>,
} }
@@ -59,26 +58,26 @@ impl MethodResponseSender {
// ) { // ) {
// let _ = self.0.send(map_method_return(result)); // let _ = self.0.send(map_method_return(result));
// } // }
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) { pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MethodError { self.send(f().map_err(|e| ScenegraphError::MemberError {
error: e.to_string(), error: e.to_string(),
})) }))
} }
pub fn wrap_async<T: Serialize>( pub fn wrap_async<T: Serialize>(
self, self,
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static, f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) { ) {
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) }); tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
} }
} }
fn map_method_return<T: Serialize>( fn map_method_return<T: Serialize>(
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>, result: Result<(T, Vec<OwnedFd>)>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> { ) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError { let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
error: e.to_string(), error: e.to_string(),
})?; })?;
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError { let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
error: format!("Internal: Serialization failed: {e}"), error: format!("Internal: Serialization failed: {e}"),
})?; })?;
Ok((serialized_value, fds)) Ok((serialized_value, fds))
@@ -86,19 +85,21 @@ fn map_method_return<T: Serialize>(
impl scenegraph::Scenegraph for Scenegraph { impl scenegraph::Scenegraph for Scenegraph {
fn send_signal( fn send_signal(
&self, &self,
node: u64, node_id: u64,
aspect_id: u64,
method: u64, 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::NodeNotFound);
}; };
debug_span!("Handle signal", node, method).in_scope(|| { debug_span!("Handle signal", aspect_id, node_id, method).in_scope(|| {
self.get_node(node) self.get_node(node_id)
.ok_or(ScenegraphError::NodeNotFound)? .ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal( .send_local_signal(
client, client,
aspect_id,
method, method,
Message { Message {
data: data.to_vec(), data: data.to_vec(),
@@ -109,23 +110,25 @@ impl scenegraph::Scenegraph for Scenegraph {
} }
fn execute_method( fn execute_method(
&self, &self,
node: u64, node_id: u64,
aspect_id: u64,
method: u64, 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>>,
) { ) {
let Some(client) = self.get_client() else { let Some(client) = self.get_client() else {
let _ = response.send(Err(ScenegraphError::MethodNotFound)); let _ = response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
debug!(node, method, "Handle method"); debug!(aspect_id, node_id, method, "Handle method");
let Some(node) = self.get_node(node) else { let Some(node) = self.get_node(node_id) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound)); let _ = response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
node.execute_local_method( node.execute_local_method(
client, client,
aspect_id,
method, method,
Message { Message {
data: data.to_vec(), data: data.to_vec(),

View File

@@ -11,30 +11,32 @@ use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, input}; use crate::nodes::{audio, drawable, input};
use clap::Parser; use clap::Parser;
use core::client::Client; use core::client::{Client, tick_internal_client};
use core::task; use core::task;
use directories::ProjectDirs; use directories::ProjectDirs;
use objects::ServerObjects; use objects::ServerObjects;
use once_cell::sync::OnceCell;
use session::{launch_start, save_session}; use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server; use stardust_xr::server;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::{Arc, OnceLock};
use std::time::Duration; use std::time::Duration;
use stereokit_rust::material::Material; use stereokit_rust::material::Material;
use stereokit_rust::shader::Shader; use stereokit_rust::shader::Shader;
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings}; use stereokit_rust::sk::{
use stereokit_rust::system::{LogLevel, Renderer}; AppMode, DepthMode, DisplayBlend, OriginMode, QuitReason, SkSettings, sk_quit,
};
use stereokit_rust::system::{Handed, Input, LogLevel, Renderer};
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType}; use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
use stereokit_rust::ui::Ui; use stereokit_rust::ui::Ui;
use stereokit_rust::util::{Color128, Time}; use stereokit_rust::util::{Color128, SphericalHarmonics, Time};
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tokio::sync::Notify; use tokio::sync::Notify;
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::{EnvFilter, fmt, prelude::*};
use zbus::fdo::ObjectManager;
use zbus::Connection; use zbus::Connection;
use zbus::fdo::ObjectManager;
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
@@ -65,12 +67,16 @@ struct CliArgs {
/// Restore the session with the given ID (or `latest`), ignoring the startup script. Sessions are stored in directories at `~/.local/state/stardust/`. /// 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)] #[clap(id = "SESSION_ID", long = "restore", action)]
restore: Option<String>, restore: Option<String>,
/// this should fix nvidia issues, it'll only help on driver 565+
/// and only if running under wayland, probably
#[clap(long)]
nvidia: bool,
} }
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new(); static STARDUST_INSTANCE: OnceLock<String> = OnceLock::new();
// #[tokio::main] // #[tokio::main(flavor = "current_thread")]
#[tokio::main(flavor = "current_thread")] #[tokio::main]
async fn main() { async fn main() {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
@@ -91,11 +97,29 @@ async fn main() {
.with_thread_names(true) .with_thread_names(true)
.with_ansi(true) .with_ansi(true)
.with_line_number(true) .with_line_number(true)
.with_filter(EnvFilter::from_default_env()); .with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.from_env_lossy(),
);
registry.with(log_layer).init(); registry.with(log_layer).init();
let cli_args = CliArgs::parse(); let cli_args = CliArgs::parse();
if cli_args.nvidia && !cli_args.flatscreen {
// Only call this while singlethreaded since it can/will cause raceconditions with other
// functions reading or writing from the env
unsafe {
std::env::set_var("__GLX_VENDOR_LIBRARY_NAME", "mesa");
std::env::set_var(
"__EGL_VENDOR_LIBRARY_FILENAMES",
"/usr/share/glvnd/egl_vendor.d/50_mesa.json",
);
std::env::set_var("MESA_LOADER_DRIVER_OVERRIDE", "zink");
std::env::set_var("GALLIUM_DRIVER", "zink");
}
}
let socket_path = let socket_path =
server::get_free_socket_path().expect("Unable to find a free stardust socket path"); server::get_free_socket_path().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell"); STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
@@ -120,7 +144,9 @@ async fn main() {
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 dbus_connection = Connection::session() let dbus_connection = Connection::session()
@@ -129,7 +155,9 @@ async fn main() {
dbus_connection dbus_connection
.request_name("org.stardustxr.HMD") .request_name("org.stardustxr.HMD")
.await .await
.expect("Another instance of the server is running. This is not supported currently (but is planned)."); .expect(
"Another instance of the server is running. This is not supported currently (but is planned).",
);
dbus_connection dbus_connection
.object_server() .object_server()
@@ -137,13 +165,25 @@ async fn main() {
.await .await
.expect("Couldn't add the object manager"); .expect("Couldn't add the object manager");
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect(
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
);
let sk_ready_notifier = Arc::new(Notify::new()); let sk_ready_notifier = Arc::new(Notify::new());
let stereokit_loop = tokio::task::spawn_blocking({ let stereokit_loop = tokio::task::spawn_blocking({
let sk_ready_notifier = sk_ready_notifier.clone(); let sk_ready_notifier = sk_ready_notifier.clone();
let project_dirs = project_dirs.clone(); let project_dirs = project_dirs.clone();
let cli_args = cli_args.clone(); let cli_args = cli_args.clone();
let dbus_connection = dbus_connection.clone(); let dbus_connection = dbus_connection.clone();
move || stereokit_loop(sk_ready_notifier, project_dirs, cli_args, dbus_connection) move || {
stereokit_loop(
sk_ready_notifier,
project_dirs,
cli_args,
dbus_connection,
object_registry,
)
}
}); });
sk_ready_notifier.notified().await; sk_ready_notifier.notified().await;
let mut startup_children = project_dirs let mut startup_children = project_dirs
@@ -166,14 +206,19 @@ async fn main() {
info!("Cleanly shut down Stardust"); info!("Cleanly shut down Stardust");
} }
static DEFAULT_SKYTEX: OnceLock<Tex> = OnceLock::new();
static DEFAULT_SKYLIGHT: OnceLock<SphericalHarmonics> = OnceLock::new();
fn stereokit_loop( fn stereokit_loop(
sk_ready_notifier: Arc<Notify>, sk_ready_notifier: Arc<Notify>,
project_dirs: Option<ProjectDirs>, project_dirs: Option<ProjectDirs>,
args: CliArgs, args: CliArgs,
dbus_connection: Connection, dbus_connection: Connection,
object_registry: ObjectRegistry,
) { ) {
let sk = SkSettings::default() let sk = SkSettings::default()
.app_name("Stardust XR") .app_name("Stardust XR")
.blend_preference(DisplayBlend::AnyTransparent)
.mode(if args.flatscreen { .mode(if args.flatscreen {
AppMode::Simulator AppMode::Simulator
} else { } else {
@@ -201,23 +246,33 @@ fn stereokit_loop(
Material::default().shader(Shader::pbr_clip()); Material::default().shader(Shader::pbr_clip());
Ui::enable_far_interact(false); Ui::enable_far_interact(false);
let left_hand_material = Material::find("default/material_hand").unwrap();
let mut right_hand_material = left_hand_material.copy();
right_hand_material.id("right_hand");
Input::hand_material(Handed::Right, Some(Material::find("right_hand").unwrap()));
Input::hand_visible(Handed::Left, false);
Input::hand_visible(Handed::Right, false);
// Skytex/light stuff // Skytex/light stuff
{ {
let _ = DEFAULT_SKYTEX.set(Tex::gen_color(
Color128::BLACK,
1,
1,
TexType::Cubemap,
TexFormat::RGBA32,
));
let _ = DEFAULT_SKYLIGHT.set(Renderer::get_skylight());
if let Some(sky) = project_dirs if let Some(sky) = project_dirs
.as_ref() .as_ref()
.map(|dirs| dirs.config_dir().join("skytex.hdr")) .map(|dirs| dirs.config_dir().join("skytex.hdr"))
.filter(|f| f.exists()) .filter(|f| f.exists())
.and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok()) .and_then(|p| SHCubemap::from_cubemap(p, true, 100).ok())
{ {
sky.render_as_sky(); sky.render_as_sky();
} else { } else {
Renderer::skytex(Tex::gen_color( Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
Color128::BLACK,
1,
1,
TexType::Cubemap,
TexFormat::RGBA32,
));
} }
} }
@@ -231,6 +286,7 @@ fn stereokit_loop(
let mut objects = ServerObjects::new( let mut objects = ServerObjects::new(
dbus_connection.clone(), dbus_connection.clone(),
&sk, &sk,
[left_hand_material, right_hand_material],
args.disable_controllers, args.disable_controllers,
args.disable_hands, args.disable_hands,
); );
@@ -246,7 +302,7 @@ fn stereokit_loop(
wayland.frame_event(); wayland.frame_event();
destroy_queue::clear(); destroy_queue::clear();
objects.update(&sk, token); objects.update(&sk, token, &dbus_connection, &object_registry);
input::process_input(); input::process_input();
nodes::root::Root::send_frame_events(Time::get_step_unscaled()); nodes::root::Root::send_frame_events(Time::get_step_unscaled());
adaptive_sleep( adaptive_sleep(
@@ -255,6 +311,7 @@ fn stereokit_loop(
Duration::from_micros(250), Duration::from_micros(250),
); );
tick_internal_client();
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
wayland.update(); wayland.update();
drawable::draw(token); drawable::draw(token);
@@ -262,8 +319,6 @@ fn stereokit_loop(
} }
info!("Cleanly shut down StereoKit"); info!("Cleanly shut down StereoKit");
#[cfg(feature = "wayland")]
drop(wayland);
} }
fn adaptive_sleep( fn adaptive_sleep(

View File

@@ -1,6 +1,5 @@
use super::{Aspect, Node}; use super::{Aspect, AspectIdentifier, Node};
use crate::core::{client::Client, registry::Registry}; use crate::core::{client::Client, error::Result, registry::Registry};
use color_eyre::eyre::Result;
use std::{ use std::{
ops::Add, ops::Add,
sync::{Arc, Weak}, sync::{Arc, Weak},
@@ -68,8 +67,31 @@ impl Alias {
Ok(()) Ok(())
} }
} }
impl AspectIdentifier for Alias {
const ID: u64 = 0;
}
impl Aspect for Alias { impl Aspect for Alias {
const NAME: &'static str = "Alias"; fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
fn run_signal(
&self,
_calling_client: Arc<Client>,
_node: Arc<Node>,
_signal: u64,
_message: super::Message,
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
Ok(())
}
fn run_method(
&self,
_calling_client: Arc<Client>,
_node: Arc<Node>,
_method: u64,
_message: super::Message,
_response: crate::core::scenegraph::MethodResponseSender,
) {
}
} }
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> { pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
@@ -106,7 +128,7 @@ impl AliasList {
.into_iter() .into_iter()
.find(move |node| links_to(node.clone(), original.clone())) .find(move |node| links_to(node.clone(), original.clone()))
} }
pub fn get_from_aspect<A: Aspect>(&self, aspect: &A) -> Option<Arc<Node>> { pub fn get_from_aspect<A: AspectIdentifier>(&self, aspect: &A) -> Option<Arc<Node>> {
self.0.get_valid_contents().into_iter().find(|node| { self.0.get_valid_contents().into_iter().find(|node| {
let Some(node) = get_original(node.clone(), false) else { let Some(node) = get_original(node.clone(), false) else {
return false; return false;
@@ -120,7 +142,7 @@ impl AliasList {
pub fn get_aliases(&self) -> Vec<Arc<Node>> { pub fn get_aliases(&self) -> Vec<Arc<Node>> {
self.0.get_valid_contents() self.0.get_valid_contents()
} }
pub fn remove_aspect<A: Aspect>(&self, aspect: &A) { pub fn remove_aspect<A: AspectIdentifier>(&self, aspect: &A) {
self.0.retain(|node| { self.0.retain(|node| {
let Some(original) = get_original(node.clone(), false) else { let Some(original) = get_original(node.clone(), false) else {
return false; return false;

View File

@@ -1,19 +1,17 @@
use super::{Aspect, Node}; use super::{Aspect, AspectIdentifier, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::destroy_queue; use crate::core::destroy_queue;
use crate::core::error::Result;
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::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform};
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; use color_eyre::eyre::eyre;
use crate::nodes::spatial::{Spatial, Transform}; use glam::{Vec4Swizzles, vec3};
use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
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, OnceLock};
use std::{ffi::OsStr, path::PathBuf}; use std::{ffi::OsStr, path::PathBuf};
use stereokit_rust::sound::{Sound as SkSound, SoundInst}; use stereokit_rust::sound::{Sound as SkSound, SoundInst};
@@ -25,7 +23,7 @@ pub struct Sound {
volume: f32, volume: f32,
pending_audio_path: PathBuf, pending_audio_path: PathBuf,
sk_sound: OnceCell<SkSound>, sk_sound: OnceLock<SkSound>,
instance: Mutex<Option<SoundInst>>, instance: Mutex<Option<SoundInst>>,
stop: Mutex<Option<()>>, stop: Mutex<Option<()>>,
play: Mutex<Option<()>>, play: Mutex<Option<()>>,
@@ -42,14 +40,13 @@ impl Sound {
space: node.get_aspect::<Spatial>().unwrap().clone(), space: node.get_aspect::<Spatial>().unwrap().clone(),
volume: 1.0, volume: 1.0,
pending_audio_path, pending_audio_path,
sk_sound: OnceCell::new(), sk_sound: OnceLock::new(),
instance: Mutex::new(None), instance: Mutex::new(None),
stop: Mutex::new(None), stop: Mutex::new(None),
play: Mutex::new(None), play: Mutex::new(None),
}; };
let sound_arc = SOUND_REGISTRY.add(sound); let sound_arc = SOUND_REGISTRY.add(sound);
node.add_aspect_raw(sound_arc.clone()); node.add_aspect_raw(sound_arc.clone());
<Sound as SoundAspect>::add_node_members(node);
Ok(sound_arc) Ok(sound_arc)
} }
@@ -71,8 +68,11 @@ impl Sound {
} }
} }
} }
impl AspectIdentifier for Sound {
impl_aspect_for_sound_aspect_id! {}
}
impl Aspect for Sound { impl Aspect for Sound {
const NAME: &'static str = "Sound"; impl_aspect_for_sound_aspect! {}
} }
impl SoundAspect for Sound { impl SoundAspect for Sound {
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> { fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -88,6 +88,9 @@ impl SoundAspect for Sound {
} }
impl Drop for Sound { impl Drop for Sound {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(instance) = self.instance.lock().take() {
instance.stop();
}
if let Some(sk_sound) = self.sk_sound.take() { if let Some(sk_sound) = self.sk_sound.take() {
destroy_queue::add(sk_sound); destroy_queue::add(sk_sound);
} }
@@ -101,9 +104,7 @@ pub fn update() {
} }
} }
create_interface!(AudioInterface); impl InterfaceAspect for Interface {
struct 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>,

View File

@@ -1,276 +0,0 @@
use super::alias::AliasList;
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Node};
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::create_interface;
use crate::nodes::fields::FIELD_ALIAS_INFO;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{bail, ensure, eyre, Result};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use stardust_xr::schemas::flex::flexbuffers;
use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak};
lazy_static! {
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
}
// TODO: probably just use d-bus for this stuff (custom protocol for exporting spatials as refs) because the mask stuff is just too confusing
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(datamap.raw().as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
(|| -> Result<_> {
for key in get_mask(mask_map_lesser)?.iter_keys() {
let lesser_key = get_mask(mask_map_lesser)?.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
if lesser_key.flexbuffer_type().is_heterogenous_vector()
&& lesser_key.as_vector().is_empty()
&& greater_key.flexbuffer_type().is_vector()
{
continue;
}
if !lesser_key.flexbuffer_type().is_null()
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
{
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
}
}
Ok(())
})()
.is_ok()
}
stardust_xr_server_codegen::codegen_data_protocol!();
pub struct PulseSender {
node: Weak<Node>,
pub mask: Datamap,
aliases: AliasList,
field_aliases: AliasList,
}
impl PulseSender {
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
let sender = PulseSender {
node: Arc::downgrade(node),
mask,
aliases: AliasList::default(),
field_aliases: AliasList::default(),
};
// <PulseSender as PulseSenderAspect>::add_node_members(node);
let sender = PULSE_SENDER_REGISTRY.add(sender);
node.add_aspect_raw(sender.clone());
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
Ok(sender.clone())
}
fn handle_new_receiver(&self, receiver: &PulseReceiver) {
if !mask_matches(&self.mask, &receiver.mask) {
return;
}
let Some(tx_node) = self.node.upgrade() else {
return;
};
let Some(tx_client) = tx_node.get_client() else {
return;
};
let Some(rx_node) = receiver.node.upgrade() else {
return;
};
// Receiver itself
let Ok(rx_alias) = Alias::create(
&rx_node,
&tx_client,
PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
Some(&self.aliases),
) else {
return;
};
// Receiver's field
let Ok(rx_field_alias) = Alias::create(
&rx_node
.get_aspect::<PulseReceiver>()
.unwrap()
.field
.spatial
.node()
.unwrap(),
&tx_client,
FIELD_ALIAS_INFO.clone(),
Some(&self.aliases),
) else {
return;
};
let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
}
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let Some(node) = receiver.node.upgrade() else {
return;
};
self.aliases.remove_aspect(receiver);
self.field_aliases.remove_aspect(receiver.field.as_ref());
let Some(tx_node) = self.node.upgrade() else {
return;
};
let _ = pulse_sender_client::drop_receiver(&tx_node, node.get_id());
}
}
impl Aspect for PulseSender {
const NAME: &'static str = "PulseSender";
}
impl PulseSenderAspect for PulseSender {}
impl Drop for PulseSender {
fn drop(&mut self) {
PULSE_SENDER_REGISTRY.remove(self);
}
}
pub struct PulseReceiver {
pub node: Weak<Node>,
pub field: Arc<Field>,
pub mask: Datamap,
}
impl PulseReceiver {
pub fn add_to(
node: &Arc<Node>,
field: Arc<Field>,
mask: Datamap,
) -> Result<Arc<PulseReceiver>> {
let receiver = PulseReceiver {
node: Arc::downgrade(node),
field,
mask,
};
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
node.add_aspect_raw(receiver.clone());
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
Ok(receiver)
}
}
impl Aspect for PulseReceiver {
const NAME: &'static str = "PulseReceiver";
}
impl PulseReceiverAspect for PulseReceiver {
fn send_data(
node: Arc<Node>,
_calling_client: Arc<Client>,
sender: Arc<Node>,
data: Datamap,
) -> Result<()> {
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
ensure!(
mask_matches(&this_receiver.mask, &data),
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
this_receiver.mask
);
pulse_receiver_client::data(&node, &sender, &data)?;
Ok(())
}
}
impl Drop for PulseReceiver {
fn drop(&mut self) {
PULSE_RECEIVER_REGISTRY.remove(self);
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_drop_receiver(self);
}
}
}
create_interface!(DataInterface);
struct DataInterface;
impl InterfaceAspect for DataInterface {
fn create_pulse_sender(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseSender::add_to(&node, mask)?;
Ok(())
}
fn create_pulse_receiver(
_node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::from_id(&calling_client, id, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let field = field.get_aspect::<Field>()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseReceiver::add_to(&node, field, mask)?;
Ok(())
}
async fn register_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap: String,
) -> Result<u64> {
let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps
.iter()
.filter(|(_k, v)| *v == &keymap)
.map(|(k, _v)| k)
.last()
{
return Ok(found_keymap_id.data().as_ffi());
}
let key = keymaps.insert(keymap);
Ok(key.data().as_ffi())
}
async fn get_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap_id: u64,
) -> Result<String> {
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
bail!("Could not find keymap. Try registering it")
};
Ok(keymap.clone())
}
}

View File

@@ -1,12 +1,10 @@
use super::{Line, LinesAspect}; use super::{Line, LinesAspect};
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, error::Result, registry::Registry},
nodes::{spatial::Spatial, Aspect, Node}, nodes::{Node, spatial::Spatial},
}; };
use color_eyre::eyre::Result; use glam::{FloatExt, Vec3};
use glam::Vec3;
use parking_lot::Mutex; use parking_lot::Mutex;
use prisma::Lerp;
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use stereokit_rust::{ use stereokit_rust::{
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128, maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
@@ -40,7 +38,6 @@ impl Lines {
space: node.get_aspect::<Spatial>()?.clone(), space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(lines), data: Mutex::new(lines),
}); });
<Lines as LinesAspect>::add_node_members(node);
node.add_aspect_raw(lines.clone()); node.add_aspect_raw(lines.clone());
Ok(lines) Ok(lines)
@@ -64,10 +61,10 @@ impl Lines {
let last = line.points.last().unwrap(); let last = line.points.last().unwrap();
let color = Color128 { let color = Color128 {
r: first.color.c.r.lerp(&last.color.c.r, 0.5), r: first.color.c.r.lerp(last.color.c.r, 0.5),
g: first.color.c.g.lerp(&last.color.c.g, 0.5), g: first.color.c.g.lerp(last.color.c.g, 0.5),
b: first.color.c.b.lerp(&last.color.c.b, 0.5), b: first.color.c.b.lerp(last.color.c.b, 0.5),
a: first.color.a.lerp(&last.color.a, 0.5), a: first.color.a.lerp(last.color.a, 0.5),
}; };
let connect_point = SkLinePoint { let connect_point = SkLinePoint {
pt: transform_mat pt: transform_mat
@@ -83,9 +80,6 @@ impl Lines {
} }
} }
} }
impl Aspect for Lines {
const NAME: &'static str = "Lines";
}
impl LinesAspect for Lines { impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> { fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
let lines_aspect = node.get_aspect::<Lines>()?; let lines_aspect = node.get_aspect::<Lines>()?;

View File

@@ -1,21 +1,20 @@
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;
use self::{lines::Lines, model::Model, text::Text}; use self::{lines::Lines, model::Model, text::Text};
use super::{ use super::{
Aspect, AspectIdentifier, Node,
spatial::{Spatial, Transform}, spatial::{Spatial, Transform},
Node,
}; };
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; use crate::{DEFAULT_SKYLIGHT, nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO};
use crate::{ use crate::{
core::{client::Client, resource::get_resource_file}, DEFAULT_SKYTEX,
create_interface, core::{client::Client, error::Result, resource::get_resource_file},
}; };
use color_eyre::eyre::{self, Result}; use color_eyre::eyre::eyre;
use model::ModelPart;
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};
@@ -26,30 +25,72 @@ pub fn draw(token: &MainThreadToken) {
lines::draw_all(token); lines::draw_all(token);
model::draw_all(token); model::draw_all(token);
text::draw_all(token); text::draw_all(token);
match QUEUED_SKYTEX.lock().take() {
if let Some(skytex) = QUEUED_SKYTEX.lock().take() { Some(Some(skytex)) => {
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) { if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) {
Renderer::skytex(skytex.tex); Renderer::skytex(skytex.tex);
}
} }
Some(None) => {
Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
}
None => {}
} }
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() { match QUEUED_SKYLIGHT.lock().take() {
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) { Some(Some(skylight)) => {
Renderer::skylight(skylight.sh); if let Ok(skylight) = SHCubemap::from_cubemap(skylight, true, 100) {
Renderer::skylight(skylight.sh);
}
} }
Some(None) => {
Renderer::skylight(*DEFAULT_SKYLIGHT.get().unwrap());
}
None => {}
} }
} }
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYLIGHT: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYTEX: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
stardust_xr_server_codegen::codegen_drawable_protocol!(); stardust_xr_server_codegen::codegen_drawable_protocol!();
create_interface!(DrawableInterface);
pub struct DrawableInterface; impl AspectIdentifier for Lines {
impl InterfaceAspect for DrawableInterface { impl_aspect_for_lines_aspect_id! {}
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")]) impl Aspect for Lines {
.ok_or(eyre::eyre!("Could not find resource"))?; impl_aspect_for_lines_aspect! {}
}
impl AspectIdentifier for Model {
impl_aspect_for_model_aspect_id! {}
}
impl Aspect for Model {
impl_aspect_for_model_aspect! {}
}
impl AspectIdentifier for ModelPart {
impl_aspect_for_model_part_aspect_id! {}
}
impl Aspect for ModelPart {
impl_aspect_for_model_part_aspect! {}
}
impl AspectIdentifier for Text {
impl_aspect_for_text_aspect_id! {}
}
impl Aspect for Text {
impl_aspect_for_text_aspect! {}
}
impl InterfaceAspect for Interface {
fn set_sky_tex(
_node: Arc<Node>,
calling_client: Arc<Client>,
tex: Option<ResourceID>,
) -> Result<()> {
let resource_path = tex
.map(|tex| {
get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre!("Could not find resource"))
})
.transpose()?;
QUEUED_SKYTEX.lock().replace(resource_path); QUEUED_SKYTEX.lock().replace(resource_path);
Ok(()) Ok(())
} }
@@ -57,10 +98,14 @@ impl InterfaceAspect for DrawableInterface {
fn set_sky_light( fn set_sky_light(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
light: ResourceID, light: Option<ResourceID>,
) -> Result<()> { ) -> Result<()> {
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")]) let resource_path = light
.ok_or(eyre::eyre!("Could not find resource"))?; .map(|light| {
get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre!("Could not find resource"))
})
.transpose()?;
QUEUED_SKYLIGHT.lock().replace(resource_path); QUEUED_SKYLIGHT.lock().replace(resource_path);
Ok(()) Ok(())
} }

View File

@@ -1,30 +1,38 @@
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO}; use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect};
use crate::bail;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::error::Result;
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::Node;
use crate::nodes::alias::{Alias, AliasList}; use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::Spatial; use crate::nodes::spatial::Spatial;
use crate::nodes::{Aspect, Node}; use color_eyre::eyre::eyre;
use color_eyre::eyre::{bail, eyre, Result};
use glam::{Mat4, Vec2, Vec3}; use glam::{Mat4, Vec2, Vec3};
use once_cell::sync::{Lazy, OnceCell};
use parking_lot::Mutex; use parking_lot::Mutex;
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::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::{Arc, Weak}; use std::sync::{Arc, LazyLock, OnceLock, Weak};
use stereokit_rust::material::Transparency; use stereokit_rust::material::Transparency;
use stereokit_rust::maths::Bounds; use stereokit_rust::maths::Bounds;
use stereokit_rust::sk::MainThreadToken; use stereokit_rust::sk::MainThreadToken;
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128}; use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
pub struct MaterialWrapper(pub Material); pub struct MaterialWrapper(pub Material);
impl Drop for MaterialWrapper {
fn drop(&mut self) {
MATERIAL_REGISTRY.remove(self);
}
}
impl Hash for MaterialWrapper { impl Hash for MaterialWrapper {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.0.get_shader().0.as_ptr().hash(state); self.0.get_shader().0.as_ptr().hash(state);
for param in self.0.get_all_param_info() { for param in self.0.get_all_param_info() {
param.to_string().hash(state) param.name.hash(state);
param.to_string().hash(state);
} }
self.0.get_chain().map(MaterialWrapper).hash(state) self.0.get_chain().map(MaterialWrapper).hash(state)
} }
@@ -57,10 +65,9 @@ unsafe impl Send for MaterialWrapper {}
unsafe impl Sync for MaterialWrapper {} unsafe impl Sync for MaterialWrapper {}
#[derive(Default)] #[derive(Default)]
struct MaterialRegistry(Mutex<FxHashMap<u64, String>>); struct MaterialRegistry(Mutex<FxHashMap<u64, Weak<MaterialWrapper>>>);
impl MaterialRegistry { impl MaterialRegistry {
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> { fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
let mut lock = self.0.lock();
let hash = { let hash = {
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new(); let mut hasher = std::collections::hash_map::DefaultHasher::new();
@@ -68,20 +75,31 @@ impl MaterialRegistry {
hasher.finish() hasher.finish()
}; };
if let Some(id) = lock.get(&hash) { let mut lock = self.0.lock();
if let Ok(existing) = Material::find(id) { if let Some(mat) = lock.get(&hash) {
return Arc::new(MaterialWrapper(existing)); if let Some(mat) = mat.upgrade() {
return mat;
} }
} }
lock.insert(hash, material.0.get_id().to_string()); lock.insert(hash, Arc::downgrade(&material));
material material
} }
fn remove(&self, material: &MaterialWrapper) {
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
material.hash(&mut hasher);
hasher.finish()
};
let mut lock = self.0.lock();
lock.remove(&hash);
}
} }
static MATERIAL_REGISTRY: Lazy<MaterialRegistry> = Lazy::new(MaterialRegistry::default); static MATERIAL_REGISTRY: LazyLock<MaterialRegistry> = LazyLock::new(MaterialRegistry::default);
static MODEL_REGISTRY: Registry<Model> = Registry::new(); static MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new(); static HOLDOUT_MATERIAL: OnceLock<Arc<MaterialWrapper>> = OnceLock::new();
impl MaterialParameter { impl MaterialParameter {
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) { fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
@@ -130,6 +148,7 @@ pub struct ModelPart {
path: String, path: String,
space: Arc<Spatial>, space: Arc<Spatial>,
model: Weak<Model>, model: Weak<Model>,
material: Mutex<Option<Arc<MaterialWrapper>>>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>, pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
aliases: AliasList, aliases: AliasList,
@@ -203,8 +222,8 @@ impl ModelPart {
pending_material_parameters: Mutex::new(FxHashMap::default()), pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None), pending_material_replacement: Mutex::new(None),
aliases: AliasList::default(), aliases: AliasList::default(),
material: Mutex::new(part.get_material().map(MaterialWrapper).map(Arc::new)),
}); });
<ModelPart as ModelPartAspect>::add_node_members(&node);
node.add_aspect_raw(model_part.clone()); node.add_aspect_raw(model_part.clone());
parts.push(model_part.clone()); parts.push(model_part.clone());
Some(model_part) Some(model_part)
@@ -230,7 +249,10 @@ impl ModelPart {
}; };
let shared_material = let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy()))); MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
let mut lock = self.material.lock();
part.material(&shared_material.0); part.material(&shared_material.0);
lock.replace(shared_material);
} }
fn update(&self) { fn update(&self) {
@@ -257,7 +279,9 @@ impl ModelPart {
}; };
if let Some(material_replacement) = self.pending_material_replacement.lock().take() { if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
let mut lock = self.material.lock();
part.material(&material_replacement.0); part.material(&material_replacement.0);
lock.replace(material_replacement);
} }
'mat_params: { 'mat_params: {
@@ -273,14 +297,13 @@ impl ModelPart {
let shared_material = let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material))); MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
let mut lock = self.material.lock();
part.material(&shared_material.0); part.material(&shared_material.0);
lock.replace(shared_material);
} }
} }
} }
} }
impl Aspect for ModelPart {
const NAME: &'static str = "ModelPart";
}
impl ModelPartAspect for ModelPart { impl ModelPartAspect for ModelPart {
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."] #[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> { fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -309,7 +332,7 @@ impl ModelPartAspect for ModelPart {
pub struct Model { pub struct Model {
space: Arc<Spatial>, space: Arc<Spatial>,
_resource_id: ResourceID, _resource_id: ResourceID,
sk_model: OnceCell<SKModel>, sk_model: OnceLock<SKModel>,
parts: Mutex<Vec<Arc<ModelPart>>>, parts: Mutex<Vec<Arc<ModelPart>>>,
} }
impl Model { impl Model {
@@ -324,10 +347,9 @@ impl Model {
let model = Arc::new(Model { let model = Arc::new(Model {
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: OnceLock::new(),
parts: Mutex::new(Vec::default()), parts: Mutex::new(Vec::default()),
}); });
<Model as ModelAspect>::add_node_members(node);
MODEL_REGISTRY.add_raw(&model); MODEL_REGISTRY.add_raw(&model);
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP // technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
@@ -361,9 +383,6 @@ impl Model {
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly) // TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
unsafe impl Send for Model {} unsafe impl Send for Model {}
unsafe impl Sync for Model {} unsafe impl Sync for Model {}
impl Aspect for Model {
const NAME: &'static str = "Model";
}
impl ModelAspect for Model { impl ModelAspect for Model {
#[doc = "Bind a model part to the node with the ID input."] #[doc = "Bind a model part to the node with the ID input."]
fn bind_model_part( fn bind_model_part(
@@ -371,12 +390,12 @@ impl ModelAspect for Model {
calling_client: Arc<Client>, calling_client: Arc<Client>,
id: u64, id: u64,
part_path: String, part_path: String,
) -> color_eyre::eyre::Result<()> { ) -> Result<()> {
let model = node.get_aspect::<Model>()?; let model = node.get_aspect::<Model>()?;
let parts = model.parts.lock(); let parts = model.parts.lock();
let Some(part) = parts.iter().find(|p| p.path == part_path) else { let Some(part) = parts.iter().find(|p| p.path == part_path) else {
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>(); let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",) bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",);
}; };
Alias::create_with_id( Alias::create_with_id(
&part.space.node().unwrap(), &part.space.node().unwrap(),

View File

@@ -1,137 +0,0 @@
#![allow(dead_code)]
use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError,
};
use stereokit_rust::shader::{Shader, _ShaderT};
use tracing::error;
struct FfiAssetHeader {
asset_type: i32,
asset_state: i32,
id: u64,
index: u64,
refs: i32,
debug: *mut u8,
}
struct FfiSkgShader {
meta: *mut u8,
vertex: u32,
pixel: u32,
program: u32,
compute: u32,
}
struct FfiShader {
header: FfiAssetHeader,
shader: FfiSkgShader,
}
unsafe fn compile_shader(
gl: &ffi::Gles2,
variant: ffi::types::GLuint,
src: &str,
) -> Result<ffi::types::GLuint, GlesError> {
let shader = gl.CreateShader(variant);
if shader == 0 {
return Err(GlesError::CreateShaderObject);
}
gl.ShaderSource(
shader,
1,
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
&(src.len() as i32) as *const _,
);
gl.CompileShader(shader);
let mut status = ffi::FALSE as i32;
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
if status == ffi::FALSE as i32 {
let mut max_len = 0;
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
let mut error = Vec::with_capacity(max_len as usize);
let mut len = 0;
gl.GetShaderInfoLog(
shader,
max_len as _,
&mut len as *mut _,
error.as_mut_ptr() as *mut _,
);
error.set_len(len as usize);
error!(
"[GL] {}",
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
);
gl.DeleteShader(shader);
return Err(GlesError::ShaderCompileError);
}
Ok(shader)
}
unsafe fn link_program(
gl: &ffi::Gles2,
vert: ffi::types::GLuint,
frag: ffi::types::GLuint,
) -> Result<ffi::types::GLuint, GlesError> {
let program = gl.CreateProgram();
gl.AttachShader(program, vert);
gl.AttachShader(program, frag);
gl.LinkProgram(program);
// gl.DetachShader(program, vert);
// gl.DetachShader(program, frag);
// gl.DeleteShader(vert);
// gl.DeleteShader(frag);
let mut status = ffi::FALSE as i32;
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
if status == ffi::FALSE as i32 {
let mut max_len = 0;
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
let mut error = Vec::with_capacity(max_len as usize);
let mut len = 0;
gl.GetProgramInfoLog(
program,
max_len as _,
&mut len as *mut _,
error.as_mut_ptr() as *mut _,
);
error.set_len(len as usize);
error!(
"[GL] {}",
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
);
gl.DeleteProgram(program);
return Err(GlesError::ProgramLinkError);
}
Ok(program)
}
pub unsafe fn shader_inject(
c: &Gles2,
sk_shader: &mut Shader,
vert_str: &str,
frag_str: &str,
) -> Result<(), GlesError> {
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
let gl_prog = link_program(c, gl_vert, gl_frag)?;
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
if let Some(shader) = shader.as_mut() {
shader.shader.vertex = gl_vert;
shader.shader.pixel = gl_frag;
shader.shader.program = gl_prog;
}
Ok(())
}

View File

@@ -1,17 +1,23 @@
use crate::{ use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file}, core::{
nodes::{spatial::Spatial, Aspect, Node}, client::Client, destroy_queue, error::Result, registry::Registry,
resource::get_resource_file,
},
nodes::{Node, spatial::Spatial},
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::eyre;
use glam::{vec3, Mat4, Vec2}; use glam::{Mat4, Vec2, vec3};
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use std::{
ffi::OsStr,
path::PathBuf,
sync::{Arc, OnceLock},
};
use stereokit_rust::{ use stereokit_rust::{
font::Font, font::Font,
sk::MainThreadToken, sk::MainThreadToken,
system::{TextAlign, TextFit, TextStyle as SkTextStyle}, system::{TextAlign, TextFit, TextStyle as SkTextStyle},
util::{Color128, Color32}, util::{Color32, Color128},
}; };
use super::{TextAspect, TextStyle}; use super::{TextAspect, TextStyle};
@@ -35,7 +41,7 @@ fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
pub struct Text { pub struct Text {
space: Arc<Spatial>, space: Arc<Spatial>,
font_path: Option<PathBuf>, font_path: Option<PathBuf>,
style: OnceCell<SkTextStyle>, style: OnceLock<SkTextStyle>,
text: Mutex<String>, text: Mutex<String>,
data: Mutex<TextStyle>, data: Mutex<TextStyle>,
@@ -48,93 +54,85 @@ impl Text {
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: OnceLock::new(),
text: Mutex::new(text), text: Mutex::new(text),
data: Mutex::new(style), data: Mutex::new(style),
}); });
<Text as TextAspect>::add_node_members(node);
node.add_aspect_raw(text.clone()); node.add_aspect_raw(text.clone());
Ok(text) Ok(text)
} }
fn draw(&self, token: &MainThreadToken) { fn draw(&self, token: &MainThreadToken) {
let style = let style = self.style.get_or_init(|| {
self.style let font = self
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> { .font_path
let font = self .as_deref()
.font_path .and_then(|path| Font::from_file(path).ok())
.as_deref() .unwrap_or_default();
.and_then(|path| Font::from_file(path).ok()) SkTextStyle::from_font(font, 1.0, Color32::WHITE)
.unwrap_or_default(); });
Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
});
if let Ok(style) = style { let text = self.text.lock();
let text = self.text.lock(); let data = self.data.lock();
let data = self.data.lock(); let transform = self.space.global_transform()
let transform = self.space.global_transform() * Mat4::from_scale(vec3(
* Mat4::from_scale(vec3( data.character_height,
data.character_height, data.character_height,
data.character_height, data.character_height,
data.character_height, ));
)); if let Some(bounds) = &data.bounds {
if let Some(bounds) = &data.bounds { stereokit_rust::system::Text::add_in(
stereokit_rust::system::Text::add_in( token,
token, &*text,
&*text, transform,
transform, Vec2::from(bounds.bounds) / data.character_height,
Vec2::from(bounds.bounds) / data.character_height, match bounds.fit {
match bounds.fit { super::TextFit::Wrap => TextFit::Wrap,
super::TextFit::Wrap => TextFit::Wrap, super::TextFit::Clip => TextFit::Clip,
super::TextFit::Clip => TextFit::Clip, super::TextFit::Squeeze => TextFit::Squeeze,
super::TextFit::Squeeze => TextFit::Squeeze, super::TextFit::Exact => TextFit::Exact,
super::TextFit::Exact => TextFit::Exact, super::TextFit::Overflow => TextFit::Overflow,
super::TextFit::Overflow => TextFit::Overflow, },
}, Some(*style),
Some(*style), Some(Color128::new(
Some(Color128::new( data.color.c.r,
data.color.c.r, data.color.c.g,
data.color.c.g, data.color.c.b,
data.color.c.b, data.color.a,
data.color.a, )),
)), data.bounds
data.bounds .as_ref()
.as_ref() .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), Some(convert_align(data.text_align_x, data.text_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)), None,
None, None,
None, None,
None, );
); } else {
} else { stereokit_rust::system::Text::add_at(
stereokit_rust::system::Text::add_at( token,
token, &*text,
&*text, transform,
transform, Some(*style),
Some(*style), Some(Color128::new(
Some(Color128::new( data.color.c.r,
data.color.c.r, data.color.c.g,
data.color.c.g, data.color.c.b,
data.color.c.b, data.color.a,
data.color.a, )),
)), data.bounds
data.bounds .as_ref()
.as_ref() .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), Some(convert_align(data.text_align_x, data.text_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)), None,
None, None,
None, None,
None, );
);
}
} }
} }
} }
impl Aspect for Text {
const NAME: &'static str = "Text";
}
impl TextAspect for Text { impl TextAspect for Text {
fn set_character_height( fn set_character_height(
node: Arc<Node>, node: Arc<Node>,

View File

@@ -1,25 +1,25 @@
use super::alias::{Alias, AliasInfo}; use super::alias::{Alias, AliasInfo};
use super::spatial::{ use super::spatial::{
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE, SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
Spatial,
}; };
use super::{Aspect, Node}; use super::{Aspect, AspectIdentifier, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::create_interface; use crate::core::error::Result;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{OptionExt, Result}; use crate::nodes::spatial::Transform;
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles}; use color_eyre::eyre::OptionExt;
use once_cell::sync::Lazy; use glam::{Vec3, Vec3A, Vec3Swizzles, vec2, vec3, vec3a};
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use stardust_xr::values::Vector3; use stardust_xr::values::Vector3;
use std::sync::Arc; use std::sync::{Arc, LazyLock};
// TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against // TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo { pub static FIELD_ALIAS_INFO: LazyLock<AliasInfo> = LazyLock::new(|| AliasInfo {
server_methods: vec![ server_methods: vec![
SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
@@ -145,15 +145,65 @@ impl Field {
shape: Mutex::new(shape), shape: Mutex::new(shape),
}; };
let field = node.add_aspect(field); let field = node.add_aspect(field);
<Field as FieldRefAspect>::add_node_members(node); node.add_aspect(FieldRef);
<Field as FieldAspect>::add_node_members(node);
Ok(field) Ok(field)
} }
} }
impl Aspect for Field { impl AspectIdentifier for Field {
const NAME: &'static str = "Field"; impl_aspect_for_field_aspect_id! {}
} }
impl FieldRefAspect for Field { impl Aspect for Field {
impl_aspect_for_field_aspect! {}
}
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.xz().length().abs() - radius, p.y.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
}
}
}
}
pub struct FieldRef;
impl AspectIdentifier for FieldRef {
impl_aspect_for_field_ref_aspect_id! {}
}
impl Aspect for FieldRef {
impl_aspect_for_field_ref_aspect! {}
}
impl FieldRefAspect for FieldRef {
async fn distance( async fn distance(
node: Arc<Node>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
@@ -205,56 +255,14 @@ impl FieldRefAspect for Field {
})) }))
} }
} }
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> { impl InterfaceAspect for Interface {
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( async fn import_field_ref(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
uid: u64, uid: u64,
) -> Result<Arc<Node>> { ) -> Result<Arc<Node>> {
EXPORTED_FIELDS Ok(EXPORTED_FIELDS
.lock() .lock()
.get(&uid) .get(&uid)
.map(|s| { .map(|s| {
@@ -266,7 +274,7 @@ impl InterfaceAspect for FieldInterface {
) )
.unwrap() .unwrap()
}) })
.ok_or_eyre("Couldn't find spatial with that ID") .ok_or_eyre("Couldn't find spatial with that ID")?)
} }
fn create_field( fn create_field(

View File

@@ -1,7 +1,7 @@
use super::{Finger, Hand, InputDataTrait, InputHandler, InputMethod, Joint, Thumb}; use super::{Finger, Hand, InputDataTrait, InputHandler, InputMethod, Joint, Thumb};
use crate::nodes::fields::{Field, FieldTrait}; use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::spatial::Spatial; use crate::nodes::spatial::Spatial;
use glam::{vec3a, Mat4, Quat}; use glam::{Mat4, Quat, vec3a};
use std::sync::Arc; use std::sync::Arc;
impl Default for Joint { impl Default for Joint {

View File

@@ -1,5 +1,5 @@
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY}; use super::{INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY, InputHandlerAspect};
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Aspect, Node}; use crate::nodes::{Node, alias::AliasList, fields::Field, spatial::Spatial};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::sync::Arc; use std::sync::Arc;
@@ -23,9 +23,6 @@ impl InputHandler {
Ok(()) Ok(())
} }
} }
impl Aspect for InputHandler {
const NAME: &'static str = "InputHandler";
}
impl InputHandlerAspect for InputHandler {} impl InputHandlerAspect for InputHandler {}
impl PartialEq for InputHandler { impl PartialEq for InputHandler {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {

View File

@@ -1,18 +1,22 @@
use super::{ use super::{
input_method_client, InputData, InputDataTrait, InputDataType, InputHandler, InputMethodAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO, INPUT_METHOD_REGISTRY, InputData,
InputMethodRefAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO, InputDataTrait, InputDataType, InputHandler, InputMethodAspect, InputMethodRefAspect,
INPUT_METHOD_REGISTRY, input_method_client,
}; };
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{
client::Client,
error::{Result, ServerError},
registry::Registry,
},
nodes::{ nodes::{
Node,
alias::{Alias, AliasList}, alias::{Alias, AliasList},
fields::{Field, FIELD_ALIAS_INFO}, fields::{FIELD_ALIAS_INFO, Field},
spatial::Spatial, spatial::Spatial,
Aspect, Node,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::eyre;
use parking_lot::Mutex; use parking_lot::Mutex;
use stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -25,7 +29,7 @@ pub struct InputMethod {
handler_aliases: AliasList, handler_aliases: AliasList,
handler_field_aliases: AliasList, handler_field_aliases: AliasList,
pub(super) handler_order: Mutex<Vec<Weak<InputHandler>>>, pub(super) handler_order: Mutex<Vec<Weak<InputHandler>>>,
pub internal_capture_requests: Registry<InputHandler>, pub capture_attempts: Registry<InputHandler>,
pub captures: Registry<InputHandler>, pub captures: Registry<InputHandler>,
} }
impl InputMethod { impl InputMethod {
@@ -42,16 +46,15 @@ impl InputMethod {
handler_aliases: AliasList::default(), handler_aliases: AliasList::default(),
handler_field_aliases: AliasList::default(), handler_field_aliases: AliasList::default(),
handler_order: Mutex::new(Vec::new()), handler_order: Mutex::new(Vec::new()),
internal_capture_requests: Registry::new(), capture_attempts: Registry::new(),
captures: 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() { for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler); method.handle_new_handler(&handler);
} }
let method = INPUT_METHOD_REGISTRY.add(method); let method = INPUT_METHOD_REGISTRY.add(method);
node.add_aspect_raw(method.clone()); node.add_aspect_raw(method.clone());
node.add_aspect(InputMethodRef);
Ok(method) Ok(method)
} }
@@ -131,6 +134,7 @@ impl InputMethod {
self.handler_aliases.remove_aspect(handler); self.handler_aliases.remove_aspect(handler);
self.handler_field_aliases self.handler_field_aliases
.remove_aspect(handler.field.as_ref()); .remove_aspect(handler.field.as_ref());
self.capture_attempts.remove(handler);
} }
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData { pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
@@ -153,24 +157,23 @@ impl InputMethod {
captured: self.captures.get_valid_contents().contains(handler), 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 pub(super) fn cull_capture_attempts(&self) {
.internal_capture_requests let sent = self
.add_raw(&input_handler); .handler_order
Ok(()) .lock()
.iter()
.filter_map(Weak::upgrade)
.collect::<Registry<InputHandler>>();
self.capture_attempts.retain(|handler| {
!handler
.spatial
.node()
.and_then(|n| n.get_client())
.map(|c| c.unresponsive())
.unwrap_or(false)
&& sent.contains(handler)
});
} }
} }
impl InputMethodAspect for InputMethod { impl InputMethodAspect for InputMethod {
@@ -231,3 +234,46 @@ impl Drop for InputMethod {
INPUT_METHOD_REGISTRY.remove(self); INPUT_METHOD_REGISTRY.remove(self);
} }
} }
pub struct InputMethodRef;
impl InputMethodRefAspect for InputMethodRef {
#[doc = "Try to capture the input method with the given handler. When the handler does not get input from the method, it will be released."]
fn try_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.capture_attempts.add_raw(&input_handler);
let Some(handler_alias) = input_method
.handler_aliases
.get_from_aspect(&*input_handler)
else {
return Err(ServerError::Report(eyre!(
"Internal: Couldn't get handler alias somehow?"
)));
};
input_method_client::request_capture_handler(&node, handler_alias.get_id())
}
#[doc = "If captured by this handler, release it (e.g. the object is let go of after grabbing)."]
fn release(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.capture_attempts.remove(&input_handler);
let Some(handler_alias) = input_method
.handler_aliases
.get_from_aspect(&*input_handler)
else {
return Err(ServerError::Report(eyre!(
"Internal: Couldn't get handler alias somehow?"
)));
};
input_method_client::release_handler(&node, handler_alias.get_id())
}
}

View File

@@ -9,14 +9,15 @@ mod tip;
pub use handler::*; pub use handler::*;
pub use method::*; pub use method::*;
use super::Aspect;
use super::AspectIdentifier;
use super::fields::Field; use super::fields::Field;
use super::spatial::Spatial; use super::spatial::Spatial;
use crate::create_interface; use crate::core::error::Result;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use crate::{core::client::Client, nodes::Node}; 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 stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::sync::Arc; use std::sync::Arc;
@@ -25,6 +26,25 @@ pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
stardust_xr_server_codegen::codegen_input_protocol!(); stardust_xr_server_codegen::codegen_input_protocol!();
impl AspectIdentifier for InputHandler {
impl_aspect_for_input_handler_aspect_id! {}
}
impl Aspect for InputHandler {
impl_aspect_for_input_handler_aspect! {}
}
impl AspectIdentifier for InputMethod {
impl_aspect_for_input_method_aspect_id! {}
}
impl Aspect for InputMethod {
impl_aspect_for_input_method_aspect! {}
}
impl AspectIdentifier for InputMethodRef {
impl_aspect_for_input_method_ref_aspect_id! {}
}
impl Aspect for InputMethodRef {
impl_aspect_for_input_method_ref_aspect! {}
}
pub trait InputDataTrait { pub trait InputDataTrait {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler); fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32; fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
@@ -47,9 +67,7 @@ impl InputDataTrait for InputDataType {
} }
} }
create_interface!(InputInterface); impl InterfaceAspect for Interface {
pub struct InputInterface;
impl InterfaceAspect for InputInterface {
#[doc = "Create an input method node"] #[doc = "Create an input method node"]
fn create_input_method( fn create_input_method(
_node: Arc<Node>, _node: Arc<Node>,
@@ -147,6 +165,6 @@ pub fn process_input() {
let _ = input_handler_client::input(&handler_node, &methods, &datas); let _ = input_handler_client::input(&handler_node, &methods, &datas);
} }
for method in methods { for method in methods {
method.internal_capture_requests.clear(); method.cull_capture_attempts();
} }
} }

View File

@@ -3,7 +3,7 @@ use crate::nodes::{
fields::{Field, FieldTrait, Ray, RayMarchResult}, fields::{Field, FieldTrait, Ray, RayMarchResult},
spatial::Spatial, spatial::Spatial,
}; };
use glam::{vec3, Mat4, Quat}; use glam::{Mat4, Quat, vec3};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
impl Default for Pointer { impl Default for Pointer {

View File

@@ -1,30 +1,30 @@
use super::{ use super::{Item, ItemType, create_item_acceptor_flex, register_item_ui_flex};
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType, use crate::bail;
}; use crate::core::error::Result;
use crate::nodes::Aspect;
use crate::nodes::AspectIdentifier;
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO; use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO; use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
use crate::{ use crate::{
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender}, core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
create_interface,
nodes::{ nodes::{
Message, Node,
drawable::{ drawable::{
model::{MaterialWrapper, ModelPart}, model::{MaterialWrapper, ModelPart},
shaders::UNLIT_SHADER_BYTES, shaders::UNLIT_SHADER_BYTES,
}, },
items::TypeInfo, items::TypeInfo,
spatial::{Spatial, Transform}, spatial::{Spatial, Transform},
Message, Node,
}, },
}; };
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::{ColumnMatrix4, Vector2}; use mint::{ColumnMatrix4, Vector2};
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc; use std::sync::Arc;
use std::sync::OnceLock;
use stereokit_rust::{ use stereokit_rust::{
material::{Material, Transparency}, material::{Material, Transparency},
shader::Shader, shader::Shader,
@@ -33,7 +33,6 @@ use stereokit_rust::{
tex::{Tex, TexFormat, TexType}, tex::{Tex, TexFormat, TexType},
util::Color128, util::Color128,
}; };
use tracing::error;
pub struct TexWrapper(pub Tex); pub struct TexWrapper(pub Tex);
unsafe impl Send for TexWrapper {} unsafe impl Send for TexWrapper {}
@@ -48,6 +47,12 @@ lazy_static! {
ui: Default::default(), ui: Default::default(),
items: Registry::new(), items: Registry::new(),
acceptors: Registry::new(), acceptors: Registry::new(),
add_acceptor_aspect: |node| {
node.add_aspect(CameraItemAcceptor);
},
add_ui_aspect: |node| {
node.add_aspect(CameraItemUi);
},
new_acceptor_fn: |node, acceptor, acceptor_field| { new_acceptor_fn: |node, acceptor, acceptor_field| {
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field); let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
} }
@@ -62,30 +67,27 @@ 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<TexWrapper>, sk_tex: OnceLock<TexWrapper>,
sk_mat: OnceCell<Arc<MaterialWrapper>>, sk_mat: OnceLock<Arc<MaterialWrapper>>,
applied_to: Registry<ModelPart>, applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>, apply_to: Registry<ModelPart>,
} }
#[allow(unused)] #[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( let item = Arc::new(CameraItem {
node, space: node.get_aspect::<Spatial>().unwrap().clone(),
&ITEM_TYPE_INFO_CAMERA, frame_info: Mutex::new(FrameInfo {
ItemType::Camera(CameraItem { proj_matrix,
space: node.get_aspect::<Spatial>().unwrap().clone(), px_size,
frame_info: Mutex::new(FrameInfo {
proj_matrix,
px_size,
}),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
applied_to: Registry::new(),
apply_to: Registry::new(),
}), }),
); sk_tex: OnceLock::new(),
// <CameraItem as CameraItemAspect>::node_methods(node); sk_mat: OnceLock::new(),
applied_to: Registry::new(),
apply_to: Registry::new(),
});
Item::add_to(node, &ITEM_TYPE_INFO_CAMERA, ItemType::Camera(item.clone()));
node.add_aspect_raw(item);
} }
fn frame_flex( fn frame_flex(
@@ -97,7 +99,7 @@ impl CameraItem {
response.wrap_sync(move || { response.wrap_sync(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else { else {
return Err(eyre!("Wrong item type?")); bail!("Wrong item type?");
}; };
Ok(serialize(())?.into()) Ok(serialize(())?.into())
}); });
@@ -109,7 +111,7 @@ impl CameraItem {
message: Message, message: Message,
) -> Result<()> { ) -> Result<()> {
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else { let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
bail!("Wrong item type?") bail!("Wrong item type?");
}; };
let model_part_node = let model_part_node =
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?; calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
@@ -137,19 +139,13 @@ impl CameraItem {
TexFormat::RGBA32Linear, TexFormat::RGBA32Linear,
)) ))
}); });
let sk_mat = self let sk_mat = self.sk_mat.get_or_init(|| {
.sk_mat let shader = Shader::from_memory(UNLIT_SHADER_BYTES).unwrap();
.get_or_try_init(|| -> Result<Arc<MaterialWrapper>> { let mut mat = Material::new(&shader, None);
let shader = Shader::from_memory(UNLIT_SHADER_BYTES)?; mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
let mut mat = Material::new(&shader, None); mat.transparency(Transparency::Blend);
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0); Arc::new(MaterialWrapper(mat))
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())
} }
@@ -167,9 +163,31 @@ impl CameraItem {
} }
} }
} }
impl AspectIdentifier for CameraItem {
impl_aspect_for_camera_item_aspect_id! {}
}
impl Aspect for CameraItem {
impl_aspect_for_camera_item_aspect! {}
}
impl CameraItemAspect for CameraItem {} impl CameraItemAspect for CameraItem {}
impl CameraItemAcceptorAspect for ItemAcceptor { pub struct CameraItemUi;
impl AspectIdentifier for CameraItemUi {
impl_aspect_for_camera_item_ui_aspect_id! {}
}
impl Aspect for CameraItemUi {
impl_aspect_for_camera_item_ui_aspect! {}
}
impl CameraItemUiAspect for CameraItemUi {}
pub struct CameraItemAcceptor;
impl AspectIdentifier for CameraItemAcceptor {
impl_aspect_for_camera_item_acceptor_aspect_id! {}
}
impl Aspect for CameraItemAcceptor {
impl_aspect_for_camera_item_acceptor_aspect! {}
}
impl CameraItemAcceptorAspect for CameraItemAcceptor {
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> { fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
super::acceptor_capture_item_flex(node, item) super::acceptor_capture_item_flex(node, item)
} }
@@ -184,8 +202,7 @@ pub fn update(token: &MainThreadToken) {
} }
} }
create_interface!(ItemInterface); impl InterfaceAspect for Interface {
impl InterfaceAspect for ItemInterface {
#[doc = "Create a camera item at a specific location"] #[doc = "Create a camera item at a specific location"]
fn create_camera_item( fn create_camera_item(
_node: Arc<Node>, _node: Arc<Node>,
@@ -206,19 +223,21 @@ impl InterfaceAspect for ItemInterface {
} }
#[doc = "Register this client to manage camera items and create default 3D UI for them."] #[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<()> { fn register_camera_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
node.add_aspect(CameraItemUi);
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA) 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>`."] #[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( fn create_camera_item_acceptor(
_node: Arc<Node>, node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
id: u64, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
field: Arc<Node>, field: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
node.add_aspect(CameraItemAcceptor);
create_item_acceptor_flex( create_item_acceptor_flex(
calling_client, calling_client,
id, id,

View File

@@ -4,15 +4,16 @@ pub mod panel;
use self::camera::CameraItem; use self::camera::CameraItem;
use self::panel::PanelItemTrait; use self::panel::PanelItemTrait;
use super::alias::AliasList; use super::alias::AliasList;
use super::fields::{Field, FIELD_ALIAS_INFO}; use super::fields::{FIELD_ALIAS_INFO, Field};
use super::spatial::Spatial; use super::spatial::Spatial;
use super::{Alias, Aspect, Node}; use super::{Alias, Aspect, AspectIdentifier, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::error::Result;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::ensure;
use crate::nodes::alias::AliasInfo; use crate::nodes::alias::AliasInfo;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{ensure, Result}; use crate::nodes::spatial::Transform;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::hash::Hash; use std::hash::Hash;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -47,6 +48,8 @@ pub struct TypeInfo {
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 add_ui_aspect: fn(node: &Node),
pub add_acceptor_aspect: fn(node: &Node),
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>), pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
} }
impl Hash for TypeInfo { impl Hash for TypeInfo {
@@ -81,7 +84,6 @@ impl Item {
}; };
let item = type_info.items.add(item); let item = type_info.items.add(item);
<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);
} }
@@ -108,8 +110,11 @@ impl Item {
) )
} }
} }
impl AspectIdentifier for Item {
impl_aspect_for_item_aspect_id! {}
}
impl Aspect for Item { impl Aspect for Item {
const NAME: &'static str = "Item"; impl_aspect_for_item_aspect! {}
} }
impl ItemAspect for Item { impl ItemAspect for Item {
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> { fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -129,7 +134,7 @@ impl Drop for Item {
} }
pub enum ItemType { pub enum ItemType {
Camera(CameraItem), Camera(Arc<CameraItem>),
Panel(Arc<dyn PanelItemTrait>), Panel(Arc<dyn PanelItemTrait>),
} }
impl ItemType { impl ItemType {
@@ -284,8 +289,11 @@ impl ItemUI {
.remove_aspect(acceptor.field.as_ref()); .remove_aspect(acceptor.field.as_ref());
} }
} }
impl AspectIdentifier for ItemUI {
impl_aspect_for_item_ui_aspect_id! {}
}
impl Aspect for ItemUI { impl Aspect for ItemUI {
const NAME: &'static str = "Item"; impl_aspect_for_item_ui_aspect! {}
} }
impl Drop for ItemUI { impl Drop for ItemUI {
fn drop(&mut self) { fn drop(&mut self) {
@@ -342,8 +350,11 @@ impl ItemAcceptor {
let _ = item_acceptor_client::release_item(&node, alias.id); let _ = item_acceptor_client::release_item(&node, alias.id);
} }
} }
impl AspectIdentifier for ItemAcceptor {
impl_aspect_for_item_acceptor_aspect_id! {}
}
impl Aspect for ItemAcceptor { impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor"; impl_aspect_for_item_acceptor_aspect! {}
} }
impl ItemAcceptorAspect for ItemAcceptor {} impl ItemAcceptorAspect for ItemAcceptor {}
impl Drop for ItemAcceptor { impl Drop for ItemAcceptor {
@@ -364,6 +375,7 @@ pub fn register_item_ui_flex(
) -> Result<()> { ) -> Result<()> {
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?; let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?; ItemUI::add_to(&ui, type_info)?;
(type_info.add_ui_aspect)(&ui);
Ok(()) Ok(())
} }
fn create_item_acceptor_flex( fn create_item_acceptor_flex(
@@ -381,6 +393,7 @@ fn create_item_acceptor_flex(
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?; let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(space.clone()), transform, false); Spatial::add_to(&node, Some(space.clone()), transform, false);
ItemAcceptor::add_to(&node, type_info, field); ItemAcceptor::add_to(&node, type_info, field);
(type_info.add_acceptor_aspect)(&node);
Ok(()) Ok(())
} }
@@ -391,6 +404,3 @@ fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
Ok(()) Ok(())
} }
struct ItemInterface;
// create_interface!(ItemInterface);

View File

@@ -1,23 +1,27 @@
use super::{create_item_acceptor_flex, register_item_ui_flex, ItemAcceptor, ItemInterface}; use super::camera::CameraItemAcceptor;
use super::{create_item_acceptor_flex, register_item_ui_flex};
use crate::bail;
use crate::core::error::Result;
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO; use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO; use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
use crate::nodes::{Aspect, AspectIdentifier};
use crate::{ use crate::{
core::{ core::{
client::{get_env, state, Client, INTERNAL_CLIENT}, client::{Client, INTERNAL_CLIENT, get_env, state},
registry::Registry, registry::Registry,
}, },
create_interface,
nodes::{ nodes::{
Node,
drawable::model::ModelPart, drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo}, items::{Item, ItemType, TypeInfo},
spatial::{Spatial, Transform}, spatial::{Spatial, Transform},
Node,
}, },
}; };
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 parking_lot::Mutex;
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tracing::{debug, info}; use tracing::{debug, info};
@@ -32,6 +36,7 @@ impl Default for Geometry {
} }
lazy_static! { lazy_static! {
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel", type_name: "panel",
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(), alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
@@ -39,6 +44,12 @@ lazy_static! {
ui: Default::default(), ui: Default::default(),
items: Registry::new(), items: Registry::new(),
acceptors: Registry::new(), acceptors: Registry::new(),
add_acceptor_aspect: |node| {
node.add_aspect(PanelItemUi);
},
add_ui_aspect: |node| {
node.add_aspect(PanelItemAcceptor);
},
new_acceptor_fn: |node, acceptor, acceptor_field| { new_acceptor_fn: |node, acceptor, acceptor_field| {
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field); let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
} }
@@ -65,7 +76,7 @@ pub trait Backend: Send + Sync + 'static {
scroll_steps: Option<Vector2<f32>>, scroll_steps: Option<Vector2<f32>>,
); );
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>); fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool);
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>);
@@ -115,7 +126,7 @@ impl<B: Backend> PanelItem<B> {
&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_aspect_raw(panel_item.clone());
(node, panel_item) (node, panel_item)
} }
@@ -197,9 +208,12 @@ impl<B: Backend> PanelItem<B> {
panel_item_client::destroy_child(&node, id); panel_item_client::destroy_child(&node, id);
} }
} }
impl<B: Backend> AspectIdentifier for PanelItem<B> {
// make these stupid vectors u32 in the protocol somehow!!!!!!!1 impl_aspect_for_panel_item_aspect_id! {}
}
impl<B: Backend> Aspect for PanelItem<B> {
impl_aspect_for_panel_item_aspect! {}
}
#[allow(unused)] #[allow(unused)]
impl<B: Backend> PanelItemAspect for PanelItem<B> { impl<B: Backend> PanelItemAspect for PanelItem<B> {
#[doc = "Apply the cursor as a material to a model."] #[doc = "Apply the cursor as a material to a model."]
@@ -341,19 +355,20 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
} }
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."] #[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
fn keyboard_keys( fn keyboard_key(
node: Arc<Node>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
surface: SurfaceId, surface: SurfaceId,
keymap_id: u64, keymap_id: u64,
keys: Vec<i32>, key: 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(());
}; };
panel_item panel_item
.backend() .backend()
.keyboard_keys(&surface, keymap_id, keys); .keyboard_key(&surface, keymap_id, key, pressed);
Ok(()) Ok(())
} }
@@ -405,7 +420,23 @@ impl<B: Backend> PanelItemAspect for PanelItem<B> {
} }
} }
impl PanelItemAcceptorAspect for ItemAcceptor { pub struct PanelItemUi;
impl AspectIdentifier for PanelItemUi {
impl_aspect_for_panel_item_ui_aspect_id! {}
}
impl Aspect for PanelItemUi {
impl_aspect_for_panel_item_ui_aspect! {}
}
impl PanelItemUiAspect for PanelItemUi {}
pub struct PanelItemAcceptor;
impl AspectIdentifier for PanelItemAcceptor {
impl_aspect_for_panel_item_acceptor_aspect_id! {}
}
impl Aspect for PanelItemAcceptor {
impl_aspect_for_panel_item_acceptor_aspect! {}
}
impl PanelItemAcceptorAspect for PanelItemAcceptor {
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> { fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
super::acceptor_capture_item_flex(node, item) super::acceptor_capture_item_flex(node, item)
} }
@@ -435,22 +466,23 @@ impl<B: Backend> Drop for PanelItem<B> {
} }
} }
create_interface!(ItemInterface); impl InterfaceAspect for Interface {
impl InterfaceAspect for ItemInterface {
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."] #[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<()> { fn register_panel_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
node.add_aspect(CameraItemAcceptor);
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL) 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>`."] #[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( fn create_panel_item_acceptor(
_node: Arc<Node>, node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
id: u64, id: u64,
parent: Arc<Node>, parent: Arc<Node>,
transform: Transform, transform: Transform,
field: Arc<Node>, field: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
node.add_aspect(PanelItemAcceptor);
create_item_acceptor_flex( create_item_acceptor_flex(
calling_client, calling_client,
id, id,
@@ -460,4 +492,36 @@ impl InterfaceAspect for ItemInterface {
field, field,
) )
} }
async fn register_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap: String,
) -> Result<u64> {
let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps
.iter()
.filter(|(_k, v)| *v == &keymap)
.map(|(k, _v)| k)
.last()
{
return Ok(found_keymap_id.data().as_ffi());
}
let key = keymaps.insert(keymap);
Ok(key.data().as_ffi())
}
async fn get_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap_id: u64,
) -> Result<String> {
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
bail!("Could not find keymap. Try registering it");
};
Ok(keymap.clone())
}
} }

View File

@@ -1,6 +1,5 @@
pub mod alias; pub mod alias;
pub mod audio; pub mod audio;
pub mod data;
pub mod drawable; pub mod drawable;
pub mod fields; pub mod fields;
pub mod input; pub mod input;
@@ -10,13 +9,11 @@ pub mod spatial;
use self::alias::Alias; use self::alias::Alias;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::error::{Result, ServerError};
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender; use crate::core::scenegraph::MethodResponseSender;
use color_eyre::eyre::{eyre, Result}; use dashmap::DashMap;
use parking_lot::Mutex; use serde::{Serialize, de::DeserializeOwned};
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use spatial::Spatial; use spatial::Spatial;
use stardust_xr::messenger::MessageSenderHandle; use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::scenegraph::ScenegraphError;
@@ -24,6 +21,7 @@ use stardust_xr::schemas::flex::{deserialize, serialize};
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::fmt::Debug; use std::fmt::Debug;
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::vec::Vec; use std::vec::Vec;
@@ -46,11 +44,29 @@ impl AsRef<[u8]> for Message {
} }
} }
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
stardust_xr_server_codegen::codegen_node_protocol!(); stardust_xr_server_codegen::codegen_node_protocol!();
pub struct Owned;
impl AspectIdentifier for Owned {
impl_aspect_for_owned_aspect_id! {}
}
impl Aspect for Owned {
impl_aspect_for_owned_aspect! {}
}
impl OwnedAspect for Owned {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.set_enabled(enabled);
Ok(())
}
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
}
}
pub struct OwnedNode(pub Arc<Node>); pub struct OwnedNode(pub Arc<Node>);
impl Drop for OwnedNode { impl Drop for OwnedNode {
fn drop(&mut self) { fn drop(&mut self) {
@@ -64,8 +80,6 @@ pub struct Node {
client: Weak<Client>, client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>, message_sender_handle: Option<MessageSenderHandle>,
local_signals: Mutex<FxHashMap<u64, Signal>>,
local_methods: Mutex<FxHashMap<u64, Method>>,
aliases: Registry<Alias>, aliases: Registry<Alias>,
aspects: Aspects, aspects: Aspects,
destroyable: bool, destroyable: bool,
@@ -87,26 +101,24 @@ impl Node {
client: Arc::downgrade(client), client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(), message_sender_handle: client.message_sender_handle.clone(),
id, id,
local_signals: Default::default(),
local_methods: Default::default(),
aliases: Default::default(), aliases: Default::default(),
aspects: Default::default(), aspects: Default::default(),
destroyable, destroyable,
}; };
<Node as OwnedAspect>::add_node_members(&node); node.aspects.add(Owned);
node node
} }
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> { pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
Ok(self Ok(self
.get_client() .get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))? .ok_or(ServerError::NoClient)?
.scenegraph .scenegraph
.add_node(self)) .add_node(self))
} }
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> { pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
Ok(OwnedNode( Ok(OwnedNode(
self.get_client() self.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))? .ok_or(ServerError::NoClient)?
.scenegraph .scenegraph
.add_node(self), .add_node(self),
)) ))
@@ -118,7 +130,8 @@ impl Node {
.global_transform() .global_transform()
.to_scale_rotation_translation() .to_scale_rotation_translation()
.0 .0
.length_squared() > 0.0 .length_squared()
> 0.0
} else { } else {
true true
} }
@@ -146,60 +159,57 @@ impl Node {
// Ok(serialize(pid)?.into()) // Ok(serialize(pid)?.into())
// } // }
pub fn add_local_signal(&self, id: u64, signal: Signal) { pub fn add_aspect<A: AspectIdentifier>(&self, aspect: A) -> Arc<A> {
self.local_signals.lock().insert(id, signal);
}
pub fn add_local_method(&self, id: u64, method: Method) {
self.local_methods.lock().insert(id, method);
}
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
self.aspects.add(aspect) self.aspects.add(aspect)
} }
pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) { pub fn add_aspect_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
self.aspects.add_raw(aspect) self.aspects.add_raw(aspect)
} }
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> { pub fn get_aspect<A: AspectIdentifier>(&self) -> Result<Arc<A>> {
self.aspects.get() self.aspects.get()
} }
pub fn send_local_signal( pub fn send_local_signal(
self: Arc<Self>, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
aspect_id: u64,
method: u64, 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::MemberNotFound);
} }
alias alias
.original .original
.upgrade() .upgrade()
.ok_or(ScenegraphError::BrokenAlias)? .ok_or(ScenegraphError::BrokenAlias)?
.send_local_signal(calling_client, method, message) .send_local_signal(calling_client, aspect_id, method, message)
} else { } else {
let signal = self let aspect = self
.local_signals .aspects
.lock() .0
.get(&method) .get(&aspect_id)
.cloned() .ok_or(ScenegraphError::AspectNotFound)?
.ok_or(ScenegraphError::SignalNotFound)?; .clone();
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError { aspect
error: error.to_string(), .run_signal(calling_client, self.clone(), method, message)
}) .map_err(|error| ScenegraphError::MemberError {
error: error.to_string(),
})
} }
} }
pub fn execute_local_method( pub fn execute_local_method(
self: Arc<Self>, self: Arc<Self>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
aspect_id: u64,
method: u64, 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::MemberNotFound));
return; return;
} }
let Some(alias) = alias.original.upgrade() else { let Some(alias) = alias.original.upgrade() else {
@@ -208,6 +218,7 @@ impl Node {
}; };
alias.execute_local_method( alias.execute_local_method(
calling_client, calling_client,
aspect_id,
method, method,
Message { Message {
data: message.data.clone(), data: message.data.clone(),
@@ -216,14 +227,19 @@ impl Node {
response, response,
) )
} else { } else {
let Some(method) = self.local_methods.lock().get(&method).cloned() else { let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else {
response.send(Err(ScenegraphError::MethodNotFound)); response.send(Err(ScenegraphError::AspectNotFound));
return; return;
}; };
method(self, calling_client, message, response); aspect.run_method(calling_client, self.clone(), method, message, response);
} }
} }
pub fn send_remote_signal(&self, method: u64, message: impl Into<Message>) -> Result<()> { pub fn send_remote_signal(
&self,
aspect_id: u64,
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()
@@ -233,6 +249,7 @@ impl Node {
.for_each(|node| { .for_each(|node| {
// Beware! file descriptors will not be sent to aliases!!! // Beware! file descriptors will not be sent to aliases!!!
let _ = node.send_remote_signal( let _ = node.send_remote_signal(
aspect_id,
method, method,
Message { Message {
data: message.data.clone(), data: message.data.clone(),
@@ -241,12 +258,13 @@ impl Node {
); );
}); });
if let Some(handle) = self.message_sender_handle.as_ref() { if let Some(handle) = self.message_sender_handle.as_ref() {
handle.signal(self.id, method, &message.data, message.fds)?; handle.signal(self.id, aspect_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,
aspect_id: u64,
method: u64, method: u64,
input: S, input: S,
fds: Vec<OwnedFd>, fds: Vec<OwnedFd>,
@@ -254,13 +272,13 @@ impl Node {
let message_sender_handle = self let message_sender_handle = self
.message_sender_handle .message_sender_handle
.as_ref() .as_ref()
.ok_or(eyre!("Messenger does not exist for this node"))?; .ok_or(ServerError::NoMessenger)?;
let serialized = serialize(input)?; let serialized = serialize(input)?;
let result = message_sender_handle let result = message_sender_handle
.method(self.id, method, &serialized, fds)? .method(self.id, aspect_id, method, &serialized, fds)
.await .await?
.map_err(|e| eyre!(e))?; .map_err(ServerError::RemoteMethodError)?;
let (message, fds) = result.into_components(); let (message, fds) = result.into_components();
let deserialized: D = deserialize(&message)?; let deserialized: D = deserialize(&message)?;
@@ -271,61 +289,62 @@ 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("id", &self.id) .field("id", &self.id)
.field("local_signals", &self.local_signals.lock().keys())
.field("local_methods", &self.local_methods.lock().keys())
.field("destroyable", &self.destroyable) .field("destroyable", &self.destroyable)
.finish() .finish()
} }
} }
impl OwnedAspect for Node {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.set_enabled(enabled);
Ok(())
}
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
}
}
impl Drop for Node { impl Drop for Node {
fn drop(&mut self) { fn drop(&mut self) {
// Debug breakpoint // Debug breakpoint
} }
} }
pub trait AspectIdentifier: Aspect {
const ID: u64;
}
pub trait Aspect: Any + Send + Sync + 'static { pub trait Aspect: Any + Send + Sync + 'static {
const NAME: &'static str; fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
fn run_signal(
&self,
calling_client: Arc<Client>,
node: Arc<Node>,
signal: u64,
message: Message,
) -> Result<(), stardust_xr::scenegraph::ScenegraphError>;
fn run_method(
&self,
calling_client: Arc<Client>,
node: Arc<Node>,
method: u64,
message: Message,
response: MethodResponseSender,
);
} }
#[derive(Default)] #[derive(Default)]
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>); struct Aspects(DashMap<u64, Arc<dyn Aspect>>);
impl Aspects { impl Aspects {
fn add<A: Aspect>(&self, t: A) -> Arc<A> { fn add<A: AspectIdentifier>(&self, t: A) -> Arc<A> {
let aspect = Arc::new(t); let aspect = Arc::new(t);
self.add_raw(aspect.clone()); self.add_raw(aspect.clone());
aspect aspect
} }
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) { fn add_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
self.0.lock().insert(Self::type_key::<A>(), aspect); self.0.insert(A::ID, aspect);
} }
fn get<A: Aspect>(&self) -> Result<Arc<A>> { fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
self.0 self.0
.lock() .get(&A::ID)
.get(&Self::type_key::<A>()) // .cloned doesn't work for some reason
.and_then(|a| Arc::downcast(a.clone()).ok()) .map(|v| v.clone())
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase())) .map(|a| a.as_any())
} .and_then(|a| Arc::downcast(a).ok())
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
fn type_key<A: 'static>() -> TypeId {
TypeId::of::<A>()
} }
} }
impl Drop for Aspects { impl Drop for Aspects {
fn drop(&mut self) { fn drop(&mut self) {
self.0.lock().clear() // why would this be needed? do drop impls not run otherwise?
self.0.clear()
} }
} }

View File

@@ -1,19 +1,17 @@
use super::spatial::Spatial; use super::spatial::Spatial;
use super::Node; use super::{Aspect, AspectIdentifier, Node};
use crate::core::client::Client; use crate::bail;
use crate::core::client::{CLIENTS, Client};
use crate::core::client_state::ClientStateParsed; use crate::core::client_state::ClientStateParsed;
use crate::core::registry::Registry; use crate::core::error::Result;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO; use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use crate::session::connection_env; use crate::session::connection_env;
use color_eyre::eyre::{bail, Result};
use glam::Mat4; use glam::Mat4;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use tracing::info; use tracing::info;
static ROOT_REGISTRY: Registry<Root> = Registry::new();
stardust_xr_server_codegen::codegen_root_protocol!(); stardust_xr_server_codegen::codegen_root_protocol!();
pub struct Root { pub struct Root {
@@ -23,18 +21,20 @@ pub struct Root {
impl Root { impl Root {
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> { pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
let node = Node::from_id(client, 0, false); let node = Node::from_id(client, 0, false);
<Self as RootAspect>::add_node_members(&node);
let node = node.add_to_scenegraph()?; let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(&node, None, transform, false); let _ = Spatial::add_to(&node, None, transform, false);
let root_aspect = node.add_aspect(Root {
Ok(ROOT_REGISTRY.add(Root { node: node.clone(),
node,
connect_instant: Instant::now(), connect_instant: Instant::now(),
})) });
Ok(root_aspect)
} }
pub fn send_frame_events(delta: f64) { pub fn send_frame_events(delta: f64) {
for root in ROOT_REGISTRY.get_valid_contents() { for client in CLIENTS.get_vec() {
let Some(root) = client.root.get() else {
continue;
};
let _ = root_client::frame( let _ = root_client::frame(
&root.node, &root.node,
&FrameInfo { &FrameInfo {
@@ -47,13 +47,19 @@ impl Root {
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<ClientState> { pub async fn save_state(&self) -> Result<ClientState> {
Ok(root_client::save_state(&self.node).await?.0) Ok(root_client::save_state(&self.node).await?.0)
} }
} }
impl AspectIdentifier for Root {
impl_aspect_for_root_aspect_id! {}
}
impl Aspect for Root {
impl_aspect_for_root_aspect! {}
}
impl RootAspect for Root { impl RootAspect for Root {
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> { async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
let Some(state) = calling_client.state.get() else { let Some(state) = calling_client.state.get() else {
@@ -92,13 +98,8 @@ impl RootAspect for Root {
} }
#[doc = "Cleanly disconnect from the server"] #[doc = "Cleanly disconnect from the server"]
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> { fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
calling_client.disconnect(Ok(())); calling_client.disconnect(Ok(()));
Ok(()) Ok(())
} }
} }
impl Drop for Root {
fn drop(&mut self) {
ROOT_REGISTRY.remove(self);
}
}

View File

@@ -3,20 +3,20 @@ pub mod zone;
use self::zone::Zone; use self::zone::Zone;
use super::alias::Alias; use super::alias::Alias;
use super::fields::{Field, FieldTrait}; use super::fields::{Field, FieldTrait};
use super::Aspect; use super::{Aspect, AspectIdentifier};
use crate::bail;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::error::Result;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::create_interface;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use color_eyre::eyre::{eyre, OptionExt, Result}; use color_eyre::eyre::OptionExt;
use glam::{vec3a, Mat4, Quat, Vec3}; use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3; use mint::Vector3;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::ptr; use std::sync::{Arc, OnceLock, Weak};
use std::sync::{Arc, Weak}; use std::{f32, ptr};
use stereokit_rust::maths::Bounds; use stereokit_rust::maths::Bounds;
stardust_xr_server_codegen::codegen_spatial_protocol!(); stardust_xr_server_codegen::codegen_spatial_protocol!();
@@ -38,6 +38,12 @@ impl Transform {
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into()) Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
} }
} }
impl AspectIdentifier for Zone {
impl_aspect_for_zone_aspect_id! {}
}
impl Aspect for Zone {
impl_aspect_for_zone_aspect! {}
}
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default()); pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
@@ -52,7 +58,7 @@ pub struct Spatial {
transform: Mutex<Mat4>, transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>, zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>, pub bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
} }
impl Spatial { impl Spatial {
@@ -64,7 +70,7 @@ impl Spatial {
transform: Mutex::new(transform), transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()), zone: Mutex::new(Weak::new()),
children: Registry::new(), children: Registry::new(),
bounding_box_calc: OnceCell::default(), bounding_box_calc: OnceLock::default(),
}) })
} }
pub fn add_to( pub fn add_to(
@@ -74,16 +80,14 @@ impl Spatial {
zoneable: bool, zoneable: bool,
) -> Arc<Spatial> { ) -> Arc<Spatial> {
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform); let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
<Spatial as SpatialAspect>::add_node_members(node);
if zoneable { if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial); ZONEABLE_REGISTRY.add_raw(&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);
node.add_aspect_raw(spatial.clone()); node.add_aspect_raw(spatial.clone());
node.add_aspect(SpatialRef);
spatial spatial
} }
@@ -188,45 +192,29 @@ impl Spatial {
fn get_parent(&self) -> Option<Arc<Spatial>> { fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.lock().clone() self.parent.lock().clone()
} }
fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) { fn set_parent(self: &Arc<Self>, new_parent: &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 { new_parent.children.add_raw(self);
new_parent.children.add_raw(self);
}
*self.parent.lock() = new_parent.cloned(); *self.parent.lock() = Some(new_parent.clone());
} }
pub fn set_spatial_parent(self: &Arc<Self>, parent: Option<&Arc<Spatial>>) -> Result<()> { pub fn set_spatial_parent(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> {
let is_ancestor = parent if self.is_ancestor_of(parent.clone()) {
.as_ref() bail!("Setting spatial parent would cause a loop");
.map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
} }
self.set_parent(parent); self.set_parent(parent);
Ok(()) Ok(())
} }
pub fn set_spatial_parent_in_place( pub fn set_spatial_parent_in_place(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> {
self: &Arc<Self>, if self.is_ancestor_of(parent.clone()) {
parent: Option<&Arc<Spatial>>, bail!("Setting spatial parent would cause a loop");
) -> Result<()> {
let is_ancestor = parent
.as_ref()
.map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
} }
self.set_local_transform(Spatial::space_to_space_matrix( self.set_local_transform(Spatial::space_to_space_matrix(Some(self), Some(parent)));
Some(self),
parent.map(AsRef::as_ref),
));
self.set_parent(parent); self.set_parent(parent);
Ok(()) Ok(())
@@ -238,13 +226,109 @@ impl Spatial {
.upgrade() .upgrade()
.map(|zone| zone.field.clone()) .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::NEG_INFINITY)
} }
} }
impl Aspect for Spatial { impl AspectIdentifier for Spatial {
const NAME: &'static str = "Spatial"; impl_aspect_for_spatial_aspect_id! {}
} }
impl SpatialRefAspect for Spatial { impl Aspect for Spatial {
impl_aspect_for_spatial_aspect! {}
}
impl SpatialAspect for Spatial {
fn set_local_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(None, transform);
Ok(())
}
fn set_relative_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
Ok(())
}
fn set_spatial_parent(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent(&parent)?;
Ok(())
}
fn set_spatial_parent_in_place(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent_in_place(&parent)?;
Ok(())
}
fn set_zoneable(node: Arc<Node>, _calling_client: Arc<Client>, zoneable: bool) -> Result<()> {
let spatial = node.get_aspect::<Spatial>()?;
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
} else {
ZONEABLE_REGISTRY.remove(&spatial);
zone::release(&spatial);
}
Ok(())
}
// legit gotta find a way to remove old ones, this just keeps the node alive
async fn export_spatial(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
let id = rand::random();
EXPORTED_SPATIALS.lock().insert(id, node);
Ok(id)
}
}
impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool {
self.node.as_ptr() == other.node.as_ptr()
}
}
impl Debug for Spatial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Spatial")
.field("parent", &self.parent)
.field("old_parent", &self.old_parent)
.field("transform", &self.transform)
.finish()
}
}
impl Drop for Spatial {
fn drop(&mut self) {
zone::release(self);
ZONEABLE_REGISTRY.remove(self);
}
}
pub struct SpatialRef;
impl AspectIdentifier for SpatialRef {
impl_aspect_for_spatial_ref_aspect_id! {}
}
impl Aspect for SpatialRef {
impl_aspect_for_spatial_ref_aspect! {}
}
impl SpatialRefAspect for SpatialRef {
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>,
@@ -303,91 +387,6 @@ impl SpatialRefAspect for Spatial {
}) })
} }
} }
impl SpatialAspect for Spatial {
fn set_local_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(None, transform);
Ok(())
}
fn set_relative_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
transform: Transform,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
Ok(())
}
fn set_spatial_parent(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent(Some(&parent))?;
Ok(())
}
fn set_spatial_parent_in_place(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent_in_place(Some(&parent))?;
Ok(())
}
fn set_zoneable(node: Arc<Node>, _calling_client: Arc<Client>, zoneable: bool) -> Result<()> {
let spatial = node.get_aspect::<Spatial>()?;
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
} else {
ZONEABLE_REGISTRY.remove(&spatial);
zone::release(&spatial);
}
Ok(())
}
// legit gotta find a way to remove old ones, this just keeps the node alive
async fn export_spatial(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
let id = rand::random();
EXPORTED_SPATIALS.lock().insert(id, node);
Ok(id)
}
}
impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool {
self.node.as_ptr() == other.node.as_ptr()
}
}
impl Debug for Spatial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Spatial")
.field("parent", &self.parent)
.field("old_parent", &self.old_parent)
.field("transform", &self.transform)
.finish()
}
}
impl Drop for Spatial {
fn drop(&mut self) {
zone::release(self);
ZONEABLE_REGISTRY.remove(self);
}
}
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 { pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position let position = position
@@ -406,8 +405,7 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into()) Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
} }
pub struct SpatialInterface; impl InterfaceAspect for Interface {
impl InterfaceAspect for SpatialInterface {
fn create_spatial( fn create_spatial(
_node: Arc<Node>, _node: Arc<Node>,
calling_client: Arc<Client>, calling_client: Arc<Client>,
@@ -445,7 +443,7 @@ impl InterfaceAspect for SpatialInterface {
calling_client: Arc<Client>, calling_client: Arc<Client>,
uid: u64, uid: u64,
) -> Result<Arc<Node>> { ) -> Result<Arc<Node>> {
EXPORTED_SPATIALS Ok(EXPORTED_SPATIALS
.lock() .lock()
.get(&uid) .get(&uid)
.map(|s| { .map(|s| {
@@ -457,8 +455,6 @@ impl InterfaceAspect for SpatialInterface {
) )
.unwrap() .unwrap()
}) })
.ok_or_eyre("Couldn't find spatial with that ID") .ok_or_eyre("Couldn't find spatial with that ID")?)
} }
} }
create_interface!(SpatialInterface);

View File

@@ -1,42 +1,43 @@
use super::{ use super::{
Spatial, ZoneAspect, SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO, SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO, Spatial, ZONEABLE_REGISTRY,
ZONEABLE_REGISTRY, ZoneAspect,
}; };
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, error::Result, registry::Registry},
nodes::{ nodes::{
alias::{get_original, Alias, AliasList}, Node,
alias::{Alias, AliasList, get_original},
fields::{Field, FieldTrait}, fields::{Field, FieldTrait},
Aspect, Node,
}, },
}; };
use color_eyre::eyre::Result;
use glam::vec3a; use glam::vec3a;
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.field.distance(spatial, vec3a(0.0, 0.0, 0.0)); let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
if new_distance.abs() < old_distance.abs() { if new_distance.abs() > old_distance.abs() {
release(spatial); return;
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
let Some(zone_node) = zone.spatial.node.upgrade() else {
return;
};
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);
} }
release(spatial);
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
let Some(zone_node) = zone.spatial.node.upgrade() else {
return;
};
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: &Spatial) { pub fn release(spatial: &Spatial) {
let Some(spatial_node) = spatial.node.upgrade() else { let Some(spatial_node) = spatial.node.upgrade() else {
@@ -44,7 +45,10 @@ pub fn release(spatial: &Spatial) {
}; };
let spatial = spatial_node.get_aspect::<Spatial>().unwrap(); let spatial = spatial_node.get_aspect::<Spatial>().unwrap();
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref()); let Some(old_parent) = spatial.old_parent.lock().take() else {
return;
};
let _ = spatial.set_spatial_parent_in_place(&old_parent);
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() {
@@ -73,7 +77,6 @@ impl Zone {
intersecting: AliasList::default(), intersecting: AliasList::default(),
captured: AliasList::default(), captured: AliasList::default(),
}); });
<Zone as ZoneAspect>::add_node_members(node);
node.add_aspect_raw(zone.clone()); node.add_aspect_raw(zone.clone());
zone zone
} }
@@ -82,15 +85,17 @@ impl Zone {
let current_zoneables = Registry::new(); let current_zoneables = Registry::new();
for zoneable in ZONEABLE_REGISTRY.get_valid_contents() { for zoneable in ZONEABLE_REGISTRY.get_valid_contents() {
// Skip if the zoneable is an ancestor of the zone or the zone itself
if zoneable.is_ancestor_of(self.spatial.clone()) {
continue;
}
let distance = self.field.distance(&zoneable, [0.0; 3].into()); let distance = self.field.distance(&zoneable, [0.0; 3].into());
if distance > 0.0 { if distance > 0.0 {
continue; continue;
} }
if let Some(zone) = zoneable.zone.lock().upgrade() { if distance < zoneable.zone_distance() {
let zoneable_distance = zone.field.distance(&zoneable, [0.0; 3].into()); continue;
if zoneable_distance < distance {
continue;
}
} }
current_zoneables.add_raw(&zoneable); current_zoneables.add_raw(&zoneable);
} }
@@ -124,9 +129,6 @@ impl Zone {
Ok(()) Ok(())
} }
} }
impl Aspect for Zone {
const NAME: &'static str = "Zone";
}
impl ZoneAspect for Zone { impl ZoneAspect for Zone {
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> { fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let zone = node.get_aspect::<Zone>()?; let zone = node.get_aspect::<Zone>()?;

View File

@@ -1,14 +1,14 @@
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
fields::{FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node, OwnedNode, Node, OwnedNode,
fields::{FieldTrait, Ray},
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputMethod, Pointer},
spatial::Spatial,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec3, Mat4}; use glam::{Mat4, 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;

View File

@@ -5,40 +5,43 @@ pub mod sk_hand;
use crate::nodes::{ use crate::nodes::{
fields::{Field, FieldTrait, Ray}, fields::{Field, FieldTrait, Ray},
input::{InputDataTrait, InputDataType, InputHandler, InputMethod, INPUT_HANDLER_REGISTRY}, input::{INPUT_HANDLER_REGISTRY, InputDataTrait, InputDataType, InputHandler, InputMethod},
spatial::Spatial, spatial::Spatial,
}; };
use glam::vec3; use glam::vec3;
use std::sync::Arc; use std::{
collections::VecDeque,
sync::{Arc, Weak},
};
#[derive(Default)] #[derive(Default)]
pub struct CaptureManager { pub struct CaptureManager {
pub capture: Option<Arc<InputHandler>>, pub capture: Weak<InputHandler>,
} }
impl CaptureManager { impl CaptureManager {
pub fn update_capture(&mut self, pointer: &InputMethod) { pub fn update_capture(&mut self, method: &InputMethod) {
if let Some(capture) = &self.capture { if let Some(capture) = &self.capture.upgrade() {
if !pointer if !method
.internal_capture_requests .capture_attempts
.get_valid_contents() .get_valid_contents()
.contains(capture) .contains(capture)
{ {
self.capture.take(); self.capture = Weak::new();
} }
} }
} }
pub fn set_new_capture( pub fn set_new_capture(
&mut self, &mut self,
pointer: &InputMethod, method: &InputMethod,
distance_calculator: DistanceCalculator, distance_calculator: DistanceCalculator,
) { ) {
if self.capture.is_none() { if self.capture.upgrade().is_none() {
self.capture = find_closest_capture(pointer, distance_calculator); self.capture = find_closest_capture(method, distance_calculator);
} }
} }
pub fn apply_capture(&self, method: &InputMethod) { pub fn apply_capture(&self, method: &InputMethod) {
method.captures.clear(); method.captures.clear();
if let Some(capture) = &self.capture { if let Some(capture) = &self.capture.upgrade() {
method.set_handler_order([capture].into_iter()); method.set_handler_order([capture].into_iter());
method.captures.add_raw(capture); method.captures.add_raw(capture);
} }
@@ -50,9 +53,9 @@ type DistanceCalculator = fn(&Arc<Spatial>, &InputDataType, &Field) -> Option<f3
pub fn find_closest_capture( pub fn find_closest_capture(
method: &InputMethod, method: &InputMethod,
distance_calculator: DistanceCalculator, distance_calculator: DistanceCalculator,
) -> Option<Arc<InputHandler>> { ) -> Weak<InputHandler> {
method method
.internal_capture_requests .capture_attempts
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter_map(|h| { .filter_map(|h| {
@@ -60,39 +63,31 @@ pub fn find_closest_capture(
.map(|dist| (h.clone(), dist)) .map(|dist| (h.clone(), dist))
}) })
.min_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap()) .min_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap())
.map(|(handler, _)| handler) .map(|(handler, _)| Arc::downgrade(&handler))
.unwrap_or_default()
} }
/// sorts them greatest to least distance (so you can pop off the closest ones easily)
pub fn get_sorted_handlers( pub fn get_sorted_handlers(
method: &InputMethod, method: &InputMethod,
distance_calculator: DistanceCalculator, distance_calculator: DistanceCalculator,
) -> Vec<Arc<InputHandler>> { ) -> Vec<(Arc<InputHandler>, f32)> {
INPUT_HANDLER_REGISTRY let mut handlers = INPUT_HANDLER_REGISTRY
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter(|handler| handler.spatial.node().map_or(false, |node| node.enabled())) .filter(|handler| handler.spatial.node().is_some_and(|node| node.enabled()))
.filter(|handler| { .filter(|handler| {
handler handler
.field .field
.spatial .spatial
.node() .node()
.map_or(false, |node| node.enabled()) .is_some_and(|node| node.enabled())
}) })
.filter_map(|handler| { .filter_map(|handler| {
distance_calculator(&method.spatial, &method.data.lock(), &handler.field) distance_calculator(&method.spatial, &method.data.lock(), &handler.field)
.map(|distance| (vec![handler], distance)) .map(|distance| (handler, distance))
}) })
.filter(|(_, distance)| *distance > 0.0) .collect::<Vec<_>>();
.reduce(|(mut handlers_a, distance_a), (handlers_b, distance_b)| { handlers.sort_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap());
if (distance_a - distance_b).abs() < 0.001 { handlers
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

@@ -1,26 +1,29 @@
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
data::{
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
},
fields::{FieldTrait, Ray},
input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node, OwnedNode, Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer},
items::panel::KEYMAPS,
spatial::Spatial,
}, },
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{vec3, Mat4, Vec3}; use glam::{Mat4, Vec3, vec3};
use mint::Vector2; use mint::Vector2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey}; use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::values::Datamap; use stardust_xr::{
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
values::Datamap,
};
use std::sync::Arc; use std::sync::Arc;
use stereokit_rust::system::{Input, Key}; use stereokit_rust::system::{Input, Key};
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat}; use tokio::task::JoinSet;
use tokio::time::{Duration, timeout};
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator}; use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
use zbus::{Connection, names::OwnedInterfaceName};
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
struct MouseEvent { struct MouseEvent {
@@ -46,12 +49,14 @@ impl Default for MouseEvent {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[zbus::proxy(
pub struct KeyboardEvent { interface = "org.stardustxr.XKBv1",
pub keyboard: (), default_service = "org.stardustxr.XKBv1"
pub xkbv1: (), )]
pub keymap_id: u64, trait KeyboardHandler {
pub keys: Vec<i32>, async fn keymap(&self, keymap_id: u64) -> zbus::Result<()>;
async fn key_state(&self, key: u32, pressed: bool) -> zbus::Result<()>;
async fn reset(&self) -> zbus::Result<()>;
} }
#[allow(unused)] #[allow(unused)]
@@ -62,8 +67,6 @@ pub struct MousePointer {
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
mouse_datamap: MouseEvent, mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent,
keyboard_sender: Arc<PulseSender>,
} }
impl MousePointer { impl MousePointer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
@@ -83,29 +86,16 @@ impl MousePointer {
.unwrap(), .unwrap(),
); );
let keyboard_sender = PulseSender::add_to(
&node.0,
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
)
.unwrap();
Ok(MousePointer { Ok(MousePointer {
node, node,
spatial, spatial,
pointer, pointer,
capture_manager: CaptureManager::default(), capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(), mouse_datamap: Default::default(),
keyboard_datamap: KeyboardEvent {
keyboard: (),
xkbv1: (),
keymap_id: keymap.data().as_ffi(),
keys: vec![],
},
keymap, keymap,
keyboard_sender,
}) })
} }
pub fn update(&mut self) { pub fn update(&mut self, dbus_connection: &Connection, object_registry: &ObjectRegistry) {
let mouse = Input::get_mouse(); let mouse = Input::get_mouse();
let ray = mouse.get_ray(); let ray = mouse.get_ray();
@@ -123,7 +113,9 @@ impl MousePointer {
select: Input::key(Key::MouseLeft).is_active() as u32 as f32, select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32, middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
context: Input::key(Key::MouseRight).is_active() as u32 as f32, context: Input::key(Key::MouseRight).is_active() as u32 as f32,
grab: Input::key(Key::MouseBack).is_active() as u32 as f32, grab: (Input::key(Key::MouseRight).is_active()
|| (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_continuous: [0.0, mouse.scroll_change / 120.0].into(),
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(), scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
raw_input_events: vec![], raw_input_events: vec![],
@@ -131,7 +123,7 @@ impl MousePointer {
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap(); *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
} }
self.target_pointer_input(); self.target_pointer_input();
self.send_keyboard_input(); self.send_keyboard_input(dbus_connection, object_registry);
} }
fn target_pointer_input(&mut self) { fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| { let distance_calculator: DistanceCalculator = |space, data, field| {
@@ -150,64 +142,115 @@ impl MousePointer {
.set_new_capture(&self.pointer, distance_calculator); .set_new_capture(&self.pointer, distance_calculator);
self.capture_manager.apply_capture(&self.pointer); self.capture_manager.apply_capture(&self.pointer);
if self.capture_manager.capture.is_some() { if self.capture_manager.capture.upgrade().is_some() {
return; return;
} }
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator); let mut handlers = get_sorted_handlers(&self.pointer, distance_calculator);
self.pointer.set_handler_order(sorted_handlers.iter()); let first_distance = handlers
.first()
.map(|(_, distance)| *distance)
.unwrap_or(f32::NEG_INFINITY);
self.pointer.set_handler_order(
handlers
.iter()
.filter(|(handler, distance)| (distance - first_distance).abs() <= 0.001)
.map(|(handler, _)| handler),
);
} }
fn send_keyboard_input(&mut self) { pub fn send_keyboard_input(
let rx = PULSE_RECEIVER_REGISTRY &mut self,
.get_valid_contents() dbus_connection: &Connection,
.into_iter() object_registry: &ObjectRegistry,
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask)) ) {
.map(|rx| { let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0), // Spawn async task to handle keyboard input
direction: vec3(0.0, 0.0, -1.0), tokio::spawn({
space: self.spatial.clone(), let keyboard_handlers = keyboard_handlers.clone();
}); let spatial = self.spatial.clone();
(rx, result) let keymap_id = self.keymap.data().as_ffi();
}) let dbus_connection = dbus_connection.clone();
.filter(|(_rx, result)| {
result.deepest_point_distance > 0.0 && result.min_distance < 0.05 async move {
}) let mut closest_handler = None;
.reduce(|(rx_a, result_a), (rx_b, result_b)| { let mut closest_distance = f32::MAX;
if result_a.deepest_point_distance < result_b.deepest_point_distance {
(rx_a, result_a) let mut join_set = JoinSet::new();
} else { for handler in &keyboard_handlers {
(rx_b, result_b) let handler = handler.clone();
let dbus_connection = dbus_connection.clone();
join_set.spawn(async move {
timeout(Duration::from_millis(1), async {
let field_ref = handler
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
.await
.ok()?;
let uid = field_ref.uid().await.ok()?;
Some((handler, uid))
})
.await
.ok()
.flatten()
});
} }
}) while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
.map(|(rx, _)| rx); let exported_fields = EXPORTED_FIELDS.lock();
let Some(field_ref_node) = exported_fields.get(&field_ref_id) else {
println!("didn't find a thing :(");
continue;
};
// println!("still sendin stuff :)");
let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
continue;
};
drop(exported_fields);
if let Some(rx) = rx { let result = field_ref.ray_march(Ray {
let keys = (8_u32..254) origin: vec3(0.0, 0.0, 0.0),
.map(|i| unsafe { std::mem::transmute(i) }) direction: vec3(0.0, 0.0, -1.0),
.filter_map(|k| Some((map_key(k)?, Input::key(k)))) space: spatial.clone(),
.filter_map(|(i, k)| { });
if k.is_just_active() {
Some(i as i32) if result.deepest_point_distance > 0.0
} else if k.is_just_inactive() { && result.min_distance < 0.05
Some(-(i as i32)) && result.deepest_point_distance < closest_distance
} else { {
None closest_distance = result.deepest_point_distance;
closest_handler = Some(handler);
} }
}) }
.collect();
self.keyboard_datamap.keys = keys; let Some(handler) = closest_handler else {
if !self.keyboard_datamap.keys.is_empty() { return;
pulse_receiver_client::data( };
&rx.node.upgrade().unwrap(), let Ok(keyboard_handler) = handler
&self.node.0, .to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
&Datamap::from_typed(&self.keyboard_datamap).unwrap(), .await
) else {
.unwrap(); return;
};
// Register keymap first
let _ = keyboard_handler.keymap(keymap_id).await;
// Send key states
for i in 8_u32..254 {
let key = unsafe { std::mem::transmute::<u32, stereokit_rust::system::Key>(i) };
let Some(mapped_key) = map_key(key) else {
continue;
};
let input_state = Input::key(key);
if input_state.is_just_active() {
let _ = keyboard_handler.key_state(mapped_key + 8, true).await;
} else if input_state.is_just_inactive() {
let _ = keyboard_handler.key_state(mapped_key + 8, false).await;
}
}
} }
} });
} }
} }

View File

@@ -1,13 +1,13 @@
use super::{get_sorted_handlers, CaptureManager}; use super::{CaptureManager, get_sorted_handlers};
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
fields::{Field, FieldTrait},
input::{InputDataType, InputHandler, InputMethod, Tip, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node, OwnedNode, Node, OwnedNode,
fields::{Field, FieldTrait},
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
spatial::Spatial,
}, },
objects::{ObjectHandle, SpatialRef}, objects::{ObjectHandle, SpatialRef, Tracked},
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{Mat4, Vec2, Vec3}; use glam::{Mat4, Vec2, Vec3};
@@ -40,18 +40,19 @@ pub struct SkController {
material: Material, material: Material,
capture_manager: CaptureManager, capture_manager: CaptureManager,
datamap: ControllerDatamap, datamap: ControllerDatamap,
tracked: ObjectHandle<Tracked>,
} }
impl SkController { impl SkController {
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> { pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let (spatial, object_handle) = SpatialRef::create( Input::set_controller_model(handed, Some(Model::new()));
connection, let path = "/org/stardustxr/Controller/".to_string()
&("/org/stardustxr/Controller/".to_string() + match handed {
+ match handed { Handed::Left => "left",
Handed::Left => "left", _ => "right",
_ => "right", };
}), let (spatial, object_handle) = SpatialRef::create(connection, &path);
); let tracked = Tracked::new(connection, &path);
let model = Model::copy(Model::from_memory( let model = Model::copy(&Model::from_memory(
"cursor.glb", "cursor.glb",
include_bytes!("cursor.glb"), include_bytes!("cursor.glb"),
None, None,
@@ -74,19 +75,28 @@ impl SkController {
material, material,
capture_manager: CaptureManager::default(), capture_manager: CaptureManager::default(),
datamap: Default::default(), datamap: Default::default(),
tracked,
}) })
} }
pub fn update(&mut self, token: &MainThreadToken) { pub fn update(&mut self, token: &MainThreadToken) {
let controller = Input::controller(self.handed); let controller = Input::controller(self.handed);
let input_node = self.input.spatial.node().unwrap(); let input_node = self.input.spatial.node().unwrap();
input_node.set_enabled(controller.tracked.is_active()); input_node.set_enabled(controller.tracked.is_active());
if input_node.enabled() { let enabled = input_node.enabled();
tokio::spawn({
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
if enabled {
let world_transform = Mat4::from_rotation_translation( let world_transform = Mat4::from_rotation_translation(
controller.aim.orientation.into(), controller.aim.orientation.into(),
controller.aim.position.into(), controller.aim.position.into(),
); );
self.material self.material
.color_tint(if self.capture_manager.capture.is_none() { .color_tint(if self.capture_manager.capture.upgrade().is_none() {
Color128::new_rgb(1.0, 1.0, 1.0) Color128::new_rgb(1.0, 1.0, 1.0)
} else { } else {
Color128::new_rgb(0.0, 1.0, 0.75) Color128::new_rgb(0.0, 1.0, 0.75)
@@ -118,11 +128,12 @@ impl SkController {
.set_new_capture(&self.input, distance_calculator); .set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input); self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() { if self.capture_manager.capture.upgrade().is_some() {
return; return;
} }
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator); let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
self.input.set_handler_order(sorted_handlers.iter()); self.input
.set_handler_order(sorted_handlers.iter().map(|(handler, _)| handler));
} }
} }

View File

@@ -1,25 +1,25 @@
use crate::core::client::INTERNAL_CLIENT; use crate::core::client::INTERNAL_CLIENT;
use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
use crate::nodes::OwnedNode; use crate::nodes::OwnedNode;
use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler};
use crate::nodes::{ use crate::nodes::{
Node,
input::{Hand, InputMethod, Joint}, input::{Hand, InputMethod, Joint},
spatial::Spatial, spatial::Spatial,
Node,
}; };
use crate::objects::{ObjectHandle, SpatialRef}; use crate::objects::{ObjectHandle, SpatialRef, Tracked};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::{Mat4, Quat, Vec3}; use glam::{Mat4, Quat, Vec3};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap; use stardust_xr::values::Datamap;
use std::f32::INFINITY;
use std::sync::Arc; use std::sync::Arc;
use stereokit_rust::material::Material;
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk}; use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines}; use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
use stereokit_rust::util::Color128; use stereokit_rust::util::Color128;
use zbus::Connection; use zbus::Connection;
use super::{get_sorted_handlers, CaptureManager}; use super::{CaptureManager, get_sorted_handlers};
fn convert_joint(joint: HandJoint) -> Joint { fn convert_joint(joint: HandJoint) -> Joint {
Joint { Joint {
@@ -44,6 +44,7 @@ pub struct SkHand {
input: Arc<InputMethod>, input: Arc<InputMethod>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
datamap: HandDatamap, datamap: HandDatamap,
tracked: ObjectHandle<Tracked>,
} }
impl SkHand { impl SkHand {
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> { pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
@@ -55,6 +56,14 @@ impl SkHand {
_ => "right", _ => "right",
} + "/palm"), } + "/palm"),
); );
let tracked = Tracked::new(
connection,
&("/org/stardustxr/Hand/".to_string()
+ match handed {
Handed::Left => "left",
_ => "right",
}),
);
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?; let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false); Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
let hand = InputDataType::Hand(Hand { let hand = InputDataType::Hand(Hand {
@@ -63,19 +72,20 @@ impl SkHand {
}); });
let datamap = Datamap::from_typed(HandDatamap::default())?; let datamap = Datamap::from_typed(HandDatamap::default())?;
let input = InputMethod::add_to(&_node.0, hand, datamap)?; let input = InputMethod::add_to(&_node.0, hand, datamap)?;
Input::hand_visible(handed, true);
Input::hand_visible(handed, false);
Ok(SkHand { Ok(SkHand {
_node, _node,
palm_spatial, palm_spatial,
palm_object, palm_object,
handed, handed,
input, input,
tracked,
capture_manager: CaptureManager::default(), capture_manager: CaptureManager::default(),
datamap: Default::default(), datamap: Default::default(),
}) })
} }
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) { pub fn update(&mut self, sk: &Sk, token: &MainThreadToken, material: &mut Material) {
let sk_hand = Input::hand(self.handed); let sk_hand = Input::hand(self.handed);
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32; let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() { if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
@@ -84,7 +94,15 @@ impl SkHand {
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen) (real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
&& sk_hand.tracked.is_active(), && sk_hand.tracked.is_active(),
); );
if input_node.enabled() { let enabled = input_node.enabled();
tokio::spawn({
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
if enabled {
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]); hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]); hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]); hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
@@ -122,15 +140,12 @@ impl SkHand {
hand.elbow = None; hand.elbow = None;
self.draw( let hand_color = if self.capture_manager.capture.upgrade().is_none() {
token, Color128::new_rgb(1.0, 1.0, 1.0)
if self.capture_manager.capture.is_none() { } else {
Color128::new_rgb(1.0, 1.0, 1.0) Color128::new_rgb(0.0, 1.0, 0.75)
} else { };
Color128::new_rgb(0.0, 1.0, 0.75) material.color_tint(hand_color);
},
hand,
);
} }
} }
self.datamap.pinch_strength = sk_hand.pinch_activation; self.datamap.pinch_strength = sk_hand.pinch_activation;
@@ -159,72 +174,18 @@ impl SkHand {
.set_new_capture(&self.input, distance_calculator); .set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input); self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() { if self.capture_manager.capture.upgrade().is_some() {
return; return;
} }
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator); let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
self.input.set_handler_order(sorted_handlers.iter()); self.input
.set_handler_order(sorted_handlers.iter().map(|(handler, _)| handler));
} }
}
fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) { impl Drop for SkHand {
// thumb fn drop(&mut self) {
Lines::add_list( Input::hand_visible(self.handed, false);
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),
],
);
} }
} }

View File

@@ -3,24 +3,29 @@
use crate::{ use crate::{
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
fields::{Field, Shape, EXPORTED_FIELDS},
spatial::{Spatial, EXPORTED_SPATIALS},
Node, OwnedNode, Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, Shape},
spatial::{EXPORTED_SPATIALS, Spatial},
}, },
}; };
use glam::{vec3, Mat4}; use glam::{Mat4, vec3};
use input::{ use input::{
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController, eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
sk_hand::SkHand, sk_hand::SkHand,
}; };
use play_space::PlaySpaceBounds; use play_space::PlaySpaceBounds;
use std::{marker::PhantomData, sync::Arc}; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use std::{
marker::PhantomData,
sync::{Arc, atomic::Ordering},
};
use stereokit_rust::{ use stereokit_rust::{
material::Material,
sk::{DisplayMode, MainThreadToken, Sk}, sk::{DisplayMode, MainThreadToken, Sk},
system::{Handed, Input, Key, World}, system::{Handed, Input, Key, World},
util::Device, util::Device,
}; };
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection}; use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath};
pub mod input; pub mod input;
pub mod play_space; pub mod play_space;
@@ -45,6 +50,7 @@ pub struct ServerObjects {
connection: Connection, connection: Connection,
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>), hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>, play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
hand_materials: [Material; 2],
inputs: Inputs, inputs: Inputs,
disable_controllers: bool, disable_controllers: bool,
disable_hands: bool, disable_hands: bool,
@@ -53,15 +59,13 @@ impl ServerObjects {
pub fn new( pub fn new(
connection: Connection, connection: Connection,
sk: &Sk, sk: &Sk,
hand_materials: [Material; 2],
disable_controllers: bool, disable_controllers: bool,
disable_hands: bool, disable_hands: bool,
) -> ServerObjects { ) -> ServerObjects {
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD"); let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
let play_space = (World::has_bounds() let play_space = Some(SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
&& 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() { if play_space.is_some() {
let dbus_connection = connection.clone(); let dbus_connection = connection.clone();
tokio::task::spawn(async move { tokio::task::spawn(async move {
@@ -106,13 +110,20 @@ impl ServerObjects {
connection, connection,
hmd, hmd,
play_space, play_space,
hand_materials,
inputs, inputs,
disable_controllers, disable_controllers,
disable_hands, disable_hands,
} }
} }
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) { pub fn update(
&mut self,
sk: &Sk,
token: &MainThreadToken,
dbus_connection: &Connection,
object_registry: &ObjectRegistry,
) {
let hmd_pose = Input::get_head(); let hmd_pose = Input::get_head();
self.hmd self.hmd
.0 .0
@@ -132,6 +143,7 @@ impl ServerObjects {
)); ));
} }
#[allow(clippy::collapsible_if)]
if sk.get_active_display_mode() != DisplayMode::MixedReality { if sk.get_active_display_mode() != DisplayMode::MixedReality {
if Input::key(Key::F6).is_just_inactive() { if Input::key(Key::F6).is_just_inactive() {
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap()); self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
@@ -142,12 +154,12 @@ impl ServerObjects {
// SkController::new(Handed::Right).unwrap(), // SkController::new(Handed::Right).unwrap(),
// )); // ));
// } // }
if Input::key(Key::F8).is_just_inactive() { // if Input::key(Key::F8).is_just_inactive() {
self.inputs = Inputs::Hands { // self.inputs = Inputs::Hands {
left: SkHand::new(&self.connection, Handed::Left).unwrap(), // left: SkHand::new(&self.connection, Handed::Left).unwrap(),
right: SkHand::new(&self.connection, Handed::Right).unwrap(), // right: SkHand::new(&self.connection, Handed::Right).unwrap(),
}; // };
} // }
} }
match &mut self.inputs { match &mut self.inputs {
@@ -162,28 +174,38 @@ impl ServerObjects {
controller_left.update(token); controller_left.update(token);
controller_right.update(token); controller_right.update(token);
} }
Input::hand_visible(Handed::Left, !self.disable_hands);
Input::hand_visible(Handed::Right, !self.disable_hands);
if !self.disable_hands { if !self.disable_hands {
hand_left.update(sk, token); hand_left.update(sk, token, &mut self.hand_materials[0]);
hand_right.update(sk, token); hand_right.update(sk, token, &mut self.hand_materials[1]);
} }
if let Some(eye_pointer) = eye_pointer { if let Some(eye_pointer) = eye_pointer {
eye_pointer.update(); eye_pointer.update();
} }
} }
Inputs::MousePointer(mouse_pointer) => mouse_pointer.update(), Inputs::MousePointer(mouse_pointer) => {
mouse_pointer.update(dbus_connection, object_registry)
}
// Inputs::Controllers((left, right)) => { // Inputs::Controllers((left, right)) => {
// left.update(token); // left.update(token);
// right.update(token); // right.update(token);
// } // }
Inputs::Hands { left, right } => { Inputs::Hands { left, right } => {
left.update(sk, token); left.update(sk, token, &mut self.hand_materials[0]);
right.update(sk, token); right.update(sk, token, &mut self.hand_materials[1]);
} }
} }
} }
} }
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>); pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
impl<I: Interface> Clone for ObjectHandle<I> {
fn clone(&self) -> Self {
Self(self.0.clone(), self.1.clone(), PhantomData)
}
}
impl<I: Interface> Drop for ObjectHandle<I> { impl<I: Interface> Drop for ObjectHandle<I> {
fn drop(&mut self) { fn drop(&mut self) {
let connection = self.0.clone(); let connection = self.0.clone();
@@ -231,6 +253,52 @@ impl SpatialRef {
} }
} }
pub struct Tracked(bool);
impl Tracked {
pub fn new(connection: &Connection, path: &str) -> ObjectHandle<Tracked> {
tokio::task::spawn({
let connection = connection.clone();
let path = path.to_string();
async move {
connection
.object_server()
.at(path, Self(false))
.await
.unwrap();
}
});
ObjectHandle(
connection.clone(),
OwnedObjectPath::try_from(path.to_string()).unwrap(),
PhantomData,
)
}
}
impl ObjectHandle<Tracked> {
pub async fn set_tracked(&self, is_tracked: bool) -> zbus::Result<()> {
let tracked_ref = self
.0
.object_server()
.interface::<_, Tracked>(self.1.as_ref())
.await?;
let mut tracked = tracked_ref.get_mut().await;
if tracked.0 != is_tracked {
tracked.0 = is_tracked;
tracked
.is_tracked_changed(tracked_ref.signal_emitter())
.await;
}
Ok(())
}
}
#[interface(name = "org.stardustxr.Tracked")]
impl Tracked {
#[zbus(property)]
fn is_tracked(&self) -> bool {
self.0
}
}
pub struct FieldRef(u64, OwnedNode); pub struct FieldRef(u64, OwnedNode);
impl FieldRef { impl FieldRef {
pub fn create( pub fn create(

View File

@@ -1,5 +1,5 @@
use stereokit_rust::system::World; use stereokit_rust::system::World;
use zbus::{interface, Connection, ObjectServer}; use zbus::{Connection, ObjectServer, interface};
pub struct PlaySpaceBounds; pub struct PlaySpaceBounds;
impl PlaySpaceBounds { impl PlaySpaceBounds {
@@ -15,12 +15,19 @@ impl PlaySpaceBounds {
impl PlaySpaceBounds { impl PlaySpaceBounds {
#[zbus(property)] #[zbus(property)]
fn bounds(&self) -> Vec<(f64, f64)> { fn bounds(&self) -> Vec<(f64, f64)> {
let bounds = World::get_bounds_size(); if (World::has_bounds()
vec![ && World::get_bounds_size().x != 0.0
((bounds.x).into(), (bounds.y).into()), && World::get_bounds_size().y != 0.0)
((bounds.x).into(), (-bounds.y).into()), {
((-bounds.x).into(), (-bounds.y).into()), let bounds = World::get_bounds_size();
((-bounds.x).into(), (bounds.y).into()), vec![
] ((bounds.x).into(), (bounds.y).into()),
((bounds.x).into(), (-bounds.y).into()),
((-bounds.x).into(), (-bounds.y).into()),
((-bounds.x).into(), (bounds.y).into()),
]
} else {
vec![]
}
} }
} }

View File

@@ -104,11 +104,7 @@ pub fn connection_env() -> FxHashMap<String, String> {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
{ {
var_env_insert!(env, WAYLAND_DISPLAY); var_env_insert!(env, WAYLAND_DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string()); env.insert("XDG_SESSION_TYPE".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 env
} }

View File

@@ -8,18 +8,20 @@ use crate::{
wayland::surface::CoreSurface, wayland::surface::CoreSurface,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicU32, Ordering};
use rand::Rng; use rand::Rng;
use smithay::{ use smithay::{
backend::renderer::utils::{on_commit_buffer_handler, RendererSurfaceStateUserData}, backend::renderer::utils::{RendererSurfaceStateUserData, on_commit_buffer_handler},
delegate_compositor, delegate_compositor,
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, desktop::PopupKind,
reexports::wayland_server::{Client, protocol::wl_surface::WlSurface},
wayland::compositor::{ wayland::compositor::{
self, add_post_commit_hook, CompositorClientState, CompositorHandler, CompositorState, CompositorClientState, CompositorHandler, CompositorState, add_post_commit_hook,
}, },
}; };
use std::sync::Arc; use std::sync::Arc;
use tracing::debug; use tracing::{debug, warn};
pub struct ConfiguredSurface;
impl CompositorHandler for WaylandState { impl CompositorHandler for WaylandState {
fn compositor_state(&mut self) -> &mut CompositorState { fn compositor_state(&mut self) -> &mut CompositorState {
@@ -30,19 +32,27 @@ impl CompositorHandler for WaylandState {
debug!(?surface, "Surface commit"); debug!(?surface, "Surface commit");
on_commit_buffer_handler::<WaylandState>(surface); on_commit_buffer_handler::<WaylandState>(surface);
let mut count = 0;
compositor::with_states(surface, |data| {
let count_new = data
.data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
if !count_new {
if let Some(stored_count) = data.data_map.get::<AtomicU32>() {
count = stored_count.fetch_add(1, Ordering::Relaxed);
}
}
data.data_map.get::<Arc<CoreSurface>>().cloned() if let Some(toplevel) = self
}); .xdg_shell
.toplevel_surfaces()
.iter()
.find(|s| s.wl_surface() == surface)
{
if !toplevel.is_initial_configure_sent() {
debug!("Sending initial configure for toplevel surface");
toplevel.send_configure();
surface.insert_data(ConfiguredSurface);
}
}
self.popup_manager.commit(surface);
if let Some(PopupKind::Xdg(popup)) = self.popup_manager.find_popup(surface) {
if surface.insert_data(ConfiguredSurface) {
debug!("Configuring popup surface");
let _ = popup.send_configure();
}
}
} }
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
@@ -54,6 +64,7 @@ impl CompositorHandler for WaylandState {
surface.insert_data(SurfaceId::Child(id)); surface.insert_data(SurfaceId::Child(id));
CoreSurface::add_to(surface); CoreSurface::add_to(surface);
let Some(parent_surface_id) = parent.get_data::<SurfaceId>() else { let Some(parent_surface_id) = parent.get_data::<SurfaceId>() else {
warn!("Parent surface has no SurfaceId");
return; return;
}; };
surface.insert_data(Mutex::new(ChildInfo { surface.insert_data(Mutex::new(ChildInfo {
@@ -68,6 +79,7 @@ impl CompositorHandler for WaylandState {
})); }));
let Some(panel_item) = surface_panel_item(parent) else { let Some(panel_item) = surface_panel_item(parent) else {
warn!("Parent has no panel item");
return; return;
}; };
let panel_item_weak = Arc::downgrade(&panel_item); let panel_item_weak = Arc::downgrade(&panel_item);
@@ -75,11 +87,19 @@ impl CompositorHandler for WaylandState {
if surface_panel_item(surf).is_some() { if surface_panel_item(surf).is_some() {
return; return;
} }
debug!("Linking surface to panel item");
surf.insert_data(panel_item_weak.clone()); surf.insert_data(panel_item_weak.clone());
let Some(panel_item) = surface_panel_item(surf) else { let Some(panel_item) = surface_panel_item(surf) else {
warn!("Failed to link surface to panel item");
return; return;
}; };
surf.with_child_info(|_info| {
panel_item.backend.reposition_child(surf);
});
debug!("Adding new child to panel item");
panel_item.backend.new_child(surf); panel_item.backend.new_child(surf);
}); });
@@ -88,6 +108,7 @@ impl CompositorHandler for WaylandState {
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|s| s.lock().ok()?.view()) .get_data_raw::<RendererSurfaceStateUserData, _, _>(|s| s.lock().ok()?.view())
.flatten() .flatten()
else { else {
debug!("No view data for surface");
return; return;
}; };
let mut changed = false; let mut changed = false;
@@ -96,11 +117,13 @@ impl CompositorHandler for WaylandState {
&& info.geometry.origin.y != view.offset.y && info.geometry.origin.y != view.offset.y
{ {
changed = true; changed = true;
debug!("Surface position changed");
} }
if info.geometry.size.x != view.dst.w as u32 if info.geometry.size.x != view.dst.w as u32
&& info.geometry.size.y != view.dst.h as u32 && info.geometry.size.y != view.dst.h as u32
{ {
changed = true; changed = true;
debug!("Surface size changed");
} }
info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into(); info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into();
}); });
@@ -109,6 +132,7 @@ impl CompositorHandler for WaylandState {
return; return;
}; };
if changed { if changed {
debug!("Repositioning child due to geometry change");
panel_item.backend.reposition_child(surf); panel_item.backend.reposition_child(surf);
} }
}); });
@@ -119,6 +143,7 @@ impl CompositorHandler for WaylandState {
return; return;
}; };
if surface.get_child_info().is_some() { if surface.get_child_info().is_some() {
debug!("Dropping destroyed child surface");
panel_item.backend.drop_child(surface); panel_item.backend.drop_child(surface);
} }
} }

View File

@@ -1,4 +1,5 @@
use smithay::reexports::wayland_server::{ use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
protocol::{ protocol::{
wl_data_device::{ wl_data_device::{
Request::{Release, SetSelection, StartDrag}, Request::{Release, SetSelection, StartDrag},
@@ -13,7 +14,6 @@ use smithay::reexports::wayland_server::{
WlDataSource, WlDataSource,
}, },
}, },
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
}; };
use super::state::WaylandState; use super::state::WaylandState;

View File

@@ -13,8 +13,8 @@ use smithay::{
Mode as KdeMode, OrgKdeKwinServerDecoration, Mode as KdeMode, OrgKdeKwinServerDecoration,
}, },
wayland_server::{ wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak,
GlobalDispatch, New, Resource, WEnum, Weak, protocol::wl_surface::WlSurface,
}, },
}, },
wayland::shell::{self, kde::decoration::KdeDecorationHandler}, wayland::shell::{self, kde::decoration::KdeDecorationHandler},

View File

@@ -23,8 +23,8 @@ mod generated {
use super::state::WaylandState; use super::state::WaylandState;
use smithay::{ use smithay::{
backend::allocator::{ backend::allocator::{
dmabuf::{Dmabuf, DmabufFlags},
Fourcc, Modifier, Fourcc, Modifier,
dmabuf::{Dmabuf, DmabufFlags},
}, },
reexports::wayland_server::{ reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,

View File

@@ -11,35 +11,35 @@ mod xdg_shell;
use self::{state::WaylandState, surface::CORE_SURFACES}; use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::{core::task, wayland::state::ClientState}; use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{ensure, Result}; use color_eyre::eyre::{Result, ensure};
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::{
use smithay::backend::egl::EGLContext; backend::{
use smithay::backend::renderer::gles::GlesRenderer; allocator::dmabuf::Dmabuf,
use smithay::backend::renderer::{ImportDma, Renderer}; egl::EGLContext,
use smithay::output::Output; renderer::{ImportDma, Renderer, gles::GlesRenderer},
use smithay::reexports::wayland_server::backend::ClientId; },
use smithay::reexports::wayland_server::DisplayHandle; output::Output,
use smithay::reexports::wayland_server::{Display, ListeningSocket}; reexports::wayland_server::{Display, DisplayHandle, ListeningSocket},
use smithay::wayland::dmabuf; wayland::dmabuf,
use std::ffi::OsStr; };
use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::unix::prelude::AsRawFd;
use std::{ use std::{
ffi::c_void, ffi::{OsStr, c_void},
os::unix::{net::UnixListener, prelude::FromRawFd}, os::fd::AsFd,
sync::Arc, sync::{Arc, OnceLock},
}; };
use stereokit_rust::system::{Backend, BackendGraphics}; use stereokit_rust::system::{Backend, BackendGraphics};
use tokio::io::unix::AsyncFdReadyGuard;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{ use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle, io::unix::AsyncFd,
sync::{
Notify,
mpsc::{self, UnboundedReceiver},
},
task::AbortHandle,
}; };
use tracing::{debug_span, info, instrument}; use tracing::{debug_span, info, instrument};
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new(); pub static WAYLAND_DISPLAY: OnceLock<String> = OnceLock::new();
struct EGLRawHandles { struct EGLRawHandles {
display: *const c_void, display: *const c_void,
@@ -61,40 +61,10 @@ fn get_sk_egl() -> Result<EGLRawHandles> {
}) })
} }
pub struct DisplayWrapper(Mutex<Display<WaylandState>>, DisplayHandle);
impl DisplayWrapper {
pub fn handle(&self) -> DisplayHandle {
self.1.clone()
}
pub fn dispatch_clients(&self, state: &mut WaylandState) -> Result<usize, std::io::Error> {
self.0.lock().dispatch_clients(state)
}
pub fn flush_clients(&self, client: Option<ClientId>) {
if let Some(mut lock) = self.0.try_lock() {
let _ = lock.backend().flush(client);
}
}
pub fn poll_fd(&self) -> Result<OwnedFd, std::io::Error> {
self.0.lock().backend().poll_fd().try_clone_to_owned()
}
}
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>, flush_notify: Arc<Notify>,
pub socket_name: Option<String>, client_listener: AbortHandle,
join_handle: JoinHandle<Result<()>>, client_dispatcher: AbortHandle,
renderer: GlesRenderer, renderer: GlesRenderer,
output: Output, output: Output,
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>, dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
@@ -114,7 +84,6 @@ impl Wayland {
let display_handle = display.handle(); let display_handle = display.handle();
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel(); let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone()));
let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx); let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx);
let output = wayland_state.lock().output.clone(); let output = wayland_state.lock().output.clone();
@@ -129,58 +98,78 @@ impl Wayland {
} }
info!(socket_name, "Wayland active"); info!(socket_name, "Wayland active");
let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state)?; let flush_notify = Arc::new(Notify::new());
let client_listener = task::new(
|| "Wayland client listener loop",
Wayland::client_listener_loop(display_handle, socket, wayland_state.clone()),
)?
.abort_handle();
let client_dispatcher = task::new(
|| "Wayland dispatch client loop",
Wayland::dispatch_client_loop(display, flush_notify.clone(), wayland_state),
)?
.abort_handle();
Ok(Wayland { Ok(Wayland {
display, flush_notify,
socket_name, client_listener,
join_handle, client_dispatcher,
renderer, renderer,
output, output,
dmabuf_rx, dmabuf_rx,
}) })
} }
fn start_loop( async fn client_listener_loop(
display: Arc<DisplayWrapper>, mut display_handle: DisplayHandle,
socket: ListeningSocket, socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>, state: Arc<Mutex<WaylandState>>,
) -> Result<JoinHandle<Result<()>>> { ) -> Result<()> {
let listen_async = let async_fd = AsyncFd::new(socket.as_fd())?;
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?; loop {
let mut guard = async_fd.readable().await?;
let Ok(Some(stream)) = socket.accept() else {
guard.clear_ready();
continue;
};
let dispatch_poll_fd = display.poll_fd()?; let stream = tokio::net::UnixStream::from_std(stream)?;
let dispatch_poll_listener = UnownedFd(Some(AsyncFd::new(dispatch_poll_fd)?)); let pid = stream.peer_cred().ok().and_then(|c| c.pid());
let dh1 = display.handle(); // New client connected
let mut dh2 = dh1.clone(); let client_state = Arc::new(ClientState {
pid,
id: OnceLock::new(),
compositor_state: Default::default(),
seat: state.lock().seat.clone(),
});
let _client = display_handle.insert_client(stream.into_std()?, client_state.clone())?;
}
}
task::new(|| "wayland loop", async move { async fn dispatch_client_loop(
let _socket = socket; // Keep the socket alive mut display: Display<WaylandState>,
loop { flush_notify: Arc<Notify>,
tokio::select! { state: Arc<Mutex<WaylandState>>,
acc = listen_async.accept() => { // New client connected ) -> std::io::Result<()> {
let (stream, _) = acc?; loop {
let client_state = Arc::new(ClientState { let poll_fd = display.backend().poll_fd();
pid: stream.peer_cred().ok().and_then(|c| c.pid()), let async_fd = AsyncFd::new(poll_fd)?;
id: OnceCell::new(), tokio::select! {
compositor_state: Default::default(), biased;
seat: state.lock().seat.clone(), _ = async_fd.readable() => {
}); drop(async_fd);
let _client = dh2.insert_client(stream.into_std()?, client_state.clone())?; let _span = debug_span!("Dispatch wayland event");
} let _span = _span.enter();
e = dispatch_poll_listener.readable() => { // Dispatch let _ = display.dispatch_clients(&mut *state.lock());
let mut guard = e?; let _ = display.flush_clients();
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
display.dispatch_clients(&mut state.lock())?;
display.flush_clients(None);
Ok(())
})?;
guard.clear_ready();
}
} }
_ = flush_notify.notified() => {
drop(async_fd);
let _ = display.flush_clients();
},
} }
}) }
} }
#[instrument(level = "debug", name = "Wayland frame", skip(self))] #[instrument(level = "debug", name = "Wayland frame", skip(self))]
@@ -197,7 +186,7 @@ impl Wayland {
} }
let _ = self.renderer.cleanup_texture_cache(); let _ = self.renderer.cleanup_texture_cache();
self.display.flush_clients(None); self.flush_notify.notify_waiters();
} }
pub fn frame_event(&self) { pub fn frame_event(&self) {
@@ -214,6 +203,7 @@ impl Wayland {
} }
impl Drop for Wayland { impl Drop for Wayland {
fn drop(&mut self) { fn drop(&mut self) {
self.join_handle.abort(); self.client_listener.abort();
self.client_dispatcher.abort();
} }
} }

View File

@@ -1,10 +1,7 @@
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt}; use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
use crate::{ use crate::{
core::task, core::task,
nodes::{ nodes::items::panel::{Backend, Geometry, KEYMAPS, PanelItem},
data::KEYMAPS,
items::panel::{Backend, Geometry, PanelItem},
},
}; };
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -14,12 +11,12 @@ use smithay::{
backend::input::{AxisRelativeDirection, ButtonState, KeyState}, backend::input::{AxisRelativeDirection, ButtonState, KeyState},
delegate_seat, delegate_seat,
input::{ input::{
keyboard::{FilterResult, LedState},
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent},
touch::{self, DownEvent, UpEvent},
Seat, SeatHandler, Seat, SeatHandler,
keyboard::{FilterResult, LedState},
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, CursorImageSurfaceData, MotionEvent},
touch::{self, DownEvent, UpEvent},
}, },
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak as WlWeak}, reexports::wayland_server::{Resource, Weak as WlWeak, protocol::wl_surface::WlSurface},
utils::SERIAL_COUNTER, utils::SERIAL_COUNTER,
wayland::compositor, wayland::compositor,
}; };
@@ -41,6 +38,11 @@ impl SeatHandler for WaylandState {
CursorImageStatus::Surface(surface) => { CursorImageStatus::Surface(surface) => {
CoreSurface::add_to(&surface); CoreSurface::add_to(&surface);
compositor::with_states(&surface, |data| { compositor::with_states(&surface, |data| {
if let Some(cursor_attributes) = data.data_map.get::<CursorImageSurfaceData>() {
let hotspot = cursor_attributes.lock().unwrap().hotspot;
c.hotspot_x = hotspot.x;
c.hotspot_y = hotspot.y;
}
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() { if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1); core_surface.set_material_offset(1);
} }
@@ -106,6 +108,12 @@ impl SeatWrapper {
touches: Mutex::new(FxHashMap::default()), touches: Mutex::new(FxHashMap::default()),
} }
} }
pub fn unfocus_internal_state(&self, surface: &WlSurface) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
self.unfocus(surface, &mut state.lock());
}
pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) { pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) {
let pointer = self.seat.get_pointer().unwrap(); let pointer = self.seat.get_pointer().unwrap();
if pointer.current_focus() == Some(surface.clone()) { if pointer.current_focus() == Some(surface.clone()) {
@@ -203,7 +211,7 @@ impl SeatWrapper {
pointer.frame(&mut state); pointer.frame(&mut state);
} }
pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec<i32>) { pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) {
let Some(state) = self.wayland_state.upgrade() else { let Some(state) = self.wayland_state.upgrade() else {
return; return;
}; };
@@ -226,20 +234,18 @@ impl SeatWrapper {
{ {
return; return;
} }
for key in keys { keyboard.input(
keyboard.input( &mut state.lock(),
&mut state.lock(), key.into(),
key.unsigned_abs(), if pressed {
if key > 0 { KeyState::Pressed
KeyState::Pressed } else {
} else { KeyState::Released
KeyState::Released },
}, SERIAL_COUNTER.next_serial(),
SERIAL_COUNTER.next_serial(), 0,
0, |_, _, _| FilterResult::Forward::<()>,
|_, _, _| FilterResult::Forward::<()>, );
);
}
} }
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) { pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {

View File

@@ -1,15 +1,15 @@
use super::seat::SeatWrapper; use super::seat::SeatWrapper;
use crate::wayland::drm::wl_drm::WlDrm; use crate::wayland::drm::wl_drm::WlDrm;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use smithay::{ use smithay::{
backend::{ backend::{
allocator::{dmabuf::Dmabuf, Fourcc}, allocator::{Fourcc, dmabuf::Dmabuf},
egl::EGLDevice, egl::EGLDevice,
renderer::gles::GlesRenderer, renderer::gles::GlesRenderer,
}, },
delegate_dmabuf, delegate_output, delegate_shm, delegate_dmabuf, delegate_output, delegate_shm, delegate_viewporter,
input::{keyboard::XkbConfig, SeatState}, desktop::PopupManager,
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output, Scale, Subpixel}, output::{Mode, Output, Scale, Subpixel},
reexports::{ reexports::{
wayland_protocols::xdg::{ wayland_protocols::xdg::{
@@ -18,12 +18,12 @@ use smithay::{
}, },
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::{
DisplayHandle,
backend::{ClientData, ClientId, DisconnectReason}, backend::{ClientData, ClientId, DisconnectReason},
protocol::{ protocol::{
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager, wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
wl_output::WlOutput, wl_output::WlOutput,
}, },
DisplayHandle,
}, },
}, },
utils::{Size, Transform}, utils::{Size, Transform},
@@ -39,15 +39,16 @@ use smithay::{
xdg::{WmCapabilitySet, XdgShellState}, xdg::{WmCapabilitySet, XdgShellState},
}, },
shm::{ShmHandler, ShmState}, shm::{ShmHandler, ShmState},
viewporter::ViewporterState,
}, },
}; };
use std::sync::Arc; use std::sync::{Arc, OnceLock};
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 pid: Option<i32>,
pub id: OnceCell<ClientId>, pub id: OnceLock<ClientId>,
pub compositor_state: CompositorClientState, pub compositor_state: CompositorClientState,
pub seat: Arc<SeatWrapper>, pub seat: Arc<SeatWrapper>,
} }
@@ -71,11 +72,13 @@ pub struct WaylandState {
pub kde_decoration_state: KdeDecorationState, pub kde_decoration_state: KdeDecorationState,
pub shm_state: ShmState, pub shm_state: ShmState,
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>), dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub _viewporter_state: ViewporterState,
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_state: SeatState<Self>,
pub seat: Arc<SeatWrapper>, pub seat: Arc<SeatWrapper>,
pub xdg_shell: XdgShellState, pub xdg_shell: XdgShellState,
pub popup_manager: PopupManager,
pub output: Output, pub output: Output,
} }
@@ -158,6 +161,8 @@ impl WaylandState {
output.set_preferred(mode); output.set_preferred(mode);
let mut xdg_shell = XdgShellState::new::<Self>(&display_handle); let mut xdg_shell = XdgShellState::new::<Self>(&display_handle);
let _viewporter_state = ViewporterState::new::<Self>(&display_handle);
let popup_manager = PopupManager::default();
let mut capabilities = WmCapabilitySet::default(); let mut capabilities = WmCapabilitySet::default();
capabilities.set(WmCapabilities::Maximize); capabilities.set(WmCapabilities::Maximize);
capabilities.set(WmCapabilities::Fullscreen); capabilities.set(WmCapabilities::Fullscreen);
@@ -182,7 +187,9 @@ impl WaylandState {
seat_state, seat_state,
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)), seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
xdg_shell, xdg_shell,
popup_manager,
output, output,
_viewporter_state,
}) })
}) })
} }
@@ -220,3 +227,4 @@ impl OutputHandler for WaylandState {
delegate_dmabuf!(WaylandState); delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState); delegate_shm!(WaylandState);
delegate_output!(WaylandState); delegate_output!(WaylandState);
delegate_viewporter!(WaylandState);

View File

@@ -9,20 +9,23 @@ use crate::{
items::camera::TexWrapper, items::camera::TexWrapper,
}, },
}; };
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},
utils::{import_surface_tree, RendererSurfaceStateUserData},
Renderer, Texture, Renderer, Texture,
gles::{GlesRenderer, GlesTexture},
utils::{RendererSurfaceStateUserData, import_surface_tree},
}, },
desktop::utils::send_frames_surface_tree, desktop::utils::send_frames_surface_tree,
output::Output, output::Output,
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, Resource}, reexports::wayland_server::{self, Resource, protocol::wl_surface::WlSurface},
};
use std::{
ffi::c_void,
sync::{Arc, OnceLock},
time::Duration,
}; };
use std::{ffi::c_void, sync::Arc, time::Duration};
use stereokit_rust::{ use stereokit_rust::{
material::{Material, Transparency}, material::{Material, Transparency},
shader::Shader, shader::Shader,
@@ -44,8 +47,8 @@ impl Drop for CoreSurfaceData {
pub struct CoreSurface { pub struct CoreSurface {
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<Mutex<TexWrapper>>, sk_tex: OnceLock<Mutex<TexWrapper>>,
sk_mat: OnceCell<Mutex<MaterialWrapper>>, sk_mat: OnceLock<Mutex<MaterialWrapper>>,
material_offset: Mutex<Delta<u32>>, material_offset: Mutex<Delta<u32>>,
pub pending_material_applications: Registry<ModelPart>, pub pending_material_applications: Registry<ModelPart>,
} }
@@ -55,8 +58,8 @@ impl CoreSurface {
let core_surface = CORE_SURFACES.add(CoreSurface { let core_surface = CORE_SURFACES.add(CoreSurface {
weak_surface: surface.downgrade(), weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None), mapped_data: Mutex::new(None),
sk_tex: OnceCell::new(), sk_tex: OnceLock::new(),
sk_mat: OnceCell::new(), sk_mat: OnceLock::new(),
material_offset: Mutex::new(Delta::new(0)), material_offset: Mutex::new(Delta::new(0)),
pending_material_applications: Registry::new(), pending_material_applications: Registry::new(),
}); });
@@ -92,7 +95,8 @@ impl CoreSurface {
}); });
// Import all surface buffers into textures // Import all surface buffers into textures
if import_surface_tree(renderer, &wl_surface).is_err() { if let Err(err) = import_surface_tree(renderer, &wl_surface) {
tracing::error!("Failed to import surface tree for surface {}: {}", wl_surface.id(), err);
return; return;
} }
@@ -104,25 +108,11 @@ impl CoreSurface {
let Some(wl_surface) = self.wl_surface() else { let Some(wl_surface) = self.wl_surface() else {
return; return;
}; };
let mapped = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states.lock().unwrap().buffer().is_some()
})
.unwrap_or(false);
if !mapped {
return;
}
let mut mapped_data = self.mapped_data.lock();
let Some(smithay_tex) = wl_surface let Some(smithay_tex) = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| { .get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states let locked = surface_states.lock().unwrap();
.lock() locked.texture::<GlesRenderer>(renderer.id()).cloned()
.unwrap()
.texture::<GlesRenderer>(renderer.id())
.cloned()
}) })
.flatten() .flatten()
else { else {
@@ -130,9 +120,11 @@ impl CoreSurface {
}; };
let Some(sk_tex) = self.sk_tex.get() else { let Some(sk_tex) = self.sk_tex.get() else {
tracing::error!("No sk_tex found for surface");
return; return;
}; };
let Some(sk_mat) = self.sk_mat.get() else { let Some(sk_mat) = self.sk_mat.get() else {
tracing::error!("No sk_mat found for surface");
return; return;
}; };
sk_tex sk_tex
@@ -157,7 +149,7 @@ impl CoreSurface {
let new_mapped_data = CoreSurfaceData { let new_mapped_data = CoreSurfaceData {
wl_tex: Some(SendWrapper::new(smithay_tex)), wl_tex: Some(SendWrapper::new(smithay_tex)),
}; };
*mapped_data = Some(new_mapped_data); *self.mapped_data.lock() = Some(new_mapped_data);
} }
pub fn frame(&self, output: Output) { pub fn frame(&self, output: Output) {

View File

@@ -1,30 +1,34 @@
use super::{ use super::{
seat::{handle_cursor, SeatWrapper}, seat::{SeatWrapper, handle_cursor},
state::{ClientState, WaylandState}, state::{ClientState, WaylandState},
surface::CoreSurface, surface::CoreSurface,
utils::*, utils::*,
}; };
use crate::nodes::{ use crate::{
drawable::model::ModelPart, core::error::Result,
items::panel::{ nodes::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo, drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
},
}, },
}; };
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::eyre;
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::Rng; use rand::Rng;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use smithay::{ use smithay::{
delegate_xdg_shell, delegate_xdg_shell,
desktop::PopupKind,
reexports::{ reexports::{
wayland_protocols::xdg::{ wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::{ResizeEdge, State}, shell::server::xdg_toplevel::{ResizeEdge, State},
}, },
wayland_server::{ wayland_server::{
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
Resource, Resource,
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
}, },
}, },
utils::{Logical, Rectangle, Serial}, utils::{Logical, Rectangle, Serial},
@@ -87,7 +91,6 @@ impl XdgShellHandler for WaylandState {
s.states.set(State::Maximized); s.states.set(State::Maximized);
s.states.unset(State::Fullscreen); s.states.unset(State::Fullscreen);
}); });
toplevel.send_configure();
let initial_size = toplevel let initial_size = toplevel
.wl_surface() .wl_surface()
@@ -222,14 +225,17 @@ impl XdgShellHandler for WaylandState {
}; };
panel_item.toplevel_title_changed(&title) panel_item.toplevel_title_changed(&title)
} }
fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) { fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) {
self.popup_manager
.track_popup(PopupKind::Xdg(popup.clone()))
.unwrap();
let id = rand::thread_rng().gen_range(0..u64::MAX); let id = rand::thread_rng().gen_range(0..u64::MAX);
popup.wl_surface().insert_data(SurfaceId::Child(id)); popup.wl_surface().insert_data(SurfaceId::Child(id));
let Some(parent) = popup.get_parent_surface() else { let Some(parent) = popup.get_parent_surface() else {
warn!("No parent surface found for popup");
return; return;
}; };
let _ = popup.send_configure();
CoreSurface::add_to(popup.wl_surface()); CoreSurface::add_to(popup.wl_surface());
popup.wl_surface().insert_data(Mutex::new(ChildInfo { popup.wl_surface().insert_data(Mutex::new(ChildInfo {
@@ -241,6 +247,7 @@ impl XdgShellHandler for WaylandState {
})); }));
let Some(panel_item) = surface_panel_item(&parent) else { let Some(panel_item) = surface_panel_item(&parent) else {
warn!("No panel item found for popup parent");
return; return;
}; };
let panel_item_weak = Arc::downgrade(&panel_item); let panel_item_weak = Arc::downgrade(&panel_item);
@@ -252,6 +259,7 @@ impl XdgShellHandler for WaylandState {
} }
surf.insert_data(panel_item_weak.clone()); surf.insert_data(panel_item_weak.clone());
let Some(panel) = surface_panel_item(surf) else { let Some(panel) = surface_panel_item(surf) else {
warn!("Failed to get panel item for popup surface");
return; return;
}; };
panel.backend.new_child(surf); panel.backend.new_child(surf);
@@ -464,6 +472,7 @@ impl Backend for XdgBackend {
fn close_toplevel(&self) { fn close_toplevel(&self) {
if let Some(toplevel) = self.toplevel.lock().clone() { if let Some(toplevel) = self.toplevel.lock().clone() {
self.seat.unfocus_internal_state(toplevel.wl_surface());
toplevel.send_close(); toplevel.send_close();
} }
} }
@@ -515,11 +524,11 @@ impl Backend for XdgBackend {
self.seat.pointer_scroll(scroll_distance, scroll_steps) self.seat.pointer_scroll(scroll_distance, scroll_steps)
} }
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>) { fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else { let Some(surface) = self.wl_surface_from_id(surface) else {
return; return;
}; };
self.seat.keyboard_keys(surface, keymap_id, keys) self.seat.keyboard_key(surface, keymap_id, key, pressed)
} }
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) { fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {