84 Commits

Author SHA1 Message Date
nervyalloy5
08c7a5efea fix(nix): wrap executable to include correct XKB_CONFIG_ROOT variable 2025-05-22 17:02:15 -04:00
nervyalloy5
b5dcffd7c0 fix(nix): fix nix build
- Update flake.lock
- Switch to correct version of sk_gpu.zip in /nix/sk_gpu.nix
- Add xkeyboard_config and libgbm to dependencies in
  /nix/stardust-xr-server.nix
- Add openxr-loader to github fetches and add it to CPM local packages
2025-05-22 15:21:14 -04: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
63 changed files with 2216 additions and 1986 deletions

View File

@@ -3,7 +3,7 @@ name: Build
on:
push:
branches:
- '*'
- "*"
jobs:
build_and_package:
@@ -11,13 +11,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- 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
- 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
uses: actions-rs/toolchain@v1
@@ -26,21 +26,3 @@ jobs:
- name: Build server
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'

371
Cargo.lock generated
View File

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

View File

@@ -1,6 +1,6 @@
[package]
edition = "2021"
rust-version = "1.75"
edition = "2024"
rust-version = "1.85"
name = "stardust-xr-server"
version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"]
@@ -12,6 +12,10 @@ homepage = "https://stardustxr.org"
[workspace]
members = ["codegen"]
[workspace.dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
branch = "dev"
[[bin]]
name = "stardust-xr-server"
path = "src/main.rs"
@@ -37,25 +41,24 @@ auto_link_exclude_list = [
[profile.dev.package."*"]
opt-level = 3
debug = true
strip = "none"
strip = false
debug-assertions = true
overflow-checks = true
[profile.release]
opt-level = 3
debug = "line-tables-only"
strip = "none"
strip = true
debug-assertions = true
overflow-checks = false
lto = "thin"
[dependencies]
# small utility thingys
once_cell = "1.19.0"
nanoid = "0.4.0"
lazy_static = "1.5.0"
rand = "0.8.5"
rustc-hash = "2.0.0"
portable-atomic = { version = "1.7.0", features = ["float", "std"] }
send_wrapper = "0.6.0"
slotmap = "1.0.7"
global_counter = "=0.2.2"
@@ -65,6 +68,7 @@ parking_lot = "0.12.3"
color-eyre = { version = "0.6.3", default-features = false }
clap = { version = "4.5.13", features = ["derive"] }
console-subscriber = { version = "0.4.0", optional = true }
thiserror = "2.0.9"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true }
@@ -78,40 +82,33 @@ toml = "0.8.19"
glam = { version = "0.29.0", features = ["mint", "serde"] }
mint = "0.5.9"
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
prisma = "0.1.1"
# linux stuffs
libc = "0.2.155"
nix = "0.29.0"
input-event-codes = "6.2.0"
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] }
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
directories = "5.0.1"
xkbcommon-rs = "0.1.0"
# wayland
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
wayland-scanner = { version = "0.31.4", optional = true }
dashmap = "6.1.0"
[dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git"
# git = "https://github.com/colinmarc/smithay.git"
git = "https://github.com/smithay/smithay.git"
# path = "../smithay"
default-features = false
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
optional = true
[dependencies.stereokit-rust]
# path = "../StereoKit-rust"
git = "https://github.com/mvvvv/StereoKit-rust.git"
# git = "https://github.com/technobaboo/StereoKit-rust.git"
rev = "73ffaae6f42aa369e599a6ea0391f77840d682d8"
features = ["no-event-loop"]
default-features = false
[dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
branch = "dev"
workspace = true
[dependencies.stardust-xr-server-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
1. Cargo
2. CMake
3. EGL+GLES 3.2
4. GLX+Xlib
5. fontconfig
6. dlopen
7. OpenXR Loader (required even if run in flatscreen mode)
![workflow](/img/workflow.png)
Command line installation of core & dynamic dependencies are provided below:
<details>
<summary>Ubuntu/Debian</summary>
<pre><code class="language-bash">
sudo apt update && sudo apt install \
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
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
> [!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):
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_3.png)
### Startup Script
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.
![A black void representing Stardust in XR mode with a hand skeleton in the middle](/img/flatscreen_2.png)
To move around, hold down `Shift + W A S D`, with `Q` for moving down and `E` for moving up.
![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
#### Flatscreen (-f)
To drag applications out of the app launcher, hold down `Shift + ~`
![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.
Flatscreen mode upon initial startup:
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_1.png)
### XR Navigation
A video guide showcasing XR controls is available [here](https://www.youtube.com/watch?v=RbxFq6JjliA)
#### 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.
#### 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`
![controller_click](https://github.com/StardustXR/website/blob/main/static/img/controller_click.GIF)

View File

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

View File

@@ -2,7 +2,7 @@ use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr_schemas::protocol::*;
use stardust_xr::schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
quote!(#a #b)
@@ -25,10 +25,6 @@ pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::To
codegen_protocol(FIELD_PROTOCOL)
}
#[proc_macro]
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DATA_PROTOCOL)
}
#[proc_macro]
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(AUDIO_PROTOCOL)
}
@@ -64,11 +60,28 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
};
let aspect = generate_aspect(&Aspect {
name: "interface".to_string(),
id: 0,
description: protocol.description.clone(),
inherits: vec![],
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();
let custom_enums = protocol
@@ -130,7 +143,7 @@ fn generate_custom_union(custom_union: &CustomUnion) -> TokenStream {
quote! {
#[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
#[serde(tag = "t", content = "c")]
pub enum #name {#option_decls}
}
}
@@ -177,11 +190,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
Span::call_site(),
);
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)
.map(|t| {
// TODO: properly import all dependencies
quote! {
#[allow(clippy::all)]
pub mod #client_mod_name {
use super::*;
#t
@@ -190,11 +204,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
})
.unwrap_or_default();
let aspect_trait_name = Ident::new(
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let opcodes = aspect
.members
.iter()
@@ -219,31 +228,95 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
let alias_info = generate_alias_info(aspect);
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)
.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
.iter()
.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)
.map(|members| {
quote! {
fn add_node_members(node: &crate::nodes::Node) {
#members
}
}
})
.unwrap_or_default();
let server_side_members = quote! {
#[allow(clippy::all)]
#[doc = #description]
pub trait #aspect_trait_name {
#add_node_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 {
@@ -283,6 +356,7 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
quote! {
lazy_static::lazy_static! {
#[allow(clippy::all)]
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
server_signals: vec![#local_signals],
server_methods: vec![#local_methods],
@@ -293,8 +367,8 @@ fn generate_alias_info(aspect: &Aspect) -> TokenStream {
}
}
fn generate_member(member: &Member) -> TokenStream {
let id = member.opcode;
fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenStream {
let opcode = member.opcode;
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
let description = &member.description;
@@ -324,40 +398,53 @@ fn generate_member(member: &Member) -> TokenStream {
.as_ref()
.map(|r| generate_argument_type(r, false, true))
.unwrap_or_else(|| quote!(()));
let name_str = name.to_string();
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) => {
quote! {
#[doc = #description]
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
_node.send_remote_signal(#id, serialized)
pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
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! {
#[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) => {
quote! {
#[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 member_name_ident = Ident::new(&member.name, Span::call_site());
@@ -379,7 +466,10 @@ fn generate_handler(member: &Member) -> TokenStream {
.clone()
.zip(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();
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))
.reduce(|a, b| quote!(#a, #b))
.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! {
node.add_local_signal(#opcode, |_node, _calling_client, _message| {
#opcode => { let result = (move || {
#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! {
node.add_local_method(#opcode, |_node, _calling_client, _message, _method_response| {
_method_response.wrap_async(async move {
#opcode => _method_response.wrap_async(async move {
#deserialize
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
Ok((#serialize, Vec::new()))
});
});
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
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 {
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 {
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
ArgumentType::Vec(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
}
ArgumentType::Map(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
}
_ => 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::Vec(v) => {
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) => {
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),
}
@@ -511,6 +614,7 @@ fn argument_type_option_name(argument_type: &ArgumentType) -> String {
ArgumentType::Union(u) => u.clone(),
ArgumentType::Struct(s) => s.clone(),
ArgumentType::Node { _type, .. } => _type.clone(),
ArgumentType::Fd => "File Descriptor".to_string(),
}
}
fn generate_argument_type(
@@ -607,6 +711,9 @@ fn generate_argument_type(
quote!(std::sync::Arc<crate::nodes::Node>)
}
}
ArgumentType::Fd => {
quote!(&std::os::fd::OwnedFd)
}
};
if optional {

51
flake.lock generated
View File

@@ -26,11 +26,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@@ -65,11 +65,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1713050562,
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
"lastModified": 1725868201,
"narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=",
"owner": "StardustXR",
"repo": "flatland",
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
"rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90",
"type": "github"
},
"original": {
@@ -84,11 +84,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1719226092,
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
"lastModified": 1747284884,
"narHash": "sha256-lTSKhRrassMcJ1ZsuUVunyl/F04vvCKY80HB/4rvvm4=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
"rev": "7168f6002a6b48a9b6151e1e97e974a0722ecfdc",
"type": "github"
},
"original": {
@@ -115,23 +115,26 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1722555339,
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
"lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1713714899,
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
"lastModified": 1747179050,
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
"type": "github"
},
"original": {
@@ -143,11 +146,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1723991338,
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
"lastModified": 1747542820,
"narHash": "sha256-GaOZntlJ6gPPbbkTLjbd8BMWaDYafhuuYRNrxCGnPJw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8a3354191c0d7144db9756a74755672387b702ba",
"rev": "292fa7d4f6519c074f0a50394dbbe69859bb6043",
"type": "github"
},
"original": {

135
flake.nix
View File

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

@@ -3,8 +3,8 @@
let
sk_gpu_zip = fetchurl {
url =
"https://github.com/StereoKit/sk_gpu/releases/download/v2024.8.16/sk_gpu.v2024.8.16.zip";
sha256 = "sha256-Wk3PZFlWqhrsQ8xG0sQaV2xSasdg2D7TMiPvl/CgtGU=";
"https://github.com/StereoKit/sk_gpu/releases/download/v2024.9.26/sk_gpu.v2024.9.26.zip";
sha256 = "sha256-W32RveeCszioWGtbCsvAqB28YHvOsw2xJ15MosYLFXk=";
};
in stdenv.mkDerivation rec {
name = "sk_gpu";

View File

@@ -6,6 +6,7 @@
, xorg
, fontconfig
, libxkbcommon
, xkeyboard_config
, libclang
, cmake
@@ -15,11 +16,13 @@
, fetchFromGitHub
, sk_gpu
, libXau
, libgbm
, libXdmcp
, stdenv
, lib
, openxr-loader
, makeWrapper
}:
rustPlatform.buildRustPackage rec {
@@ -47,10 +50,17 @@ rustPlatform.buildRustPackage rec {
rev = "900e40fb5d2502927360fe2f31762bdbb624455f";
sha256 = "sha256-zBRAXgG5Fi6+5uPQCI/RCGatY6O4ELuYBoKrPNn4K+8=";
};
openxr_loader = fetchFromGitHub {
owner = "KhronosGroup";
repo = "OpenXR-SDK";
rev = "288d3a7ebc1ad959f62d51da75baa3d27438c499";
sha256 = "sha256-RdmnBe26hqPmqwCHIJolF6bSmZRmIKVlGF+TXAY35ig=";
};
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
DEP_SK_GPU_SOURCE = "${sk_gpu}";
DEP_OPENXR_LOADER_SOURCE = "${openxr_loader}";
postPatch = let libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
in ''
@@ -69,7 +79,8 @@ rustPlatform.buildRustPackage rec {
--set-rpath "${libPath}" \
$sk/sk_gpu/tools/linux_x64/skshaderc
'';
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang makeWrapper ];
buildInputs = [
libGL
mesa
@@ -78,9 +89,16 @@ rustPlatform.buildRustPackage rec {
xorg.libXfixes
fontconfig
libxkbcommon
xkeyboard_config
libXau
libXdmcp
openxr-loader
libgbm
];
LIBCLANG_PATH = "${libclang.lib}/lib";
postFixup = ''
wrapProgram $out/bin/stardust-xr-server \
--set XKB_CONFIG_ROOT "${xkeyboard_config}/share/X11/xkb"
'';
}

View File

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

View File

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

View File

@@ -1,14 +1,13 @@
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::any::Any;
use std::{any::Any, sync::LazyLock};
use tokio::sync::mpsc::{self, unbounded_channel};
type Anything = Box<dyn Any + Send + Sync>;
static MAIN_DESTROY_QUEUE: Lazy<(
static MAIN_DESTROY_QUEUE: LazyLock<(
mpsc::UnboundedSender<Anything>,
Mutex<mpsc::UnboundedReceiver<Anything>>,
)> = Lazy::new(|| {
)> = LazyLock::new(|| {
let (tx, rx) = unbounded_channel();
(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 delta;
pub mod destroy_queue;
pub mod idl_utils;
pub mod error;
pub mod registry;
pub mod resource;
pub mod scenegraph;

View File

@@ -1,19 +1,18 @@
#![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 std::ops::Deref;
use std::ptr;
use std::sync::{Arc, Weak};
use std::sync::{Arc, LazyLock, Weak};
#[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> {
pub const fn new() -> Self {
Registry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
Registry(MaybeLazy::Lazy(LazyLock::new(DashMap::default)))
}
pub fn add(&self, t: T) -> Arc<T>
where
@@ -24,30 +23,29 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
t_arc
}
pub fn add_raw(&self, t: &Arc<T>) {
self.lock()
self.0
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t));
}
pub fn contains(&self, t: &T) -> bool {
self.lock()
self.0
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
}
pub fn get_changes(old: &Registry<T>, new: &Registry<T>) -> (Vec<Arc<T>>, Vec<Arc<T>>) {
let old = old.lock();
let new = new.lock();
let mut added = Vec::new();
let mut removed = Vec::new();
for (id, entry) in new.iter() {
for pair in new.0.iter() {
let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade() {
if !old.contains_key(id) {
if !old.0.contains_key(id) {
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 !new.contains_key(id) {
if !new.0.contains_key(id) {
removed.push(entry);
}
}
@@ -55,52 +53,48 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
(added, removed)
}
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.lock()
self.0
.iter()
.filter_map(|pair| pair.1.upgrade())
.filter_map(|pair| pair.value().upgrade())
.collect()
}
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>> {
self.0
.lock()
.take()
.unwrap_or_default()
.into_iter()
.filter_map(|pair| pair.1.upgrade())
.collect()
let contents = self.get_valid_contents();
self.0.clear();
contents
}
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 {
// why would we want to retain things we can't upgrade?
return true;
};
(f)(&v)
})
}
pub fn remove(&self, t: &T) {
self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize));
self.0.remove(&(ptr::addr_of!(*t) as *const () as usize));
}
pub fn clear(&self) {
self.lock().clear();
self.0.clear();
}
pub fn is_empty(&self) -> bool {
let registry = self.0.lock();
let Some(registry) = &*registry else {
return true;
};
if registry.is_empty() {
if self.0.is_empty() {
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> {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone()))
Self(self.0.clone())
}
}
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>>>>);
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::alias::get_original;
use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Serialize;
@@ -11,13 +10,13 @@ use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::serialize;
use std::future::Future;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::oneshot;
use tracing::{debug, debug_span};
#[derive(Default)]
pub struct Scenegraph {
pub(super) client: OnceCell<Weak<Client>>,
pub(super) client: OnceLock<Weak<Client>>,
nodes: Mutex<FxHashMap<u64, Arc<Node>>>,
}
@@ -59,26 +58,26 @@ impl MethodResponseSender {
// ) {
// let _ = self.0.send(map_method_return(result));
// }
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MethodError {
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MemberError {
error: e.to_string(),
}))
}
pub fn wrap_async<T: Serialize>(
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)) });
}
}
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> {
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
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}"),
})?;
Ok((serialized_value, fds))
@@ -86,19 +85,21 @@ fn map_method_return<T: Serialize>(
impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(
&self,
node: u64,
node_id: u64,
aspect_id: u64,
method: u64,
data: &[u8],
fds: Vec<OwnedFd>,
) -> Result<(), ScenegraphError> {
let Some(client) = self.get_client() else {
return Err(ScenegraphError::SignalNotFound);
return Err(ScenegraphError::NodeNotFound);
};
debug_span!("Handle signal", node, method).in_scope(|| {
self.get_node(node)
debug_span!("Handle signal", aspect_id, node_id, method).in_scope(|| {
self.get_node(node_id)
.ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal(
client,
aspect_id,
method,
Message {
data: data.to_vec(),
@@ -109,23 +110,25 @@ impl scenegraph::Scenegraph for Scenegraph {
}
fn execute_method(
&self,
node: u64,
node_id: u64,
aspect_id: u64,
method: u64,
data: &[u8],
fds: Vec<OwnedFd>,
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
) {
let Some(client) = self.get_client() else {
let _ = response.send(Err(ScenegraphError::MethodNotFound));
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
debug!(node, method, "Handle method");
let Some(node) = self.get_node(node) else {
debug!(aspect_id, node_id, method, "Handle method");
let Some(node) = self.get_node(node_id) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
node.execute_local_method(
client,
aspect_id,
method,
Message {
data: data.to_vec(),

View File

@@ -11,30 +11,32 @@ use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, input};
use clap::Parser;
use core::client::Client;
use core::client::{Client, tick_internal_client};
use core::task;
use directories::ProjectDirs;
use objects::ServerObjects;
use once_cell::sync::OnceCell;
use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use stereokit_rust::material::Material;
use stereokit_rust::shader::Shader;
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
use stereokit_rust::system::{LogLevel, Renderer};
use stereokit_rust::sk::{
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::ui::Ui;
use stereokit_rust::util::{Color128, Time};
use stereokit_rust::util::{Color128, SphericalHarmonics, Time};
use tokio::net::UnixListener;
use tokio::sync::Notify;
use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use zbus::fdo::ObjectManager;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use zbus::Connection;
use zbus::fdo::ObjectManager;
#[derive(Debug, Clone, Parser)]
#[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/`.
#[clap(id = "SESSION_ID", long = "restore", action)]
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() {
color_eyre::install().unwrap();
@@ -91,11 +97,29 @@ async fn main() {
.with_thread_names(true)
.with_ansi(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();
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 =
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");
@@ -120,7 +144,9 @@ async fn main() {
let project_dirs = ProjectDirs::from("", "", "stardust");
if project_dirs.is_none() {
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
error!(
"Unable to get Stardust project directories, default skybox and startup script will not work."
);
}
let dbus_connection = Connection::session()
@@ -129,7 +155,9 @@ async fn main() {
dbus_connection
.request_name("org.stardustxr.HMD")
.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
.object_server()
@@ -137,13 +165,25 @@ async fn main() {
.await
.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 stereokit_loop = tokio::task::spawn_blocking({
let sk_ready_notifier = sk_ready_notifier.clone();
let project_dirs = project_dirs.clone();
let cli_args = cli_args.clone();
let dbus_connection = dbus_connection.clone();
move || stereokit_loop(sk_ready_notifier, project_dirs, cli_args, dbus_connection)
move || {
stereokit_loop(
sk_ready_notifier,
project_dirs,
cli_args,
dbus_connection,
object_registry,
)
}
});
sk_ready_notifier.notified().await;
let mut startup_children = project_dirs
@@ -166,14 +206,19 @@ async fn main() {
info!("Cleanly shut down Stardust");
}
static DEFAULT_SKYTEX: OnceLock<Tex> = OnceLock::new();
static DEFAULT_SKYLIGHT: OnceLock<SphericalHarmonics> = OnceLock::new();
fn stereokit_loop(
sk_ready_notifier: Arc<Notify>,
project_dirs: Option<ProjectDirs>,
args: CliArgs,
dbus_connection: Connection,
object_registry: ObjectRegistry,
) {
let sk = SkSettings::default()
.app_name("Stardust XR")
.blend_preference(DisplayBlend::AnyTransparent)
.mode(if args.flatscreen {
AppMode::Simulator
} else {
@@ -201,23 +246,33 @@ fn stereokit_loop(
Material::default().shader(Shader::pbr_clip());
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
{
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
.as_ref()
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
.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();
} else {
Renderer::skytex(Tex::gen_color(
Color128::BLACK,
1,
1,
TexType::Cubemap,
TexFormat::RGBA32,
));
Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
}
}
@@ -231,6 +286,7 @@ fn stereokit_loop(
let mut objects = ServerObjects::new(
dbus_connection.clone(),
&sk,
[left_hand_material, right_hand_material],
args.disable_controllers,
args.disable_hands,
);
@@ -246,7 +302,7 @@ fn stereokit_loop(
wayland.frame_event();
destroy_queue::clear();
objects.update(&sk, token);
objects.update(&sk, token, &dbus_connection, &object_registry);
input::process_input();
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
adaptive_sleep(
@@ -255,6 +311,7 @@ fn stereokit_loop(
Duration::from_micros(250),
);
tick_internal_client();
#[cfg(feature = "wayland")]
wayland.update();
drawable::draw(token);
@@ -262,8 +319,6 @@ fn stereokit_loop(
}
info!("Cleanly shut down StereoKit");
#[cfg(feature = "wayland")]
drop(wayland);
}
fn adaptive_sleep(

View File

@@ -1,6 +1,5 @@
use super::{Aspect, Node};
use crate::core::{client::Client, registry::Registry};
use color_eyre::eyre::Result;
use super::{Aspect, AspectIdentifier, Node};
use crate::core::{client::Client, error::Result, registry::Registry};
use std::{
ops::Add,
sync::{Arc, Weak},
@@ -68,8 +67,31 @@ impl Alias {
Ok(())
}
}
impl AspectIdentifier for Alias {
const ID: u64 = 0;
}
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>> {
@@ -106,7 +128,7 @@ impl AliasList {
.into_iter()
.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| {
let Some(node) = get_original(node.clone(), false) else {
return false;
@@ -120,7 +142,7 @@ impl AliasList {
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
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| {
let Some(original) = get_original(node.clone(), false) else {
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::destroy_queue;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::create_interface;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::{Spatial, Transform};
use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell;
use crate::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform};
use color_eyre::eyre::eyre;
use glam::{Vec4Swizzles, vec3};
use parking_lot::Mutex;
use stardust_xr::values::ResourceID;
use std::ops::DerefMut;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use std::{ffi::OsStr, path::PathBuf};
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
@@ -25,7 +23,7 @@ pub struct Sound {
volume: f32,
pending_audio_path: PathBuf,
sk_sound: OnceCell<SkSound>,
sk_sound: OnceLock<SkSound>,
instance: Mutex<Option<SoundInst>>,
stop: Mutex<Option<()>>,
play: Mutex<Option<()>>,
@@ -42,14 +40,13 @@ impl Sound {
space: node.get_aspect::<Spatial>().unwrap().clone(),
volume: 1.0,
pending_audio_path,
sk_sound: OnceCell::new(),
sk_sound: OnceLock::new(),
instance: Mutex::new(None),
stop: Mutex::new(None),
play: Mutex::new(None),
};
let sound_arc = SOUND_REGISTRY.add(sound);
node.add_aspect_raw(sound_arc.clone());
<Sound as SoundAspect>::add_node_members(node);
Ok(sound_arc)
}
@@ -71,8 +68,11 @@ impl Sound {
}
}
}
impl AspectIdentifier for Sound {
impl_aspect_for_sound_aspect_id! {}
}
impl Aspect for Sound {
const NAME: &'static str = "Sound";
impl_aspect_for_sound_aspect! {}
}
impl SoundAspect for Sound {
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -88,6 +88,9 @@ impl SoundAspect for Sound {
}
impl Drop for Sound {
fn drop(&mut self) {
if let Some(instance) = self.instance.lock().take() {
instance.stop();
}
if let Some(sk_sound) = self.sk_sound.take() {
destroy_queue::add(sk_sound);
}
@@ -101,9 +104,7 @@ pub fn update() {
}
}
create_interface!(AudioInterface);
struct AudioInterface;
impl InterfaceAspect for AudioInterface {
impl InterfaceAspect for Interface {
#[doc = "Create a sound node. WAV and MP3 are supported."]
fn create_sound(
_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 crate::{
core::{client::Client, registry::Registry},
nodes::{spatial::Spatial, Aspect, Node},
core::{client::Client, error::Result, registry::Registry},
nodes::{Node, spatial::Spatial},
};
use color_eyre::eyre::Result;
use glam::Vec3;
use glam::{FloatExt, Vec3};
use parking_lot::Mutex;
use prisma::Lerp;
use std::{collections::VecDeque, sync::Arc};
use stereokit_rust::{
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
@@ -40,7 +38,6 @@ impl Lines {
space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(lines),
});
<Lines as LinesAspect>::add_node_members(node);
node.add_aspect_raw(lines.clone());
Ok(lines)
@@ -64,10 +61,10 @@ impl Lines {
let last = line.points.last().unwrap();
let color = Color128 {
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
a: first.color.a.lerp(&last.color.a, 0.5),
r: first.color.c.r.lerp(last.color.c.r, 0.5),
g: first.color.c.g.lerp(last.color.c.g, 0.5),
b: first.color.c.b.lerp(last.color.c.b, 0.5),
a: first.color.a.lerp(last.color.a, 0.5),
};
let connect_point = SkLinePoint {
pt: transform_mat
@@ -83,9 +80,6 @@ impl Lines {
}
}
}
impl Aspect for Lines {
const NAME: &'static str = "Lines";
}
impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
let lines_aspect = node.get_aspect::<Lines>()?;

View File

@@ -1,21 +1,20 @@
pub mod lines;
pub mod model;
#[cfg(feature = "wayland")]
pub mod shader_manipulation;
pub mod shaders;
pub mod text;
use self::{lines::Lines, model::Model, text::Text};
use super::{
Aspect, AspectIdentifier, Node,
spatial::{Spatial, Transform},
Node,
};
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::{DEFAULT_SKYLIGHT, nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO};
use crate::{
core::{client::Client, resource::get_resource_file},
create_interface,
DEFAULT_SKYTEX,
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 stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
@@ -26,30 +25,72 @@ pub fn draw(token: &MainThreadToken) {
lines::draw_all(token);
model::draw_all(token);
text::draw_all(token);
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
Renderer::skytex(skytex.tex);
match QUEUED_SKYTEX.lock().take() {
Some(Some(skytex)) => {
if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) {
Renderer::skytex(skytex.tex);
}
}
Some(None) => {
Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
}
None => {}
}
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
Renderer::skylight(skylight.sh);
match QUEUED_SKYLIGHT.lock().take() {
Some(Some(skylight)) => {
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_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYLIGHT: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
stardust_xr_server_codegen::codegen_drawable_protocol!();
create_interface!(DrawableInterface);
pub struct DrawableInterface;
impl InterfaceAspect for DrawableInterface {
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?;
impl AspectIdentifier for Lines {
impl_aspect_for_lines_aspect_id! {}
}
impl Aspect for Lines {
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);
Ok(())
}
@@ -57,10 +98,14 @@ impl InterfaceAspect for DrawableInterface {
fn set_sky_light(
_node: Arc<Node>,
calling_client: Arc<Client>,
light: ResourceID,
light: Option<ResourceID>,
) -> Result<()> {
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?;
let resource_path = light
.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);
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::error::Result;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::nodes::Node;
use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::Spatial;
use crate::nodes::{Aspect, Node};
use color_eyre::eyre::{bail, eyre, Result};
use color_eyre::eyre::eyre;
use glam::{Mat4, Vec2, Vec3};
use once_cell::sync::{Lazy, OnceCell};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::values::ResourceID;
use std::ffi::OsStr;
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::maths::Bounds;
use stereokit_rust::sk::MainThreadToken;
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
pub struct MaterialWrapper(pub Material);
impl Drop for MaterialWrapper {
fn drop(&mut self) {
MATERIAL_REGISTRY.remove(self);
}
}
impl Hash for MaterialWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.get_shader().0.as_ptr().hash(state);
for param in self.0.get_all_param_info() {
param.to_string().hash(state)
param.name.hash(state);
param.to_string().hash(state);
}
self.0.get_chain().map(MaterialWrapper).hash(state)
}
@@ -57,10 +65,9 @@ unsafe impl Send for MaterialWrapper {}
unsafe impl Sync for MaterialWrapper {}
#[derive(Default)]
struct MaterialRegistry(Mutex<FxHashMap<u64, String>>);
struct MaterialRegistry(Mutex<FxHashMap<u64, Weak<MaterialWrapper>>>);
impl MaterialRegistry {
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
let mut lock = self.0.lock();
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
@@ -68,20 +75,31 @@ impl MaterialRegistry {
hasher.finish()
};
if let Some(id) = lock.get(&hash) {
if let Ok(existing) = Material::find(id) {
return Arc::new(MaterialWrapper(existing));
let mut lock = self.0.lock();
if let Some(mat) = lock.get(&hash) {
if let Some(mat) = mat.upgrade() {
return mat;
}
}
lock.insert(hash, material.0.get_id().to_string());
lock.insert(hash, Arc::downgrade(&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 HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
static HOLDOUT_MATERIAL: OnceLock<Arc<MaterialWrapper>> = OnceLock::new();
impl MaterialParameter {
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
@@ -130,6 +148,7 @@ pub struct ModelPart {
path: String,
space: Arc<Spatial>,
model: Weak<Model>,
material: Mutex<Option<Arc<MaterialWrapper>>>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
aliases: AliasList,
@@ -203,8 +222,8 @@ impl ModelPart {
pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None),
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());
parts.push(model_part.clone());
Some(model_part)
@@ -230,7 +249,10 @@ impl ModelPart {
};
let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
let mut lock = self.material.lock();
part.material(&shared_material.0);
lock.replace(shared_material);
}
fn update(&self) {
@@ -257,7 +279,9 @@ impl ModelPart {
};
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
let mut lock = self.material.lock();
part.material(&material_replacement.0);
lock.replace(material_replacement);
}
'mat_params: {
@@ -273,14 +297,13 @@ impl ModelPart {
let shared_material =
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
let mut lock = self.material.lock();
part.material(&shared_material.0);
lock.replace(shared_material);
}
}
}
}
impl Aspect for ModelPart {
const NAME: &'static str = "ModelPart";
}
impl ModelPartAspect for ModelPart {
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -309,7 +332,7 @@ impl ModelPartAspect for ModelPart {
pub struct Model {
space: Arc<Spatial>,
_resource_id: ResourceID,
sk_model: OnceCell<SKModel>,
sk_model: OnceLock<SKModel>,
parts: Mutex<Vec<Arc<ModelPart>>>,
}
impl Model {
@@ -324,10 +347,9 @@ impl Model {
let model = Arc::new(Model {
space: node.get_aspect::<Spatial>().unwrap().clone(),
_resource_id: resource_id,
sk_model: OnceCell::new(),
sk_model: OnceLock::new(),
parts: Mutex::new(Vec::default()),
});
<Model as ModelAspect>::add_node_members(node);
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
@@ -361,9 +383,6 @@ impl Model {
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
impl Aspect for Model {
const NAME: &'static str = "Model";
}
impl ModelAspect for Model {
#[doc = "Bind a model part to the node with the ID input."]
fn bind_model_part(
@@ -371,12 +390,12 @@ impl ModelAspect for Model {
calling_client: Arc<Client>,
id: u64,
part_path: String,
) -> color_eyre::eyre::Result<()> {
) -> Result<()> {
let model = node.get_aspect::<Model>()?;
let parts = model.parts.lock();
let Some(part) = parts.iter().find(|p| p.path == part_path) else {
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",)
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",);
};
Alias::create_with_id(
&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::{
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
nodes::{spatial::Spatial, Aspect, Node},
core::{
client::Client, destroy_queue, error::Result, registry::Registry,
resource::get_resource_file,
},
nodes::{Node, spatial::Spatial},
};
use color_eyre::eyre::{eyre, Result};
use glam::{vec3, Mat4, Vec2};
use once_cell::sync::OnceCell;
use color_eyre::eyre::eyre;
use glam::{Mat4, Vec2, vec3};
use parking_lot::Mutex;
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use std::{
ffi::OsStr,
path::PathBuf,
sync::{Arc, OnceLock},
};
use stereokit_rust::{
font::Font,
sk::MainThreadToken,
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
util::{Color128, Color32},
util::{Color32, Color128},
};
use super::{TextAspect, TextStyle};
@@ -35,7 +41,7 @@ fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
pub struct Text {
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceCell<SkTextStyle>,
style: OnceLock<SkTextStyle>,
text: Mutex<String>,
data: Mutex<TextStyle>,
@@ -48,93 +54,85 @@ impl Text {
font_path: style.font.as_ref().and_then(|res| {
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
}),
style: OnceCell::new(),
style: OnceLock::new(),
text: Mutex::new(text),
data: Mutex::new(style),
});
<Text as TextAspect>::add_node_members(node);
node.add_aspect_raw(text.clone());
Ok(text)
}
fn draw(&self, token: &MainThreadToken) {
let style =
self.style
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
let font = self
.font_path
.as_deref()
.and_then(|path| Font::from_file(path).ok())
.unwrap_or_default();
Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
});
let style = self.style.get_or_init(|| {
let font = self
.font_path
.as_deref()
.and_then(|path| Font::from_file(path).ok())
.unwrap_or_default();
SkTextStyle::from_font(font, 1.0, Color32::WHITE)
});
if let Ok(style) = style {
let text = self.text.lock();
let data = self.data.lock();
let transform = self.space.global_transform()
* Mat4::from_scale(vec3(
data.character_height,
data.character_height,
data.character_height,
));
if let Some(bounds) = &data.bounds {
stereokit_rust::system::Text::add_in(
token,
&*text,
transform,
Vec2::from(bounds.bounds) / data.character_height,
match bounds.fit {
super::TextFit::Wrap => TextFit::Wrap,
super::TextFit::Clip => TextFit::Clip,
super::TextFit::Squeeze => TextFit::Squeeze,
super::TextFit::Exact => TextFit::Exact,
super::TextFit::Overflow => TextFit::Overflow,
},
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
} else {
stereokit_rust::system::Text::add_at(
token,
&*text,
transform,
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
}
let text = self.text.lock();
let data = self.data.lock();
let transform = self.space.global_transform()
* Mat4::from_scale(vec3(
data.character_height,
data.character_height,
data.character_height,
));
if let Some(bounds) = &data.bounds {
stereokit_rust::system::Text::add_in(
token,
&*text,
transform,
Vec2::from(bounds.bounds) / data.character_height,
match bounds.fit {
super::TextFit::Wrap => TextFit::Wrap,
super::TextFit::Clip => TextFit::Clip,
super::TextFit::Squeeze => TextFit::Squeeze,
super::TextFit::Exact => TextFit::Exact,
super::TextFit::Overflow => TextFit::Overflow,
},
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
} else {
stereokit_rust::system::Text::add_at(
token,
&*text,
transform,
Some(*style),
Some(Color128::new(
data.color.c.r,
data.color.c.g,
data.color.c.b,
data.color.a,
)),
data.bounds
.as_ref()
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
Some(convert_align(data.text_align_x, data.text_align_y)),
None,
None,
None,
);
}
}
}
impl Aspect for Text {
const NAME: &'static str = "Text";
}
impl TextAspect for Text {
fn set_character_height(
node: Arc<Node>,

View File

@@ -1,25 +1,25 @@
use super::alias::{Alias, AliasInfo};
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,
};
use super::{Aspect, Node};
use super::{Aspect, AspectIdentifier, Node};
use crate::core::client::Client;
use crate::create_interface;
use crate::nodes::spatial::Transform;
use crate::core::error::Result;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{OptionExt, Result};
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
use once_cell::sync::Lazy;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::OptionExt;
use glam::{Vec3, Vec3A, Vec3Swizzles, vec2, vec3, vec3a};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
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
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
pub static FIELD_ALIAS_INFO: LazyLock<AliasInfo> = LazyLock::new(|| AliasInfo {
server_methods: vec![
SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
@@ -145,15 +145,65 @@ impl Field {
shape: Mutex::new(shape),
};
let field = node.add_aspect(field);
<Field as FieldRefAspect>::add_node_members(node);
<Field as FieldAspect>::add_node_members(node);
node.add_aspect(FieldRef);
Ok(field)
}
}
impl Aspect for Field {
const NAME: &'static str = "Field";
impl AspectIdentifier for 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(
node: Arc<Node>,
_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> {
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 {
impl InterfaceAspect for Interface {
async fn import_field_ref(
_node: Arc<Node>,
calling_client: Arc<Client>,
uid: u64,
) -> Result<Arc<Node>> {
EXPORTED_FIELDS
Ok(EXPORTED_FIELDS
.lock()
.get(&uid)
.map(|s| {
@@ -266,7 +274,7 @@ impl InterfaceAspect for FieldInterface {
)
.unwrap()
})
.ok_or_eyre("Couldn't find spatial with that ID")
.ok_or_eyre("Couldn't find spatial with that ID")?)
}
fn create_field(

View File

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

View File

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

View File

@@ -1,18 +1,17 @@
use super::{
input_method_client, InputData, InputDataTrait, InputDataType, InputHandler, InputMethodAspect,
InputMethodRefAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO,
INPUT_METHOD_REGISTRY,
INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO, INPUT_METHOD_REGISTRY, InputData,
InputDataTrait, InputDataType, InputHandler, InputMethodAspect, InputMethodRefAspect,
input_method_client,
};
use crate::{
core::{client::Client, registry::Registry},
core::{client::Client, error::Result, registry::Registry},
nodes::{
Node,
alias::{Alias, AliasList},
fields::{Field, FIELD_ALIAS_INFO},
fields::{FIELD_ALIAS_INFO, Field},
spatial::Spatial,
Aspect, Node,
},
};
use color_eyre::eyre::Result;
use parking_lot::Mutex;
use stardust_xr::values::Datamap;
use std::sync::{Arc, Weak};
@@ -25,7 +24,7 @@ pub struct InputMethod {
handler_aliases: AliasList,
handler_field_aliases: AliasList,
pub(super) handler_order: Mutex<Vec<Weak<InputHandler>>>,
pub internal_capture_requests: Registry<InputHandler>,
pub capture_attempts: Registry<InputHandler>,
pub captures: Registry<InputHandler>,
}
impl InputMethod {
@@ -42,16 +41,15 @@ impl InputMethod {
handler_aliases: AliasList::default(),
handler_field_aliases: AliasList::default(),
handler_order: Mutex::new(Vec::new()),
internal_capture_requests: Registry::new(),
capture_attempts: Registry::new(),
captures: Registry::new(),
};
<InputMethod as InputMethodRefAspect>::add_node_members(node);
<InputMethod as InputMethodAspect>::add_node_members(node);
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
}
let method = INPUT_METHOD_REGISTRY.add(method);
node.add_aspect_raw(method.clone());
node.add_aspect(InputMethodRef);
Ok(method)
}
@@ -131,6 +129,7 @@ impl InputMethod {
self.handler_aliases.remove_aspect(handler);
self.handler_field_aliases
.remove_aspect(handler.field.as_ref());
self.capture_attempts.remove(handler);
}
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
@@ -153,24 +152,23 @@ impl InputMethod {
captured: self.captures.get_valid_contents().contains(handler),
}
}
}
impl Aspect for InputMethod {
const NAME: &'static str = "InputMethod";
}
impl InputMethodRefAspect for InputMethod {
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
fn request_capture(
node: Arc<Node>,
_calling_client: Arc<Client>,
handler: Arc<Node>,
) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
let input_handler = handler.get_aspect::<InputHandler>()?;
input_method
.internal_capture_requests
.add_raw(&input_handler);
Ok(())
pub(super) fn cull_capture_attempts(&self) {
let sent = self
.handler_order
.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 {
@@ -231,3 +229,28 @@ impl Drop for InputMethod {
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);
Ok(())
}
#[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);
Ok(())
}
}

View File

@@ -9,14 +9,15 @@ mod tip;
pub use handler::*;
pub use method::*;
use super::Aspect;
use super::AspectIdentifier;
use super::fields::Field;
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_REF_ASPECT_ALIAS_INFO;
use crate::{core::client::Client, nodes::Node};
use crate::{core::registry::Registry, nodes::spatial::Transform};
use color_eyre::eyre::Result;
use stardust_xr::values::Datamap;
use std::sync::Arc;
@@ -25,6 +26,25 @@ pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
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 {
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
@@ -47,9 +67,7 @@ impl InputDataTrait for InputDataType {
}
}
create_interface!(InputInterface);
pub struct InputInterface;
impl InterfaceAspect for InputInterface {
impl InterfaceAspect for Interface {
#[doc = "Create an input method node"]
fn create_input_method(
_node: Arc<Node>,
@@ -147,6 +165,6 @@ pub fn process_input() {
let _ = input_handler_client::input(&handler_node, &methods, &datas);
}
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},
spatial::Spatial,
};
use glam::{vec3, Mat4, Quat};
use glam::{Mat4, Quat, vec3};
use std::sync::{Arc, Weak};
impl Default for Pointer {

View File

@@ -1,30 +1,30 @@
use super::{
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType,
};
use super::{Item, ItemType, create_item_acceptor_flex, register_item_ui_flex};
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_ASPECT_ALIAS_INFO;
use crate::{
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
create_interface,
nodes::{
Message, Node,
drawable::{
model::{MaterialWrapper, ModelPart},
shaders::UNLIT_SHADER_BYTES,
},
items::TypeInfo,
spatial::{Spatial, Transform},
Message, Node,
},
};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::{ColumnMatrix4, Vector2};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
use std::sync::OnceLock;
use stereokit_rust::{
material::{Material, Transparency},
shader::Shader,
@@ -33,7 +33,6 @@ use stereokit_rust::{
tex::{Tex, TexFormat, TexType},
util::Color128,
};
use tracing::error;
pub struct TexWrapper(pub Tex);
unsafe impl Send for TexWrapper {}
@@ -48,6 +47,12 @@ lazy_static! {
ui: Default::default(),
items: 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| {
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
}
@@ -62,30 +67,27 @@ struct FrameInfo {
pub struct CameraItem {
space: Arc<Spatial>,
frame_info: Mutex<FrameInfo>,
sk_tex: OnceCell<TexWrapper>,
sk_mat: OnceCell<Arc<MaterialWrapper>>,
sk_tex: OnceLock<TexWrapper>,
sk_mat: OnceLock<Arc<MaterialWrapper>>,
applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>,
}
#[allow(unused)]
impl CameraItem {
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
Item::add_to(
node,
&ITEM_TYPE_INFO_CAMERA,
ItemType::Camera(CameraItem {
space: node.get_aspect::<Spatial>().unwrap().clone(),
frame_info: Mutex::new(FrameInfo {
proj_matrix,
px_size,
}),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
applied_to: Registry::new(),
apply_to: Registry::new(),
let item = Arc::new(CameraItem {
space: node.get_aspect::<Spatial>().unwrap().clone(),
frame_info: Mutex::new(FrameInfo {
proj_matrix,
px_size,
}),
);
// <CameraItem as CameraItemAspect>::node_methods(node);
sk_tex: OnceLock::new(),
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(
@@ -97,7 +99,7 @@ impl CameraItem {
response.wrap_sync(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else {
return Err(eyre!("Wrong item type?"));
bail!("Wrong item type?");
};
Ok(serialize(())?.into())
});
@@ -109,7 +111,7 @@ impl CameraItem {
message: Message,
) -> Result<()> {
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
bail!("Wrong item type?")
bail!("Wrong item type?");
};
let model_part_node =
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
@@ -137,19 +139,13 @@ impl CameraItem {
TexFormat::RGBA32Linear,
))
});
let sk_mat = self
.sk_mat
.get_or_try_init(|| -> Result<Arc<MaterialWrapper>> {
let shader = Shader::from_memory(UNLIT_SHADER_BYTES)?;
let mut mat = Material::new(&shader, None);
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
mat.transparency(Transparency::Blend);
Ok(Arc::new(MaterialWrapper(mat)))
});
let Ok(sk_mat) = sk_mat else {
error!("unable to make camera item stereokit texture");
return;
};
let sk_mat = self.sk_mat.get_or_init(|| {
let shader = Shader::from_memory(UNLIT_SHADER_BYTES).unwrap();
let mut mat = Material::new(&shader, None);
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
mat.transparency(Transparency::Blend);
Arc::new(MaterialWrapper(mat))
});
for model_part in self.apply_to.take_valid_contents() {
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 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<()> {
super::acceptor_capture_item_flex(node, item)
}
@@ -184,8 +202,7 @@ pub fn update(token: &MainThreadToken) {
}
}
create_interface!(ItemInterface);
impl InterfaceAspect for ItemInterface {
impl InterfaceAspect for Interface {
#[doc = "Create a camera item at a specific location"]
fn create_camera_item(
_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."]
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)
}
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
fn create_camera_item_acceptor(
_node: Arc<Node>,
node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
node.add_aspect(CameraItemAcceptor);
create_item_acceptor_flex(
calling_client,
id,

View File

@@ -4,15 +4,16 @@ pub mod panel;
use self::camera::CameraItem;
use self::panel::PanelItemTrait;
use super::alias::AliasList;
use super::fields::{Field, FIELD_ALIAS_INFO};
use super::fields::{FIELD_ALIAS_INFO, Field};
use super::spatial::Spatial;
use super::{Alias, Aspect, Node};
use super::{Alias, Aspect, AspectIdentifier, Node};
use crate::core::client::Client;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::ensure;
use crate::nodes::alias::AliasInfo;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
use color_eyre::eyre::{ensure, Result};
use crate::nodes::spatial::Transform;
use parking_lot::Mutex;
use std::hash::Hash;
use std::sync::{Arc, Weak};
@@ -47,6 +48,8 @@ pub struct TypeInfo {
pub ui: Mutex<Weak<ItemUI>>,
pub items: Registry<Item>,
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>),
}
impl Hash for TypeInfo {
@@ -81,7 +84,6 @@ impl Item {
};
let item = type_info.items.add(item);
<Item as ItemAspect>::add_node_members(node);
if let Some(ui) = type_info.ui.lock().upgrade() {
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 {
const NAME: &'static str = "Item";
impl_aspect_for_item_aspect! {}
}
impl ItemAspect for Item {
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
@@ -129,7 +134,7 @@ impl Drop for Item {
}
pub enum ItemType {
Camera(CameraItem),
Camera(Arc<CameraItem>),
Panel(Arc<dyn PanelItemTrait>),
}
impl ItemType {
@@ -284,8 +289,11 @@ impl ItemUI {
.remove_aspect(acceptor.field.as_ref());
}
}
impl AspectIdentifier for ItemUI {
impl_aspect_for_item_ui_aspect_id! {}
}
impl Aspect for ItemUI {
const NAME: &'static str = "Item";
impl_aspect_for_item_ui_aspect! {}
}
impl Drop for ItemUI {
fn drop(&mut self) {
@@ -342,8 +350,11 @@ impl ItemAcceptor {
let _ = item_acceptor_client::release_item(&node, alias.id);
}
}
impl AspectIdentifier for ItemAcceptor {
impl_aspect_for_item_acceptor_aspect_id! {}
}
impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor";
impl_aspect_for_item_acceptor_aspect! {}
}
impl ItemAcceptorAspect for ItemAcceptor {}
impl Drop for ItemAcceptor {
@@ -364,6 +375,7 @@ pub fn register_item_ui_flex(
) -> Result<()> {
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?;
(type_info.add_ui_aspect)(&ui);
Ok(())
}
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()?;
Spatial::add_to(&node, Some(space.clone()), transform, false);
ItemAcceptor::add_to(&node, type_info, field);
(type_info.add_acceptor_aspect)(&node);
Ok(())
}
@@ -391,6 +404,3 @@ fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
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_ASPECT_ALIAS_INFO;
use crate::nodes::{Aspect, AspectIdentifier};
use crate::{
core::{
client::{get_env, state, Client, INTERNAL_CLIENT},
client::{Client, INTERNAL_CLIENT, get_env, state},
registry::Registry,
},
create_interface,
nodes::{
Node,
drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo},
spatial::{Spatial, Transform},
Node,
},
};
use color_eyre::eyre::Result;
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use parking_lot::Mutex;
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use std::sync::{Arc, Weak};
use tracing::{debug, info};
@@ -32,6 +36,7 @@ impl Default for Geometry {
}
lazy_static! {
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel",
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
@@ -39,6 +44,12 @@ lazy_static! {
ui: Default::default(),
items: 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| {
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>>,
);
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_move(&self, id: u32, position: Vector2<f32>);
@@ -115,7 +126,7 @@ impl<B: Backend> PanelItem<B> {
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item),
);
<Self as PanelItemAspect>::add_node_members(&node);
node.add_aspect_raw(panel_item.clone());
(node, panel_item)
}
@@ -197,9 +208,12 @@ impl<B: Backend> PanelItem<B> {
panel_item_client::destroy_child(&node, id);
}
}
// make these stupid vectors u32 in the protocol somehow!!!!!!!1
impl<B: Backend> AspectIdentifier for PanelItem<B> {
impl_aspect_for_panel_item_aspect_id! {}
}
impl<B: Backend> Aspect for PanelItem<B> {
impl_aspect_for_panel_item_aspect! {}
}
#[allow(unused)]
impl<B: Backend> PanelItemAspect for PanelItem<B> {
#[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)."]
fn keyboard_keys(
fn keyboard_key(
node: Arc<Node>,
_calling_client: Arc<Client>,
surface: SurfaceId,
keymap_id: u64,
keys: Vec<i32>,
key: u32,
pressed: bool,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item
.backend()
.keyboard_keys(&surface, keymap_id, keys);
.keyboard_key(&surface, keymap_id, key, pressed);
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<()> {
super::acceptor_capture_item_flex(node, item)
}
@@ -435,22 +466,23 @@ impl<B: Backend> Drop for PanelItem<B> {
}
}
create_interface!(ItemInterface);
impl InterfaceAspect for ItemInterface {
impl InterfaceAspect for Interface {
#[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)
}
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
fn create_panel_item_acceptor(
_node: Arc<Node>,
node: Arc<Node>,
calling_client: Arc<Client>,
id: u64,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
node.add_aspect(PanelItemAcceptor);
create_item_acceptor_flex(
calling_client,
id,
@@ -460,4 +492,36 @@ impl InterfaceAspect for ItemInterface {
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 audio;
pub mod data;
pub mod drawable;
pub mod fields;
pub mod input;
@@ -10,13 +9,11 @@ pub mod spatial;
use self::alias::Alias;
use crate::core::client::Client;
use crate::core::error::{Result, ServerError};
use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
use color_eyre::eyre::{eyre, Result};
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use dashmap::DashMap;
use serde::{Serialize, de::DeserializeOwned};
use spatial::Spatial;
use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError;
@@ -24,6 +21,7 @@ use stardust_xr::schemas::flex::{deserialize, serialize};
use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::os::fd::OwnedFd;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
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!();
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>);
impl Drop for OwnedNode {
fn drop(&mut self) {
@@ -64,8 +80,6 @@ pub struct Node {
client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>,
local_signals: Mutex<FxHashMap<u64, Signal>>,
local_methods: Mutex<FxHashMap<u64, Method>>,
aliases: Registry<Alias>,
aspects: Aspects,
destroyable: bool,
@@ -87,26 +101,24 @@ impl Node {
client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(),
id,
local_signals: Default::default(),
local_methods: Default::default(),
aliases: Default::default(),
aspects: Default::default(),
destroyable,
};
<Node as OwnedAspect>::add_node_members(&node);
node.aspects.add(Owned);
node
}
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
Ok(self
.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
.ok_or(ServerError::NoClient)?
.scenegraph
.add_node(self))
}
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
Ok(OwnedNode(
self.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
.ok_or(ServerError::NoClient)?
.scenegraph
.add_node(self),
))
@@ -118,7 +130,8 @@ impl Node {
.global_transform()
.to_scale_rotation_translation()
.0
.length_squared() > 0.0
.length_squared()
> 0.0
} else {
true
}
@@ -146,60 +159,57 @@ impl Node {
// Ok(serialize(pid)?.into())
// }
pub fn add_local_signal(&self, id: u64, signal: Signal) {
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> {
pub fn add_aspect<A: AspectIdentifier>(&self, aspect: A) -> Arc<A> {
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)
}
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
pub fn get_aspect<A: AspectIdentifier>(&self) -> Result<Arc<A>> {
self.aspects.get()
}
pub fn send_local_signal(
self: Arc<Self>,
calling_client: Arc<Client>,
aspect_id: u64,
method: u64,
message: Message,
) -> Result<(), ScenegraphError> {
if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_signals.iter().any(|e| *e == method) {
return Err(ScenegraphError::SignalNotFound);
return Err(ScenegraphError::MemberNotFound);
}
alias
.original
.upgrade()
.ok_or(ScenegraphError::BrokenAlias)?
.send_local_signal(calling_client, method, message)
.send_local_signal(calling_client, aspect_id, method, message)
} else {
let signal = self
.local_signals
.lock()
.get(&method)
.cloned()
.ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
error: error.to_string(),
})
let aspect = self
.aspects
.0
.get(&aspect_id)
.ok_or(ScenegraphError::AspectNotFound)?
.clone();
aspect
.run_signal(calling_client, self.clone(), method, message)
.map_err(|error| ScenegraphError::MemberError {
error: error.to_string(),
})
}
}
pub fn execute_local_method(
self: Arc<Self>,
calling_client: Arc<Client>,
aspect_id: u64,
method: u64,
message: Message,
response: MethodResponseSender,
) {
if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_methods.iter().any(|e| *e == method) {
response.send(Err(ScenegraphError::MethodNotFound));
response.send(Err(ScenegraphError::MemberNotFound));
return;
}
let Some(alias) = alias.original.upgrade() else {
@@ -208,6 +218,7 @@ impl Node {
};
alias.execute_local_method(
calling_client,
aspect_id,
method,
Message {
data: message.data.clone(),
@@ -216,14 +227,19 @@ impl Node {
response,
)
} else {
let Some(method) = self.local_methods.lock().get(&method).cloned() else {
response.send(Err(ScenegraphError::MethodNotFound));
let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else {
response.send(Err(ScenegraphError::AspectNotFound));
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();
self.aliases
.get_valid_contents()
@@ -233,6 +249,7 @@ impl Node {
.for_each(|node| {
// Beware! file descriptors will not be sent to aliases!!!
let _ = node.send_remote_signal(
aspect_id,
method,
Message {
data: message.data.clone(),
@@ -241,12 +258,13 @@ impl Node {
);
});
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(())
}
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
&self,
aspect_id: u64,
method: u64,
input: S,
fds: Vec<OwnedFd>,
@@ -254,13 +272,13 @@ impl Node {
let message_sender_handle = self
.message_sender_handle
.as_ref()
.ok_or(eyre!("Messenger does not exist for this node"))?;
.ok_or(ServerError::NoMessenger)?;
let serialized = serialize(input)?;
let result = message_sender_handle
.method(self.id, method, &serialized, fds)?
.await
.map_err(|e| eyre!(e))?;
.method(self.id, aspect_id, method, &serialized, fds)
.await?
.map_err(ServerError::RemoteMethodError)?;
let (message, fds) = result.into_components();
let deserialized: D = deserialize(&message)?;
@@ -271,61 +289,62 @@ impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node")
.field("id", &self.id)
.field("local_signals", &self.local_signals.lock().keys())
.field("local_methods", &self.local_methods.lock().keys())
.field("destroyable", &self.destroyable)
.finish()
}
}
impl 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 {
fn drop(&mut self) {
// Debug breakpoint
}
}
pub trait AspectIdentifier: Aspect {
const ID: u64;
}
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)]
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
struct Aspects(DashMap<u64, Arc<dyn Aspect>>);
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);
self.add_raw(aspect.clone());
aspect
}
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
self.0.lock().insert(Self::type_key::<A>(), aspect);
fn add_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
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
.lock()
.get(&Self::type_key::<A>())
.and_then(|a| Arc::downcast(a.clone()).ok())
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
}
fn type_key<A: 'static>() -> TypeId {
TypeId::of::<A>()
.get(&A::ID)
// .cloned doesn't work for some reason
.map(|v| v.clone())
.map(|a| a.as_any())
.and_then(|a| Arc::downcast(a).ok())
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
}
}
impl Drop for Aspects {
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::Node;
use crate::core::client::Client;
use super::{Aspect, AspectIdentifier, Node};
use crate::bail;
use crate::core::client::{CLIENTS, Client};
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::session::connection_env;
use color_eyre::eyre::{bail, Result};
use glam::Mat4;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use tracing::info;
static ROOT_REGISTRY: Registry<Root> = Registry::new();
stardust_xr_server_codegen::codegen_root_protocol!();
pub struct Root {
@@ -23,18 +21,20 @@ pub struct Root {
impl Root {
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
let node = Node::from_id(client, 0, false);
<Self as RootAspect>::add_node_members(&node);
let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(&node, None, transform, false);
Ok(ROOT_REGISTRY.add(Root {
node,
let root_aspect = node.add_aspect(Root {
node: node.clone(),
connect_instant: Instant::now(),
}))
});
Ok(root_aspect)
}
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(
&root.node,
&FrameInfo {
@@ -54,6 +54,12 @@ impl Root {
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 {
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
let Some(state) = calling_client.state.get() else {
@@ -92,13 +98,8 @@ impl RootAspect for Root {
}
#[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(()));
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 super::alias::Alias;
use super::fields::{Field, FieldTrait};
use super::Aspect;
use super::{Aspect, AspectIdentifier};
use crate::bail;
use crate::core::client::Client;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::create_interface;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use color_eyre::eyre::{eyre, OptionExt, Result};
use glam::{vec3a, Mat4, Quat, Vec3};
use color_eyre::eyre::OptionExt;
use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
use std::ptr;
use std::sync::{Arc, Weak};
use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr};
use stereokit_rust::maths::Bounds;
stardust_xr_server_codegen::codegen_spatial_protocol!();
@@ -38,6 +38,12 @@ impl Transform {
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! {
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
@@ -52,7 +58,7 @@ pub struct Spatial {
transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
}
impl Spatial {
@@ -64,7 +70,7 @@ impl Spatial {
transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()),
children: Registry::new(),
bounding_box_calc: OnceCell::default(),
bounding_box_calc: OnceLock::default(),
})
}
pub fn add_to(
@@ -74,16 +80,14 @@ impl Spatial {
zoneable: bool,
) -> Arc<Spatial> {
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
<Spatial as SpatialAspect>::add_node_members(node);
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
}
if let Some(parent) = parent {
parent.children.add_raw(&spatial);
}
<Spatial as SpatialRefAspect>::add_node_members(node);
<Spatial as SpatialAspect>::add_node_members(node);
node.add_aspect_raw(spatial.clone());
node.add_aspect(SpatialRef);
spatial
}
@@ -205,7 +209,7 @@ impl Spatial {
.map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
bail!("Setting spatial parent would cause a loop");
}
self.set_parent(parent);
@@ -220,7 +224,7 @@ impl Spatial {
.map(|parent| self.is_ancestor_of((*parent).clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
bail!("Setting spatial parent would cause a loop");
}
self.set_local_transform(Spatial::space_to_space_matrix(
@@ -238,70 +242,14 @@ impl Spatial {
.upgrade()
.map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX)
.unwrap_or(f32::NEG_INFINITY)
}
}
impl AspectIdentifier for Spatial {
impl_aspect_for_spatial_aspect_id! {}
}
impl Aspect for Spatial {
const NAME: &'static str = "Spatial";
}
impl SpatialRefAspect for Spatial {
async fn get_local_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
async fn get_relative_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let mut bounds = Bounds {
center: center.into(),
dimensions: [0.0; 3].into(),
};
bounds.grown_box(
this_spatial.get_bounding_box(),
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
);
Ok(BoundingBox {
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
async fn get_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<Transform> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
Ok(Transform {
translation: Some(position.into()),
rotation: Some(rotation.into()),
scale: Some(scale.into()),
})
}
impl_aspect_for_spatial_aspect! {}
}
impl SpatialAspect for Spatial {
fn set_local_transform(
@@ -389,6 +337,73 @@ impl Drop for Spatial {
}
}
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(
node: Arc<Node>,
_calling_client: Arc<Client>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
async fn get_relative_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let mut bounds = Bounds {
center: center.into(),
dimensions: [0.0; 3].into(),
};
bounds.grown_box(
this_spatial.get_bounding_box(),
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
);
Ok(BoundingBox {
center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(),
})
}
async fn get_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<Transform> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
Ok(Transform {
translation: Some(position.into()),
rotation: Some(rotation.into()),
scale: Some(scale.into()),
})
}
}
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(transform.translation)
@@ -406,8 +421,7 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
pub struct SpatialInterface;
impl InterfaceAspect for SpatialInterface {
impl InterfaceAspect for Interface {
fn create_spatial(
_node: Arc<Node>,
calling_client: Arc<Client>,
@@ -445,7 +459,7 @@ impl InterfaceAspect for SpatialInterface {
calling_client: Arc<Client>,
uid: u64,
) -> Result<Arc<Node>> {
EXPORTED_SPATIALS
Ok(EXPORTED_SPATIALS
.lock()
.get(&uid)
.map(|s| {
@@ -457,8 +471,6 @@ impl InterfaceAspect for SpatialInterface {
)
.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::{
Spatial, ZoneAspect, SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO,
ZONEABLE_REGISTRY,
SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO, Spatial, ZONEABLE_REGISTRY,
ZoneAspect,
};
use crate::{
core::{client::Client, registry::Registry},
core::{client::Client, error::Result, registry::Registry},
nodes::{
alias::{get_original, Alias, AliasList},
Node,
alias::{Alias, AliasList, get_original},
fields::{Field, FieldTrait},
Aspect, Node,
},
};
use color_eyre::eyre::Result;
use glam::vec3a;
use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
let old_distance = spatial.zone_distance();
let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
if new_distance.abs() < old_distance.abs() {
release(spatial);
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
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);
if new_distance.abs() > old_distance.abs() {
return;
}
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) {
let Some(spatial_node) = spatial.node.upgrade() else {
@@ -73,7 +74,6 @@ impl Zone {
intersecting: AliasList::default(),
captured: AliasList::default(),
});
<Zone as ZoneAspect>::add_node_members(node);
node.add_aspect_raw(zone.clone());
zone
}
@@ -82,15 +82,17 @@ impl Zone {
let current_zoneables = Registry::new();
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());
if distance > 0.0 {
continue;
}
if let Some(zone) = zoneable.zone.lock().upgrade() {
let zoneable_distance = zone.field.distance(&zoneable, [0.0; 3].into());
if zoneable_distance < distance {
continue;
}
if distance < zoneable.zone_distance() {
continue;
}
current_zoneables.add_raw(&zoneable);
}
@@ -124,9 +126,6 @@ impl Zone {
Ok(())
}
}
impl Aspect for Zone {
const NAME: &'static str = "Zone";
}
impl ZoneAspect for Zone {
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let zone = node.get_aspect::<Zone>()?;

View File

@@ -1,14 +1,14 @@
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
fields::{FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
Node, OwnedNode,
fields::{FieldTrait, Ray},
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputMethod, Pointer},
spatial::Spatial,
},
};
use color_eyre::eyre::Result;
use glam::{vec3, Mat4};
use glam::{Mat4, vec3};
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use std::sync::Arc;

View File

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

View File

@@ -1,26 +1,29 @@
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
use crate::{
core::client::INTERNAL_CLIENT,
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,
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
input::{InputDataType, InputMethod, Pointer},
items::panel::KEYMAPS,
spatial::Spatial,
},
};
use color_eyre::eyre::Result;
use glam::{vec3, Mat4, Vec3};
use glam::{Mat4, Vec3, vec3};
use mint::Vector2;
use serde::{Deserialize, Serialize};
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 stereokit_rust::system::{Input, Key};
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat};
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
use tokio::task::JoinSet;
use tokio::time::{Duration, timeout};
use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
use zbus::{Connection, names::OwnedInterfaceName};
#[derive(Debug, Deserialize, Serialize)]
struct MouseEvent {
@@ -46,12 +49,14 @@ impl Default for MouseEvent {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct KeyboardEvent {
pub keyboard: (),
pub xkbv1: (),
pub keymap_id: u64,
pub keys: Vec<i32>,
#[zbus::proxy(
interface = "org.stardustxr.XKBv1",
default_service = "org.stardustxr.XKBv1"
)]
trait KeyboardHandler {
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)]
@@ -62,8 +67,6 @@ pub struct MousePointer {
pointer: Arc<InputMethod>,
capture_manager: CaptureManager,
mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent,
keyboard_sender: Arc<PulseSender>,
}
impl MousePointer {
pub fn new() -> Result<Self> {
@@ -83,29 +86,16 @@ impl MousePointer {
.unwrap(),
);
let keyboard_sender = PulseSender::add_to(
&node.0,
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
)
.unwrap();
Ok(MousePointer {
node,
spatial,
pointer,
capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(),
keyboard_datamap: KeyboardEvent {
keyboard: (),
xkbv1: (),
keymap_id: keymap.data().as_ffi(),
keys: vec![],
},
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 ray = mouse.get_ray();
@@ -123,7 +113,9 @@ impl MousePointer {
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
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_discrete: [0.0, mouse.scroll_change / 120.0].into(),
raw_input_events: vec![],
@@ -131,7 +123,7 @@ impl MousePointer {
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
}
self.target_pointer_input();
self.send_keyboard_input();
self.send_keyboard_input(dbus_connection, object_registry);
}
fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| {
@@ -150,64 +142,115 @@ impl MousePointer {
.set_new_capture(&self.pointer, distance_calculator);
self.capture_manager.apply_capture(&self.pointer);
if self.capture_manager.capture.is_some() {
if self.capture_manager.capture.upgrade().is_some() {
return;
}
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
self.pointer.set_handler_order(sorted_handlers.iter());
let mut handlers = get_sorted_handlers(&self.pointer, distance_calculator);
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) {
let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| {
let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(),
});
(rx, result)
})
.filter(|(_rx, result)| {
result.deepest_point_distance > 0.0 && result.min_distance < 0.05
})
.reduce(|(rx_a, result_a), (rx_b, result_b)| {
if result_a.deepest_point_distance < result_b.deepest_point_distance {
(rx_a, result_a)
} else {
(rx_b, result_b)
pub fn send_keyboard_input(
&mut self,
dbus_connection: &Connection,
object_registry: &ObjectRegistry,
) {
let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
// Spawn async task to handle keyboard input
tokio::spawn({
let keyboard_handlers = keyboard_handlers.clone();
let spatial = self.spatial.clone();
let keymap_id = self.keymap.data().as_ffi();
let dbus_connection = dbus_connection.clone();
async move {
let mut closest_handler = None;
let mut closest_distance = f32::MAX;
let mut join_set = JoinSet::new();
for handler in &keyboard_handlers {
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()
});
}
})
.map(|(rx, _)| rx);
while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
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 keys = (8_u32..254)
.map(|i| unsafe { std::mem::transmute(i) })
.filter_map(|k| Some((map_key(k)?, Input::key(k))))
.filter_map(|(i, k)| {
if k.is_just_active() {
Some(i as i32)
} else if k.is_just_inactive() {
Some(-(i as i32))
} else {
None
let result = field_ref.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: spatial.clone(),
});
if result.deepest_point_distance > 0.0
&& result.min_distance < 0.05
&& result.deepest_point_distance < closest_distance
{
closest_distance = result.deepest_point_distance;
closest_handler = Some(handler);
}
})
.collect();
}
self.keyboard_datamap.keys = keys;
if !self.keyboard_datamap.keys.is_empty() {
pulse_receiver_client::data(
&rx.node.upgrade().unwrap(),
&self.node.0,
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
)
.unwrap();
let Some(handler) = closest_handler else {
return;
};
let Ok(keyboard_handler) = handler
.to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
.await
else {
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::{
core::client::INTERNAL_CLIENT,
nodes::{
fields::{Field, FieldTrait},
input::{InputDataType, InputHandler, InputMethod, Tip, INPUT_HANDLER_REGISTRY},
spatial::Spatial,
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 glam::{Mat4, Vec2, Vec3};
@@ -40,18 +40,19 @@ pub struct SkController {
material: Material,
capture_manager: CaptureManager,
datamap: ControllerDatamap,
tracked: ObjectHandle<Tracked>,
}
impl SkController {
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
let (spatial, object_handle) = SpatialRef::create(
connection,
&("/org/stardustxr/Controller/".to_string()
+ match handed {
Handed::Left => "left",
_ => "right",
}),
);
let model = Model::copy(Model::from_memory(
Input::set_controller_model(handed, Some(Model::new()));
let path = "/org/stardustxr/Controller/".to_string()
+ match handed {
Handed::Left => "left",
_ => "right",
};
let (spatial, object_handle) = SpatialRef::create(connection, &path);
let tracked = Tracked::new(connection, &path);
let model = Model::copy(&Model::from_memory(
"cursor.glb",
include_bytes!("cursor.glb"),
None,
@@ -74,19 +75,28 @@ impl SkController {
material,
capture_manager: CaptureManager::default(),
datamap: Default::default(),
tracked,
})
}
pub fn update(&mut self, token: &MainThreadToken) {
let controller = Input::controller(self.handed);
let input_node = self.input.spatial.node().unwrap();
input_node.set_enabled(controller.tracked.is_active());
if input_node.enabled() {
let 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(
controller.aim.orientation.into(),
controller.aim.position.into(),
);
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)
} else {
Color128::new_rgb(0.0, 1.0, 0.75)
@@ -118,11 +128,12 @@ impl SkController {
.set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() {
if self.capture_manager.capture.upgrade().is_some() {
return;
}
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::nodes::fields::{Field, FieldTrait};
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
use crate::nodes::OwnedNode;
use crate::nodes::fields::{Field, FieldTrait};
use crate::nodes::input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler};
use crate::nodes::{
Node,
input::{Hand, InputMethod, Joint},
spatial::Spatial,
Node,
};
use crate::objects::{ObjectHandle, SpatialRef};
use crate::objects::{ObjectHandle, SpatialRef, Tracked};
use color_eyre::eyre::Result;
use glam::{Mat4, Quat, Vec3};
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use std::f32::INFINITY;
use std::sync::Arc;
use stereokit_rust::material::Material;
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
use stereokit_rust::util::Color128;
use zbus::Connection;
use super::{get_sorted_handlers, CaptureManager};
use super::{CaptureManager, get_sorted_handlers};
fn convert_joint(joint: HandJoint) -> Joint {
Joint {
@@ -44,6 +44,7 @@ pub struct SkHand {
input: Arc<InputMethod>,
capture_manager: CaptureManager,
datamap: HandDatamap,
tracked: ObjectHandle<Tracked>,
}
impl SkHand {
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
@@ -55,6 +56,14 @@ impl SkHand {
_ => "right",
} + "/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()?;
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
let hand = InputDataType::Hand(Hand {
@@ -63,19 +72,20 @@ impl SkHand {
});
let datamap = Datamap::from_typed(HandDatamap::default())?;
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
Input::hand_visible(handed, true);
Input::hand_visible(handed, false);
Ok(SkHand {
_node,
palm_spatial,
palm_object,
handed,
input,
tracked,
capture_manager: CaptureManager::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 real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
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)
&& 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.distal = convert_joint(sk_hand.fingers[0][3]);
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
@@ -122,15 +140,12 @@ impl SkHand {
hand.elbow = None;
self.draw(
token,
if self.capture_manager.capture.is_none() {
Color128::new_rgb(1.0, 1.0, 1.0)
} else {
Color128::new_rgb(0.0, 1.0, 0.75)
},
hand,
);
let hand_color = if self.capture_manager.capture.upgrade().is_none() {
Color128::new_rgb(1.0, 1.0, 1.0)
} else {
Color128::new_rgb(0.0, 1.0, 0.75)
};
material.color_tint(hand_color);
}
}
self.datamap.pinch_strength = sk_hand.pinch_activation;
@@ -159,72 +174,18 @@ impl SkHand {
.set_new_capture(&self.input, distance_calculator);
self.capture_manager.apply_capture(&self.input);
if self.capture_manager.capture.is_some() {
if self.capture_manager.capture.upgrade().is_some() {
return;
}
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) {
// thumb
Lines::add_list(
token,
&[
joint_to_line_point(&hand.thumb.tip, color),
joint_to_line_point(&hand.thumb.distal, color),
joint_to_line_point(&hand.thumb.proximal, color),
joint_to_line_point(&hand.thumb.metacarpal, color),
],
);
// index
Lines::add_list(
token,
&[
joint_to_line_point(&hand.index.tip, color),
joint_to_line_point(&hand.index.distal, color),
joint_to_line_point(&hand.index.intermediate, color),
joint_to_line_point(&hand.index.proximal, color),
joint_to_line_point(&hand.index.metacarpal, color),
],
);
// middle
Lines::add_list(
token,
&[
joint_to_line_point(&hand.middle.tip, color),
joint_to_line_point(&hand.middle.distal, color),
joint_to_line_point(&hand.middle.intermediate, color),
joint_to_line_point(&hand.middle.proximal, color),
joint_to_line_point(&hand.middle.metacarpal, color),
],
);
// ring
Lines::add_list(
token,
&[
joint_to_line_point(&hand.ring.tip, color),
joint_to_line_point(&hand.ring.distal, color),
joint_to_line_point(&hand.ring.intermediate, color),
joint_to_line_point(&hand.ring.proximal, color),
joint_to_line_point(&hand.ring.metacarpal, color),
],
);
// palm
Lines::add_list(
token,
&[
joint_to_line_point(&hand.wrist, color),
joint_to_line_point(&hand.thumb.metacarpal, color),
joint_to_line_point(&hand.index.metacarpal, color),
joint_to_line_point(&hand.middle.metacarpal, color),
joint_to_line_point(&hand.ring.metacarpal, color),
joint_to_line_point(&hand.little.metacarpal, color),
joint_to_line_point(&hand.wrist, color),
],
);
}
impl Drop for SkHand {
fn drop(&mut self) {
Input::hand_visible(self.handed, false);
}
}

View File

@@ -3,24 +3,29 @@
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
fields::{Field, Shape, EXPORTED_FIELDS},
spatial::{Spatial, EXPORTED_SPATIALS},
Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, Shape},
spatial::{EXPORTED_SPATIALS, Spatial},
},
};
use glam::{vec3, Mat4};
use glam::{Mat4, vec3};
use input::{
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
sk_hand::SkHand,
};
use play_space::PlaySpaceBounds;
use std::{marker::PhantomData, sync::Arc};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use std::{
marker::PhantomData,
sync::{Arc, atomic::Ordering},
};
use stereokit_rust::{
material::Material,
sk::{DisplayMode, MainThreadToken, Sk},
system::{Handed, Input, Key, World},
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 play_space;
@@ -45,6 +50,7 @@ pub struct ServerObjects {
connection: Connection,
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
hand_materials: [Material; 2],
inputs: Inputs,
disable_controllers: bool,
disable_hands: bool,
@@ -53,15 +59,13 @@ impl ServerObjects {
pub fn new(
connection: Connection,
sk: &Sk,
hand_materials: [Material; 2],
disable_controllers: bool,
disable_hands: bool,
) -> ServerObjects {
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
let play_space = (World::has_bounds()
&& World::get_bounds_size().x != 0.0
&& World::get_bounds_size().y != 0.0)
.then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
let play_space = Some(SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
if play_space.is_some() {
let dbus_connection = connection.clone();
tokio::task::spawn(async move {
@@ -106,13 +110,20 @@ impl ServerObjects {
connection,
hmd,
play_space,
hand_materials,
inputs,
disable_controllers,
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();
self.hmd
.0
@@ -132,6 +143,7 @@ impl ServerObjects {
));
}
#[allow(clippy::collapsible_if)]
if sk.get_active_display_mode() != DisplayMode::MixedReality {
if Input::key(Key::F6).is_just_inactive() {
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
@@ -142,12 +154,12 @@ impl ServerObjects {
// SkController::new(Handed::Right).unwrap(),
// ));
// }
if Input::key(Key::F8).is_just_inactive() {
self.inputs = Inputs::Hands {
left: SkHand::new(&self.connection, Handed::Left).unwrap(),
right: SkHand::new(&self.connection, Handed::Right).unwrap(),
};
}
// if Input::key(Key::F8).is_just_inactive() {
// self.inputs = Inputs::Hands {
// left: SkHand::new(&self.connection, Handed::Left).unwrap(),
// right: SkHand::new(&self.connection, Handed::Right).unwrap(),
// };
// }
}
match &mut self.inputs {
@@ -162,28 +174,38 @@ impl ServerObjects {
controller_left.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 {
hand_left.update(sk, token);
hand_right.update(sk, token);
hand_left.update(sk, token, &mut self.hand_materials[0]);
hand_right.update(sk, token, &mut self.hand_materials[1]);
}
if let Some(eye_pointer) = eye_pointer {
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)) => {
// left.update(token);
// right.update(token);
// }
Inputs::Hands { left, right } => {
left.update(sk, token);
right.update(sk, token);
left.update(sk, token, &mut self.hand_materials[0]);
right.update(sk, token, &mut self.hand_materials[1]);
}
}
}
}
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> {
fn drop(&mut self) {
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);
impl FieldRef {
pub fn create(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,35 +11,35 @@ mod xdg_shell;
use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{ensure, Result};
use once_cell::sync::OnceCell;
use color_eyre::eyre::{Result, ensure};
use parking_lot::Mutex;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::egl::EGLContext;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::{ImportDma, Renderer};
use smithay::output::Output;
use smithay::reexports::wayland_server::backend::ClientId;
use smithay::reexports::wayland_server::DisplayHandle;
use smithay::reexports::wayland_server::{Display, ListeningSocket};
use smithay::wayland::dmabuf;
use std::ffi::OsStr;
use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::unix::prelude::AsRawFd;
use smithay::{
backend::{
allocator::dmabuf::Dmabuf,
egl::EGLContext,
renderer::{ImportDma, Renderer, gles::GlesRenderer},
},
output::Output,
reexports::wayland_server::{Display, DisplayHandle, ListeningSocket},
wayland::dmabuf,
};
use std::{
ffi::c_void,
os::unix::{net::UnixListener, prelude::FromRawFd},
sync::Arc,
ffi::{OsStr, c_void},
os::fd::AsFd,
sync::{Arc, OnceLock},
};
use stereokit_rust::system::{Backend, BackendGraphics};
use tokio::io::unix::AsyncFdReadyGuard;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
io::unix::AsyncFd,
sync::{
Notify,
mpsc::{self, UnboundedReceiver},
},
task::AbortHandle,
};
use tracing::{debug_span, info, instrument};
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
pub static WAYLAND_DISPLAY: OnceLock<String> = OnceLock::new();
struct EGLRawHandles {
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 {
display: Arc<DisplayWrapper>,
pub socket_name: Option<String>,
join_handle: JoinHandle<Result<()>>,
flush_notify: Arc<Notify>,
client_listener: AbortHandle,
client_dispatcher: AbortHandle,
renderer: GlesRenderer,
output: Output,
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
@@ -114,7 +84,6 @@ impl Wayland {
let display_handle = display.handle();
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 output = wayland_state.lock().output.clone();
@@ -129,58 +98,78 @@ impl Wayland {
}
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 {
display,
socket_name,
join_handle,
flush_notify,
client_listener,
client_dispatcher,
renderer,
output,
dmabuf_rx,
})
}
fn start_loop(
display: Arc<DisplayWrapper>,
async fn client_listener_loop(
mut display_handle: DisplayHandle,
socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>,
) -> Result<JoinHandle<Result<()>>> {
let listen_async =
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
) -> Result<()> {
let async_fd = AsyncFd::new(socket.as_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 dispatch_poll_listener = UnownedFd(Some(AsyncFd::new(dispatch_poll_fd)?));
let stream = tokio::net::UnixStream::from_std(stream)?;
let pid = stream.peer_cred().ok().and_then(|c| c.pid());
let dh1 = display.handle();
let mut dh2 = dh1.clone();
// New client connected
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 {
let _socket = socket; // Keep the socket alive
loop {
tokio::select! {
acc = listen_async.accept() => { // New client connected
let (stream, _) = acc?;
let client_state = Arc::new(ClientState {
pid: stream.peer_cred().ok().and_then(|c| c.pid()),
id: OnceCell::new(),
compositor_state: Default::default(),
seat: state.lock().seat.clone(),
});
let _client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
}
e = dispatch_poll_listener.readable() => { // Dispatch
let mut guard = e?;
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
display.dispatch_clients(&mut state.lock())?;
display.flush_clients(None);
Ok(())
})?;
guard.clear_ready();
}
async fn dispatch_client_loop(
mut display: Display<WaylandState>,
flush_notify: Arc<Notify>,
state: Arc<Mutex<WaylandState>>,
) -> std::io::Result<()> {
loop {
let poll_fd = display.backend().poll_fd();
let async_fd = AsyncFd::new(poll_fd)?;
tokio::select! {
biased;
_ = async_fd.readable() => {
drop(async_fd);
let _span = debug_span!("Dispatch wayland event");
let _span = _span.enter();
let _ = display.dispatch_clients(&mut *state.lock());
let _ = display.flush_clients();
}
_ = flush_notify.notified() => {
drop(async_fd);
let _ = display.flush_clients();
},
}
})
}
}
#[instrument(level = "debug", name = "Wayland frame", skip(self))]
@@ -197,7 +186,7 @@ impl Wayland {
}
let _ = self.renderer.cleanup_texture_cache();
self.display.flush_clients(None);
self.flush_notify.notify_waiters();
}
pub fn frame_event(&self) {
@@ -214,6 +203,7 @@ impl Wayland {
}
impl Drop for Wayland {
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 crate::{
core::task,
nodes::{
data::KEYMAPS,
items::panel::{Backend, Geometry, PanelItem},
},
nodes::items::panel::{Backend, Geometry, KEYMAPS, PanelItem},
};
use mint::Vector2;
use parking_lot::Mutex;
@@ -14,12 +11,12 @@ use smithay::{
backend::input::{AxisRelativeDirection, ButtonState, KeyState},
delegate_seat,
input::{
keyboard::{FilterResult, LedState},
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent},
touch::{self, DownEvent, UpEvent},
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,
wayland::compositor,
};
@@ -41,6 +38,11 @@ impl SeatHandler for WaylandState {
CursorImageStatus::Surface(surface) => {
CoreSurface::add_to(&surface);
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>>() {
core_surface.set_material_offset(1);
}
@@ -106,6 +108,12 @@ impl SeatWrapper {
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) {
let pointer = self.seat.get_pointer().unwrap();
if pointer.current_focus() == Some(surface.clone()) {
@@ -203,7 +211,7 @@ impl SeatWrapper {
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 {
return;
};
@@ -226,20 +234,18 @@ impl SeatWrapper {
{
return;
}
for key in keys {
keyboard.input(
&mut state.lock(),
key.unsigned_abs(),
if key > 0 {
KeyState::Pressed
} else {
KeyState::Released
},
SERIAL_COUNTER.next_serial(),
0,
|_, _, _| FilterResult::Forward::<()>,
);
}
keyboard.input(
&mut state.lock(),
key.into(),
if pressed {
KeyState::Pressed
} else {
KeyState::Released
},
SERIAL_COUNTER.next_serial(),
0,
|_, _, _| FilterResult::Forward::<()>,
);
}
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {

View File

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

View File

@@ -9,20 +9,23 @@ use crate::{
items::camera::TexWrapper,
},
};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use smithay::{
backend::renderer::{
gles::{GlesRenderer, GlesTexture},
utils::{import_surface_tree, RendererSurfaceStateUserData},
Renderer, Texture,
gles::{GlesRenderer, GlesTexture},
utils::{RendererSurfaceStateUserData, import_surface_tree},
},
desktop::utils::send_frames_surface_tree,
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::{
material::{Material, Transparency},
shader::Shader,
@@ -44,8 +47,8 @@ impl Drop for CoreSurfaceData {
pub struct CoreSurface {
pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceCell<Mutex<TexWrapper>>,
sk_mat: OnceCell<Mutex<MaterialWrapper>>,
sk_tex: OnceLock<Mutex<TexWrapper>>,
sk_mat: OnceLock<Mutex<MaterialWrapper>>,
material_offset: Mutex<Delta<u32>>,
pub pending_material_applications: Registry<ModelPart>,
}
@@ -55,8 +58,8 @@ impl CoreSurface {
let core_surface = CORE_SURFACES.add(CoreSurface {
weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
sk_tex: OnceLock::new(),
sk_mat: OnceLock::new(),
material_offset: Mutex::new(Delta::new(0)),
pending_material_applications: Registry::new(),
});
@@ -92,7 +95,8 @@ impl CoreSurface {
});
// 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;
}
@@ -104,25 +108,11 @@ impl CoreSurface {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let mapped = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states.lock().unwrap().buffer().is_some()
})
.unwrap_or(false);
if !mapped {
return;
}
let mut mapped_data = self.mapped_data.lock();
let Some(smithay_tex) = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states
.lock()
.unwrap()
.texture::<GlesRenderer>(renderer.id())
.cloned()
let locked = surface_states.lock().unwrap();
locked.texture::<GlesRenderer>(renderer.id()).cloned()
})
.flatten()
else {
@@ -130,9 +120,11 @@ impl CoreSurface {
};
let Some(sk_tex) = self.sk_tex.get() else {
tracing::error!("No sk_tex found for surface");
return;
};
let Some(sk_mat) = self.sk_mat.get() else {
tracing::error!("No sk_mat found for surface");
return;
};
sk_tex
@@ -157,7 +149,7 @@ impl CoreSurface {
let new_mapped_data = CoreSurfaceData {
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) {

View File

@@ -1,30 +1,34 @@
use super::{
seat::{handle_cursor, SeatWrapper},
seat::{SeatWrapper, handle_cursor},
state::{ClientState, WaylandState},
surface::CoreSurface,
utils::*,
};
use crate::nodes::{
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
use crate::{
core::error::Result,
nodes::{
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 parking_lot::Mutex;
use rand::Rng;
use rustc_hash::FxHashMap;
use smithay::{
delegate_xdg_shell,
desktop::PopupKind,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::{ResizeEdge, State},
},
wayland_server::{
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
Resource,
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
},
},
utils::{Logical, Rectangle, Serial},
@@ -87,7 +91,6 @@ impl XdgShellHandler for WaylandState {
s.states.set(State::Maximized);
s.states.unset(State::Fullscreen);
});
toplevel.send_configure();
let initial_size = toplevel
.wl_surface()
@@ -222,14 +225,17 @@ impl XdgShellHandler for WaylandState {
};
panel_item.toplevel_title_changed(&title)
}
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);
popup.wl_surface().insert_data(SurfaceId::Child(id));
let Some(parent) = popup.get_parent_surface() else {
warn!("No parent surface found for popup");
return;
};
let _ = popup.send_configure();
CoreSurface::add_to(popup.wl_surface());
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 {
warn!("No panel item found for popup parent");
return;
};
let panel_item_weak = Arc::downgrade(&panel_item);
@@ -252,6 +259,7 @@ impl XdgShellHandler for WaylandState {
}
surf.insert_data(panel_item_weak.clone());
let Some(panel) = surface_panel_item(surf) else {
warn!("Failed to get panel item for popup surface");
return;
};
panel.backend.new_child(surf);
@@ -464,6 +472,7 @@ impl Backend for XdgBackend {
fn close_toplevel(&self) {
if let Some(toplevel) = self.toplevel.lock().clone() {
self.seat.unfocus_internal_state(toplevel.wl_surface());
toplevel.send_close();
}
}
@@ -515,11 +524,11 @@ impl Backend for XdgBackend {
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 {
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>) {