diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a58c914..8327f56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 @@ -27,12 +27,11 @@ 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 + 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 @@ -40,7 +39,7 @@ jobs: run: cargo appimage - name: Upload AppImage - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: appimage - path: '*.AppImage' \ No newline at end of file + path: "target/appimage/stardust-xr-server.AppImage" diff --git a/Cargo.lock b/Cargo.lock index f044a50..bb83e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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#1bc94e67cad6b69fa3c509598b07bc5085cc65a3" +source = "git+https://github.com/StardustXR/core.git?branch=dev#752e34817e045997c7bdca53cda6bb8a0055e902" 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#1bc94e67cad6b69fa3c509598b07bc5085cc65a3" +source = "git+https://github.com/StardustXR/core.git?branch=dev#752e34817e045997c7bdca53cda6bb8a0055e902" 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", @@ -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", @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index dd3a454..e494c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] @@ -14,6 +14,7 @@ members = ["codegen"] [workspace.dependencies.stardust-xr] git = "https://github.com/StardustXR/core.git" +branch = "dev" [[bin]] name = "stardust-xr-server" @@ -40,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" @@ -68,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 } @@ -81,34 +82,28 @@ 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 diff --git a/README.md b/README.md index c415d06..418c141 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,164 @@ -# Stardust XR Reference Server +# Stardust XR Server -This project is a usable Linux display server that reinvents human-computer interaction for all kinds of XR, from putting 2D/XR apps into various 3D shells for varying uses to SDF-based interaction. +Stardust XR is a display server for VR and AR headsets on Linux-based systems. [Stardust provides a 3D environment](https://www.youtube.com/watch?v=v2WblwbaLaA), where anything from 2D windows (including your existing apps!), to 3D apps built from objects, can exist together in physical space. -## Prerequisites -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) -## Build +## Core Dependencies +| **Dependency** | **Ubuntu/Debian** | **Arch Linux** | **Fedora** | +|-----------------------------|-------------------------------------------------------------------------------------------------|---------------------------------------------------|-------------------------------------------------------------| +| **Cargo** | `cargo` | `cargo` | `cargo` | +| **CMake** | `cmake` | `cmake` | `cmake` | +| **EGL+GLES 3.2** | `libegl1-mesa-dev`, `libgles2-mesa-dev` | `mesa` *(provides EGL/GLES libraries and headers)* | `mesa-libEGL-devel`, `mesa-libGLES-devel` | +| **GLX+Xlib** | `libx11-dev`, `libxfixes-dev`, `libxcb1-dev`, `libgl1-mesa-dev`, `libxkbcommon-dev` | `libx11`, `libxfixes`, `libxcb` *(and GLX via mesa)*| `libX11-devel`, `libXfixes-devel`, `libxcb-devel`, `mesa-libGL-devel` *(or equivalent)* | +| **fontconfig** | `libfontconfig1-dev` | `fontconfig` | `fontconfig-devel` | +| **dlopen** (glibc function) | Provided by `libc6-dev` (part of the core C library) | Provided by `glibc` *(included in base-devel)* | Provided by `glibc-devel` | +| **OpenXR Loader** | `libopenxr-loader1`, `libopenxr-dev`, `libopenxr1-monado` | `openxr` | `openxr-devel` | + +Command line installation of core & dynamic dependencies are provided below: +
+Ubuntu/Debian +

+  sudo apt-get update && sudo apt-get install -y \
+  build-essential \
+  cargo \
+  cmake \
+  libegl1-mesa-dev libgles2-mesa-dev \
+  libx11-dev libxfixes-dev libxcb1-dev libxau-dev libgl1-mesa-dev libxkbcommon-dev \
+  libfontconfig1-dev libfreetype6-dev libharfbuzz-dev libgraphite2-dev \
+  libc6-dev \
+  libopenxr-loader1 libopenxr-dev libopenxr1-monado libwayland-dev \
+  libjsoncpp-dev libdrm-dev libexpat1-dev libxcb-randr0-dev \
+  libxml2-dev libffi-dev libbz2-dev libpng-dev libbrotli-dev liblzma-dev libglib2.0-dev libpcre2-dev
+  
+
+ +
+Arch Linux +

+  sudo pacman -Syu --needed \
+  base-devel \
+  rust \
+  cmake \
+  mesa \
+  libx11 \
+  libxfixes \
+  libxcb \
+  libxkbcommon \
+  fontconfig \
+  freetype2 \
+  openxr \
+  jsoncpp \
+  libffi \
+  wayland \
+  expat \
+  libxml2 \
+  libxau \
+  bzip2 \
+  xz \
+  libpng \
+  brotli \
+  pcre2 \
+  glib2 \
+  libdrm
+  
+
+
+Fedora +

+sudo dnf group install development-tools && \
+sudo dnf install -y \
+  cargo \
+  cmake \
+  mesa-libEGL-devel \
+  mesa-libGLES-devel \
+  libX11-devel \
+  libXfixes-devel \
+  libxcb-devel \
+  libxkbcommon-devel \
+  fontconfig-devel \
+  freetype-devel \
+  harfbuzz-devel \
+  graphite2-devel \
+  openxr-devel \ 
+  wayland-devel \
+  jsoncpp-devel \
+  libdrm-devel \
+  expat-devel \
+  xcb-util-devel \
+  libxml2-devel \
+  libXau-devel \
+  bzip2-devel \
+  xz-devel \
+  libpng-devel \
+  brotli-devel \
+  pcre2-devel \
+  glib2-devel
+  
+
+ +## Installation + +More detailed instructions and walkthroughs are provided at https://www.stardustxr.org + +The [Terra Repository](https://terra.fyralabs.com/) is required, and comes pre-installed with [Ultramarine Linux](https://ultramarine-linux.org/). Other Fedora Editions and derivatives can directly install terra-release: + +```bash +sudo dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +For a full installation of the Stardust XR server *and* a selected group of clients, run: + +```bash +sudo dnf group install stardust-xr +``` + +## Manual Build +We've provided a manual installation script [here](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh) that clones and builds the Stardust XR server along with a number of other clients from their respective repositories, and provides a startup script for automatically launching some clients. + +After cloning the repository ```bash cargo build ``` -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 \) +**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 ) - -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` \ No newline at end of file +![controller_click](https://github.com/StardustXR/website/blob/main/static/img/controller_click.GIF) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 5ededbe..a050ad9 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -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::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) -> Arc { + self + } + #[allow(clippy::all)] + fn run_signal( + &self, + _calling_client: std::sync::Arc, + _node: std::sync::Arc, + _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, + _node: std::sync::Arc, + _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)> { - _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> + Send + 'static; + pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec)> { + 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> + 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) - }); + ::#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 = ::#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::::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::>>()?) + quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::>>()?) } ArgumentType::Map(v) => { let mapping = generate_argument_deserialize("a", v, false); - quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::>>()?) + quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::>>()?) } _ => 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::>>()?) + quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::>>()?) } ArgumentType::Map(v) => { let mapping = generate_argument_serialize("a", v, false); - quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::>>()?) + quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::>>()?) } _ => 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) } } + ArgumentType::Fd => { + quote!(&std::os::fd::OwnedFd) + } }; if optional { diff --git a/flake.nix b/flake.nix index b83676a..885aef8 100644 --- a/flake.nix +++ b/flake.nix @@ -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" + ''; + }; }; - }; }; }; } diff --git a/img/flatscreen_1.png b/img/flatscreen_1.png deleted file mode 100644 index d5eb672..0000000 Binary files a/img/flatscreen_1.png and /dev/null differ diff --git a/img/flatscreen_2.png b/img/flatscreen_2.png deleted file mode 100644 index a939b82..0000000 Binary files a/img/flatscreen_2.png and /dev/null differ diff --git a/img/flatscreen_3.png b/img/flatscreen_3.png deleted file mode 100644 index 31d19de..0000000 Binary files a/img/flatscreen_3.png and /dev/null differ diff --git a/img/workflow.png b/img/workflow.png new file mode 100644 index 0000000..d6256ab Binary files /dev/null and b/img/workflow.png differ diff --git a/img/xr_mode_windowed_blank.png b/img/xr_mode_windowed_blank.png deleted file mode 100644 index 3900c95..0000000 Binary files a/img/xr_mode_windowed_blank.png and /dev/null differ diff --git a/justfile b/justfile new file mode 100644 index 0000000..5807123 --- /dev/null +++ b/justfile @@ -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 diff --git a/src/core/client.rs b/src/core/client.rs index 82897d0..adf7e30 100644 --- a/src/core/client.rs +++ b/src/core/client.rs @@ -1,24 +1,29 @@ 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 std::{ + fmt::Debug, + fs, + iter::FromIterator, + path::PathBuf, + sync::{Arc, OnceLock}, +}; use tokio::{net::UnixStream, task::JoinHandle}; use tracing::info; @@ -29,16 +34,16 @@ lazy_static! { // 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), scenegraph: Default::default(), - root: OnceCell::new(), + root: OnceLock::new(), base_resource_prefixes: Default::default(), - state: OnceCell::default(), + state: OnceLock::default(), }); } @@ -59,16 +64,16 @@ pub struct Client { pub pid: Option, // env: Option>, exe: Option, - dispatch_join_handle: OnceCell>>, - flush_join_handle: OnceCell>>, - disconnect_status: OnceCell>, + dispatch_join_handle: OnceLock>>, + flush_join_handle: OnceLock>>, + disconnect_status: OnceLock>, id_counter: CounterU32, pub message_sender_handle: Option, pub scenegraph: Arc, - pub root: OnceCell>, + pub root: OnceLock>, pub base_resource_prefixes: Mutex>, - pub state: OnceCell, + pub state: OnceLock, } impl Client { pub fn from_connection(connection: UnixStream) -> Result> { @@ -95,16 +100,16 @@ impl Client { // 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_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 +117,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 +133,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!( @@ -148,8 +152,9 @@ impl Client { } }, ) + .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 +168,7 @@ impl Client { } }, ) + .unwrap() }); Ok(client) diff --git a/src/core/client_state.rs b/src/core/client_state.rs index aa9c9bf..a2aa9c4 100644 --- a/src/core/client_state.rs +++ b/src/core/client_state.rs @@ -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; diff --git a/src/core/destroy_queue.rs b/src/core/destroy_queue.rs index 5b07bf1..f6e9d29 100644 --- a/src/core/destroy_queue.rs +++ b/src/core/destroy_queue.rs @@ -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; -static MAIN_DESTROY_QUEUE: Lazy<( +static MAIN_DESTROY_QUEUE: LazyLock<( mpsc::UnboundedSender, Mutex>, -)> = Lazy::new(|| { +)> = LazyLock::new(|| { let (tx, rx) = unbounded_channel(); (tx, Mutex::new(rx)) }); diff --git a/src/core/error.rs b/src/core/error.rs new file mode 100644 index 0000000..15b315f --- /dev/null +++ b/src/core/error.rs @@ -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 = std::result::Result; + +#[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)*))); + } + }; +} diff --git a/src/core/idl_utils.rs b/src/core/idl_utils.rs deleted file mode 100644 index e84d53c..0000000 --- a/src/core/idl_utils.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[macro_export] -macro_rules! create_interface { - ($iface:ident) => { - pub fn create_interface(client: &Arc) -> Result<()> { - let node = Node::from_id(client, INTERFACE_NODE_ID, false); - <$iface as self::InterfaceAspect>::add_node_members(&node); - node.add_to_scenegraph()?; - Ok(()) - } - }; -} diff --git a/src/core/mod.rs b/src/core/mod.rs index 93931c2..594fe29 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -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; diff --git a/src/core/registry.rs b/src/core/registry.rs index 711d6f2..d1e5431 100644 --- a/src/core/registry.rs +++ b/src/core/registry.rs @@ -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(Mutex>>>); +pub struct Registry(MaybeLazy>>); impl Registry { pub const fn new() -> Self { - Registry(const_mutex(None)) - } - fn lock(&self) -> MappedMutexGuard>> { - 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 where @@ -24,30 +23,29 @@ impl Registry { t_arc } pub fn add_raw(&self, t: &Arc) { - 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, new: &Registry) -> (Vec>, Vec>) { - 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 Registry { (added, removed) } pub fn get_valid_contents(&self) -> Vec> { - self.lock() + self.0 .iter() - .filter_map(|pair| pair.1.upgrade()) + .filter_map(|pair| pair.value().upgrade()) .collect() } pub fn set(&self, other: &Registry) { - 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> { - 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) -> 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 Clone for Registry { fn clone(&self) -> Self { - Self(Mutex::new(self.0.lock().clone())) + Self(self.0.clone()) } } impl Default for Registry { @@ -109,6 +103,30 @@ impl Default for Registry { } } +#[derive(Debug)] +enum MaybeLazy { + Lazy(LazyLock), + NonLazy(T), +} +impl Clone for MaybeLazy { + fn clone(&self) -> Self { + match self { + MaybeLazy::Lazy(lazy_lock) => Self::NonLazy(lazy_lock.deref().clone()), + MaybeLazy::NonLazy(v) => Self::NonLazy(v.clone()), + } + } +} +impl Deref for MaybeLazy { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + MaybeLazy::Lazy(lazy_lock) => lazy_lock, + MaybeLazy::NonLazy(v) => v, + } + } +} + pub struct OwnedRegistry(Mutex>>>); impl OwnedRegistry { diff --git a/src/core/scenegraph.rs b/src/core/scenegraph.rs index 55c1d79..d51d4c4 100644 --- a/src/core/scenegraph.rs +++ b/src/core/scenegraph.rs @@ -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>, + pub(super) client: OnceLock>, nodes: Mutex>>, } @@ -59,26 +58,26 @@ impl MethodResponseSender { // ) { // let _ = self.0.send(map_method_return(result)); // } - pub fn wrap_sync color_eyre::eyre::Result>(self, f: F) { - self.send(f().map_err(|e| ScenegraphError::MethodError { + pub fn wrap_sync crate::core::error::Result>(self, f: F) { + self.send(f().map_err(|e| ScenegraphError::MemberError { error: e.to_string(), })) } pub fn wrap_async( self, - f: impl Future)>> + Send + 'static, + f: impl Future)>> + Send + 'static, ) { tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) }); } } fn map_method_return( - result: color_eyre::eyre::Result<(T, Vec)>, + result: Result<(T, Vec)>, ) -> Result<(Vec, Vec), 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( impl scenegraph::Scenegraph for Scenegraph { fn send_signal( &self, - node: u64, + node_id: u64, + aspect_id: u64, method: u64, data: &[u8], fds: Vec, ) -> 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, response: oneshot::Sender, Vec), 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(), diff --git a/src/main.rs b/src/main.rs index 43679c2..34032c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,16 +15,18 @@ use core::client::Client; use core::task; use directories::ProjectDirs; use objects::ServerObjects; -use once_cell::sync::OnceCell; use session::{launch_start, save_session}; +use stardust_xr::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}; @@ -32,9 +34,9 @@ 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, + /// 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 = OnceCell::new(); +static STARDUST_INSTANCE: OnceLock = OnceLock::new(); -// #[tokio::main] -#[tokio::main(flavor = "current_thread")] +// #[tokio::main(flavor = "current_thread")] +#[tokio::main] async fn main() { color_eyre::install().unwrap(); @@ -96,6 +102,20 @@ async fn main() { 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 +140,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 +151,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 +161,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 @@ -171,9 +207,11 @@ fn stereokit_loop( project_dirs: Option, 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,13 +239,21 @@ 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 { 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 { @@ -231,6 +277,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 +293,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( @@ -262,8 +309,6 @@ fn stereokit_loop( } info!("Cleanly shut down StereoKit"); - #[cfg(feature = "wayland")] - drop(wayland); } fn adaptive_sleep( diff --git a/src/nodes/alias.rs b/src/nodes/alias.rs index 9ba9386..6cb1036 100644 --- a/src/nodes/alias.rs +++ b/src/nodes/alias.rs @@ -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) -> Arc { + self + } + fn run_signal( + &self, + _calling_client: Arc, + _node: Arc, + _signal: u64, + _message: super::Message, + ) -> Result<(), stardust_xr::scenegraph::ScenegraphError> { + Ok(()) + } + fn run_method( + &self, + _calling_client: Arc, + _node: Arc, + _method: u64, + _message: super::Message, + _response: crate::core::scenegraph::MethodResponseSender, + ) { + } } pub fn get_original(node: Arc, stop_on_disabled: bool) -> Option> { @@ -106,7 +128,7 @@ impl AliasList { .into_iter() .find(move |node| links_to(node.clone(), original.clone())) } - pub fn get_from_aspect(&self, aspect: &A) -> Option> { + pub fn get_from_aspect(&self, aspect: &A) -> Option> { 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> { self.0.get_valid_contents() } - pub fn remove_aspect(&self, aspect: &A) { + pub fn remove_aspect(&self, aspect: &A) { self.0.retain(|node| { let Some(original) = get_original(node.clone(), false) else { return false; diff --git a/src/nodes/audio.rs b/src/nodes/audio.rs index c0c2a0f..dd74d29 100644 --- a/src/nodes/audio.rs +++ b/src/nodes/audio.rs @@ -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, + sk_sound: OnceLock, instance: Mutex>, stop: Mutex>, play: Mutex>, @@ -42,14 +40,13 @@ impl Sound { space: node.get_aspect::().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()); - ::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, _calling_client: Arc) -> 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, diff --git a/src/nodes/data.rs b/src/nodes/data.rs deleted file mode 100644 index 0fb2a6d..0000000 --- a/src/nodes/data.rs +++ /dev/null @@ -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> = 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 = Registry::new(); -pub static PULSE_RECEIVER_REGISTRY: Registry = Registry::new(); - -pub fn get_mask(datamap: &Datamap) -> Result> { - 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, - pub mask: Datamap, - aliases: AliasList, - field_aliases: AliasList, -} -impl PulseSender { - pub fn add_to(node: &Arc, mask: Datamap) -> Result> { - let sender = PulseSender { - node: Arc::downgrade(node), - mask, - aliases: AliasList::default(), - field_aliases: AliasList::default(), - }; - - // ::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::() - .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, - pub field: Arc, - pub mask: Datamap, -} -impl PulseReceiver { - pub fn add_to( - node: &Arc, - field: Arc, - mask: Datamap, - ) -> Result> { - let receiver = PulseReceiver { - node: Arc::downgrade(node), - field, - mask, - }; - let receiver = PULSE_RECEIVER_REGISTRY.add(receiver); - - ::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, - _calling_client: Arc, - sender: Arc, - data: Datamap, - ) -> Result<()> { - let this_receiver = node.get_aspect::().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, - calling_client: Arc, - id: u64, - parent: Arc, - transform: Transform, - mask: Datamap, - ) -> Result<()> { - get_mask(&mask)?; - let node = Node::from_id(&calling_client, id, true); - let parent = parent.get_aspect::()?; - 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, - calling_client: Arc, - id: u64, - parent: Arc, - transform: Transform, - field: Arc, - mask: Datamap, - ) -> Result<()> { - get_mask(&mask)?; - let node = Node::from_id(&calling_client, id, true); - let parent = parent.get_aspect::()?; - let transform = parse_transform(transform, true, true, false); - let field = field.get_aspect::()?; - - 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, - _calling_client: Arc, - keymap: String, - ) -> Result { - 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, - _calling_client: Arc, - keymap_id: u64, - ) -> Result { - 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()) - } -} diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index 1266755..d414e00 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -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::()?.clone(), data: Mutex::new(lines), }); - ::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, _calling_client: Arc, lines: Vec) -> Result<()> { let lines_aspect = node.get_aspect::()?; diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index 43b2c64..4a7ae9d 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -1,21 +1,17 @@ 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::core::{client::Client, error::Result, resource::get_resource_file}; use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO; -use crate::{ - core::{client::Client, resource::get_resource_file}, - create_interface, -}; -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}; @@ -28,12 +24,12 @@ pub fn draw(token: &MainThreadToken) { text::draw_all(token); if let Some(skytex) = QUEUED_SKYTEX.lock().take() { - if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) { + if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) { Renderer::skytex(skytex.tex); } } if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() { - if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) { + if let Ok(skylight) = SHCubemap::from_cubemap(skylight, true, 100) { Renderer::skylight(skylight.sh); } } @@ -43,13 +39,36 @@ static QUEUED_SKYLIGHT: Mutex> = Mutex::new(None); static QUEUED_SKYTEX: Mutex> = Mutex::new(None); stardust_xr_server_codegen::codegen_drawable_protocol!(); -create_interface!(DrawableInterface); -pub struct DrawableInterface; -impl InterfaceAspect for DrawableInterface { +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, calling_client: Arc, tex: ResourceID) -> Result<()> { let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")]) - .ok_or(eyre::eyre!("Could not find resource"))?; + .ok_or(eyre!("Could not find resource"))?; QUEUED_SKYTEX.lock().replace(resource_path); Ok(()) } @@ -60,7 +79,7 @@ impl InterfaceAspect for DrawableInterface { light: ResourceID, ) -> Result<()> { let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")]) - .ok_or(eyre::eyre!("Could not find resource"))?; + .ok_or(eyre!("Could not find resource"))?; QUEUED_SKYLIGHT.lock().replace(resource_path); Ok(()) } diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index 01c396f..3335abe 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -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(&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>); +struct MaterialRegistry(Mutex>>); impl MaterialRegistry { fn add_or_get(&self, material: Arc) -> Arc { - 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 = Lazy::new(MaterialRegistry::default); +static MATERIAL_REGISTRY: LazyLock = LazyLock::new(MaterialRegistry::default); static MODEL_REGISTRY: Registry = Registry::new(); -static HOLDOUT_MATERIAL: OnceCell> = OnceCell::new(); +static HOLDOUT_MATERIAL: OnceLock> = 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, model: Weak, + material: Mutex>>, pending_material_parameters: Mutex>, pending_material_replacement: Mutex>>, 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)), }); - ::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, _calling_client: Arc) -> Result<()> { @@ -309,7 +332,7 @@ impl ModelPartAspect for ModelPart { pub struct Model { space: Arc, _resource_id: ResourceID, - sk_model: OnceCell, + sk_model: OnceLock, parts: Mutex>>, } impl Model { @@ -324,10 +347,9 @@ impl Model { let model = Arc::new(Model { space: node.get_aspect::().unwrap().clone(), _resource_id: resource_id, - sk_model: OnceCell::new(), + sk_model: OnceLock::new(), parts: Mutex::new(Vec::default()), }); - ::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, id: u64, part_path: String, - ) -> color_eyre::eyre::Result<()> { + ) -> Result<()> { let model = node.get_aspect::()?; 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::>(); - 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(), diff --git a/src/nodes/drawable/shader_manipulation.rs b/src/nodes/drawable/shader_manipulation.rs deleted file mode 100644 index bfcbad0..0000000 --- a/src/nodes/drawable/shader_manipulation.rs +++ /dev/null @@ -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 { - 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("") - ); - - 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 { - 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("") - ); - - 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(()) -} diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs index 33a757f..282553e 100644 --- a/src/nodes/drawable/text.rs +++ b/src/nodes/drawable/text.rs @@ -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, font_path: Option, - style: OnceCell, + style: OnceLock, text: Mutex, data: Mutex, @@ -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), }); - ::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 { - 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, diff --git a/src/nodes/fields.rs b/src/nodes/fields.rs index aa93c85..dbef5cc 100644 --- a/src/nodes/fields.rs +++ b/src/nodes/fields.rs @@ -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 = Lazy::new(|| AliasInfo { +pub static FIELD_ALIAS_INFO: LazyLock = 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); - ::add_node_members(node); - ::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, _calling_client: Arc, shape: Shape) -> Result<()> { + let field = node.get_aspect::()?; + *field.shape.lock() = shape; + Ok(()) + } + + async fn export_field(node: Arc, _calling_client: Arc) -> Result { + 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, _calling_client: Arc, @@ -205,56 +255,14 @@ impl FieldRefAspect for Field { })) } } -impl FieldAspect for Field { - fn set_shape(node: Arc, _calling_client: Arc, shape: Shape) -> Result<()> { - let field = node.get_aspect::()?; - *field.shape.lock() = shape; - Ok(()) - } - async fn export_field(node: Arc, _calling_client: Arc) -> Result { - 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, calling_client: Arc, uid: u64, ) -> Result> { - 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( diff --git a/src/nodes/input/hand.rs b/src/nodes/input/hand.rs index 8512583..892c1a7 100644 --- a/src/nodes/input/hand.rs +++ b/src/nodes/input/hand.rs @@ -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 { diff --git a/src/nodes/input/handler.rs b/src/nodes/input/handler.rs index 59e7fef..7d2776a 100644 --- a/src/nodes/input/handler.rs +++ b/src/nodes/input/handler.rs @@ -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 { diff --git a/src/nodes/input/method.rs b/src/nodes/input/method.rs index d235050..39d5d4b 100644 --- a/src/nodes/input/method.rs +++ b/src/nodes/input/method.rs @@ -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}; @@ -45,13 +44,12 @@ impl InputMethod { internal_capture_requests: Registry::new(), captures: Registry::new(), }; - ::add_node_members(node); - ::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) } @@ -154,25 +152,6 @@ impl InputMethod { } } } -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, - _calling_client: Arc, - handler: Arc, - ) -> Result<()> { - let input_method = node.get_aspect::()?; - let input_handler = handler.get_aspect::()?; - - input_method - .internal_capture_requests - .add_raw(&input_handler); - Ok(()) - } -} impl InputMethodAspect for InputMethod { #[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."] fn set_input( @@ -231,3 +210,21 @@ impl Drop for InputMethod { INPUT_METHOD_REGISTRY.remove(self); } } + +pub struct InputMethodRef; +impl InputMethodRefAspect for InputMethodRef { + #[doc = "Have the input handler that this method reference came from capture the method for the next frame."] + fn request_capture( + node: Arc, + _calling_client: Arc, + handler: Arc, + ) -> Result<()> { + let input_method = node.get_aspect::()?; + let input_handler = handler.get_aspect::()?; + + input_method + .internal_capture_requests + .add_raw(&input_handler); + Ok(()) + } +} diff --git a/src/nodes/input/mod.rs b/src/nodes/input/mod.rs index 45e2955..60aaa08 100644 --- a/src/nodes/input/mod.rs +++ b/src/nodes/input/mod.rs @@ -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 = 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, 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, diff --git a/src/nodes/input/pointer.rs b/src/nodes/input/pointer.rs index d2b669e..4ce92f9 100644 --- a/src/nodes/input/pointer.rs +++ b/src/nodes/input/pointer.rs @@ -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 { diff --git a/src/nodes/items/camera.rs b/src/nodes/items/camera.rs index 55d65db..65bdc9d 100644 --- a/src/nodes/items/camera.rs +++ b/src/nodes/items/camera.rs @@ -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, frame_info: Mutex, - sk_tex: OnceCell, - sk_mat: OnceCell>, + sk_tex: OnceLock, + sk_mat: OnceLock>, applied_to: Registry, apply_to: Registry, } #[allow(unused)] impl CameraItem { pub fn add_to(node: &Arc, proj_matrix: Mat4, px_size: Vector2) { - Item::add_to( - node, - &ITEM_TYPE_INFO_CAMERA, - ItemType::Camera(CameraItem { - space: node.get_aspect::().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::().unwrap().clone(), + frame_info: Mutex::new(FrameInfo { + proj_matrix, + px_size, }), - ); - // ::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::().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::().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> { - 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, _calling_client: Arc, item: Arc) -> 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, @@ -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, calling_client: Arc) -> Result<()> { + fn register_camera_item_ui(node: Arc, calling_client: Arc) -> 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/`."] fn create_camera_item_acceptor( - _node: Arc, + node: Arc, calling_client: Arc, id: u64, parent: Arc, transform: Transform, field: Arc, ) -> Result<()> { + node.add_aspect(CameraItemAcceptor); create_item_acceptor_flex( calling_client, id, diff --git a/src/nodes/items/mod.rs b/src/nodes/items/mod.rs index e32fda8..a0255ae 100644 --- a/src/nodes/items/mod.rs +++ b/src/nodes/items/mod.rs @@ -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>, pub items: Registry, pub acceptors: Registry, + pub add_ui_aspect: fn(node: &Node), + pub add_acceptor_aspect: fn(node: &Node), pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc, acceptor_field: &Arc), } impl Hash for TypeInfo { @@ -81,7 +84,6 @@ impl Item { }; let item = type_info.items.add(item); - ::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, _calling_client: Arc) -> Result<()> { @@ -129,7 +134,7 @@ impl Drop for Item { } pub enum ItemType { - Camera(CameraItem), + Camera(Arc), Panel(Arc), } 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, item: Arc) -> Result<()> { Ok(()) } - -struct ItemInterface; -// create_interface!(ItemInterface); diff --git a/src/nodes/items/panel.rs b/src/nodes/items/panel.rs index 32827c0..78c5a8e 100644 --- a/src/nodes/items/panel.rs +++ b/src/nodes/items/panel.rs @@ -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> = 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>, ); - fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec); + fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool); fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2); fn touch_move(&self, id: u32, position: Vector2); @@ -115,7 +126,7 @@ impl PanelItem { &ITEM_TYPE_INFO_PANEL, ItemType::Panel(generic_panel_item), ); - ::add_node_members(&node); + node.add_aspect_raw(panel_item.clone()); (node, panel_item) } @@ -197,9 +208,12 @@ impl PanelItem { panel_item_client::destroy_child(&node, id); } } - -// make these stupid vectors u32 in the protocol somehow!!!!!!!1 - +impl AspectIdentifier for PanelItem { + impl_aspect_for_panel_item_aspect_id! {} +} +impl Aspect for PanelItem { + impl_aspect_for_panel_item_aspect! {} +} #[allow(unused)] impl PanelItemAspect for PanelItem { #[doc = "Apply the cursor as a material to a model."] @@ -341,19 +355,20 @@ impl PanelItemAspect for PanelItem { } #[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."] - fn keyboard_keys( + fn keyboard_key( node: Arc, _calling_client: Arc, surface: SurfaceId, keymap_id: u64, - keys: Vec, + 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 PanelItemAspect for PanelItem { } } -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, _calling_client: Arc, item: Arc) -> Result<()> { super::acceptor_capture_item_flex(node, item) } @@ -435,22 +466,23 @@ impl Drop for PanelItem { } } -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, calling_client: Arc) -> Result<()> { + fn register_panel_item_ui(node: Arc, calling_client: Arc) -> 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//acceptor/`."] fn create_panel_item_acceptor( - _node: Arc, + node: Arc, calling_client: Arc, id: u64, parent: Arc, transform: Transform, field: Arc, ) -> 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, + _calling_client: Arc, + keymap: String, + ) -> Result { + 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, + _calling_client: Arc, + keymap_id: u64, + ) -> Result { + 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()) + } } diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 1af5a76..3296613 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -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, Arc, Message) -> Result<()>; -pub type Method = fn(Arc, Arc, 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, _calling_client: Arc, enabled: bool) -> Result<()> { + node.set_enabled(enabled); + Ok(()) + } + + fn destroy(node: Arc, _calling_client: Arc) -> Result<()> { + if node.destroyable { + node.destroy(); + } + Ok(()) + } +} + pub struct OwnedNode(pub Arc); impl Drop for OwnedNode { fn drop(&mut self) { @@ -64,8 +80,6 @@ pub struct Node { client: Weak, message_sender_handle: Option, - local_signals: Mutex>, - local_methods: Mutex>, aliases: Registry, 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, }; - ::add_node_members(&node); + node.aspects.add(Owned); node } pub fn add_to_scenegraph(self) -> Result> { 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 { 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(&self, aspect: A) -> Arc { + pub fn add_aspect(&self, aspect: A) -> Arc { self.aspects.add(aspect) } - pub fn add_aspect_raw(&self, aspect: Arc) { + pub fn add_aspect_raw(&self, aspect: Arc) { self.aspects.add_raw(aspect) } - pub fn get_aspect(&self) -> Result> { + pub fn get_aspect(&self) -> Result> { self.aspects.get() } pub fn send_local_signal( self: Arc, calling_client: Arc, + aspect_id: u64, method: u64, message: Message, ) -> Result<(), ScenegraphError> { if let Ok(alias) = self.get_aspect::() { 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, calling_client: Arc, + aspect_id: u64, method: u64, message: Message, response: MethodResponseSender, ) { if let Ok(alias) = self.get_aspect::() { 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) -> Result<()> { + pub fn send_remote_signal( + &self, + aspect_id: u64, + method: u64, + message: impl Into, + ) -> 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( &self, + aspect_id: u64, method: u64, input: S, fds: Vec, @@ -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, _calling_client: Arc, enabled: bool) -> Result<()> { - node.set_enabled(enabled); - Ok(()) - } - - fn destroy(node: Arc, _calling_client: Arc) -> 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) -> Arc; + fn run_signal( + &self, + calling_client: Arc, + node: Arc, + signal: u64, + message: Message, + ) -> Result<(), stardust_xr::scenegraph::ScenegraphError>; + fn run_method( + &self, + calling_client: Arc, + node: Arc, + method: u64, + message: Message, + response: MethodResponseSender, + ); } #[derive(Default)] -struct Aspects(Mutex>>); - +struct Aspects(DashMap>); impl Aspects { - fn add(&self, t: A) -> Arc { + fn add(&self, t: A) -> Arc { let aspect = Arc::new(t); self.add_raw(aspect.clone()); aspect } - fn add_raw(&self, aspect: Arc) { - self.0.lock().insert(Self::type_key::(), aspect); + fn add_raw(&self, aspect: Arc) { + self.0.insert(A::ID, aspect); } - fn get(&self) -> Result> { + fn get(&self) -> Result> { self.0 - .lock() - .get(&Self::type_key::()) - .and_then(|a| Arc::downcast(a.clone()).ok()) - .ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase())) - } - - fn type_key() -> TypeId { - TypeId::of::() + .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::())) } } 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() } } diff --git a/src/nodes/root.rs b/src/nodes/root.rs index 558ee32..0f9417b 100644 --- a/src/nodes/root.rs +++ b/src/nodes/root.rs @@ -1,11 +1,12 @@ use super::spatial::Spatial; -use super::Node; +use super::{Aspect, AspectIdentifier, Node}; +use crate::bail; use crate::core::client::Client; use crate::core::client_state::ClientStateParsed; +use crate::core::error::Result; use crate::core::registry::Registry; 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; @@ -23,14 +24,14 @@ pub struct Root { impl Root { pub fn create(client: &Arc, transform: Mat4) -> Result> { let node = Node::from_id(client, 0, false); - ::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(), - })) + }); + ROOT_REGISTRY.add_raw(&root_aspect); + Ok(root_aspect) } pub fn send_frame_events(delta: f64) { @@ -54,6 +55,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, calling_client: Arc) -> Result { let Some(state) = calling_client.state.get() else { @@ -92,7 +99,7 @@ impl RootAspect for Root { } #[doc = "Cleanly disconnect from the server"] - fn disconnect(_node: Arc, calling_client: Arc) -> color_eyre::eyre::Result<()> { + fn disconnect(_node: Arc, calling_client: Arc) -> Result<()> { calling_client.disconnect(Ok(())); Ok(()) } diff --git a/src/nodes/spatial/mod.rs b/src/nodes/spatial/mod.rs index b7069b8..2fb0bef 100644 --- a/src/nodes/spatial/mod.rs +++ b/src/nodes/spatial/mod.rs @@ -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 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>> = Mutex::new(FxHashMap::default()); @@ -52,7 +58,7 @@ pub struct Spatial { transform: Mutex, zone: Mutex>, children: Registry, - pub bounding_box_calc: OnceCell Bounds>, + pub bounding_box_calc: OnceLock 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 { let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform); - ::add_node_members(node); if zoneable { ZONEABLE_REGISTRY.add_raw(&spatial); } if let Some(parent) = parent { parent.children.add_raw(&spatial); } - ::add_node_members(node); - ::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( @@ -241,67 +245,11 @@ impl Spatial { .unwrap_or(f32::MAX) } } -impl Aspect for Spatial { - const NAME: &'static str = "Spatial"; +impl AspectIdentifier for Spatial { + impl_aspect_for_spatial_aspect_id! {} } -impl SpatialRefAspect for Spatial { - async fn get_local_bounding_box( - node: Arc, - _calling_client: Arc, - ) -> Result { - let this_spatial = node.get_aspect::()?; - 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, - _calling_client: Arc, - relative_to: Arc, - ) -> Result { - let this_spatial = node.get_aspect::()?; - let relative_spatial = relative_to.get_aspect::()?; - 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, - _calling_client: Arc, - relative_to: Arc, - ) -> Result { - let this_spatial = node.get_aspect::()?; - let relative_spatial = relative_to.get_aspect::()?; - - 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 { + 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, + _calling_client: Arc, + ) -> Result { + let this_spatial = node.get_aspect::()?; + 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, + _calling_client: Arc, + relative_to: Arc, + ) -> Result { + let this_spatial = node.get_aspect::()?; + let relative_spatial = relative_to.get_aspect::()?; + 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, + _calling_client: Arc, + relative_to: Arc, + ) -> Result { + let this_spatial = node.get_aspect::()?; + let relative_spatial = relative_to.get_aspect::()?; + + 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, calling_client: Arc, @@ -445,7 +459,7 @@ impl InterfaceAspect for SpatialInterface { calling_client: Arc, uid: u64, ) -> Result> { - 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); diff --git a/src/nodes/spatial/zone.rs b/src/nodes/spatial/zone.rs index b4703f4..9f45391 100644 --- a/src/nodes/spatial/zone.rs +++ b/src/nodes/spatial/zone.rs @@ -1,16 +1,15 @@ 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}; @@ -73,7 +72,6 @@ impl Zone { intersecting: AliasList::default(), captured: AliasList::default(), }); - ::add_node_members(node); node.add_aspect_raw(zone.clone()); zone } @@ -124,9 +122,6 @@ impl Zone { Ok(()) } } -impl Aspect for Zone { - const NAME: &'static str = "Zone"; -} impl ZoneAspect for Zone { fn update(node: Arc, _calling_client: Arc) -> Result<()> { let zone = node.get_aspect::()?; diff --git a/src/objects/input/eye_pointer.rs b/src/objects/input/eye_pointer.rs index bae4316..04caf8b 100644 --- a/src/objects/input/eye_pointer.rs +++ b/src/objects/input/eye_pointer.rs @@ -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; diff --git a/src/objects/input/mod.rs b/src/objects/input/mod.rs index 65f0c48..252a3c0 100644 --- a/src/objects/input/mod.rs +++ b/src/objects/input/mod.rs @@ -5,7 +5,7 @@ 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; @@ -70,13 +70,13 @@ pub fn get_sorted_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) diff --git a/src/objects/input/mouse_pointer.rs b/src/objects/input/mouse_pointer.rs index 41858f3..a932cef 100644 --- a/src/objects/input/mouse_pointer.rs +++ b/src/objects/input/mouse_pointer.rs @@ -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, +#[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, capture_manager: CaptureManager, mouse_datamap: MouseEvent, - keyboard_datamap: KeyboardEvent, - keyboard_sender: Arc, } impl MousePointer { pub fn new() -> Result { @@ -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,7 @@ 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::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 +121,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| { @@ -158,56 +148,97 @@ impl MousePointer { self.pointer.set_handler_order(sorted_handlers.iter()); } - 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::(&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::() 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::(&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::(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, true).await; + } else if input_state.is_just_inactive() { + let _ = keyboard_handler.key_state(mapped_key, false).await; + } + } } - } + }); } } diff --git a/src/objects/input/sk_controller.rs b/src/objects/input/sk_controller.rs index 0fcb11f..f60e06a 100644 --- a/src/objects/input/sk_controller.rs +++ b/src/objects/input/sk_controller.rs @@ -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, } impl SkController { pub fn new(connection: &Connection, handed: Handed) -> Result { - 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,13 +75,22 @@ 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(), diff --git a/src/objects/input/sk_hand.rs b/src/objects/input/sk_hand.rs index 06b1bde..95e2adc 100644 --- a/src/objects/input/sk_hand.rs +++ b/src/objects/input/sk_hand.rs @@ -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, capture_manager: CaptureManager, datamap: HandDatamap, + tracked: ObjectHandle, } impl SkHand { pub fn new(connection: &Connection, handed: Handed) -> Result { @@ -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.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; @@ -166,65 +181,10 @@ impl SkHand { let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator); self.input.set_handler_order(sorted_handlers.iter()); } - - fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) { - // thumb - Lines::add_list( - token, - &[ - joint_to_line_point(&hand.thumb.tip, color), - joint_to_line_point(&hand.thumb.distal, color), - joint_to_line_point(&hand.thumb.proximal, color), - joint_to_line_point(&hand.thumb.metacarpal, color), - ], - ); - // index - Lines::add_list( - token, - &[ - joint_to_line_point(&hand.index.tip, color), - joint_to_line_point(&hand.index.distal, color), - joint_to_line_point(&hand.index.intermediate, color), - joint_to_line_point(&hand.index.proximal, color), - joint_to_line_point(&hand.index.metacarpal, color), - ], - ); - // middle - Lines::add_list( - token, - &[ - joint_to_line_point(&hand.middle.tip, color), - joint_to_line_point(&hand.middle.distal, color), - joint_to_line_point(&hand.middle.intermediate, color), - joint_to_line_point(&hand.middle.proximal, color), - joint_to_line_point(&hand.middle.metacarpal, color), - ], - ); - // ring - Lines::add_list( - token, - &[ - joint_to_line_point(&hand.ring.tip, color), - joint_to_line_point(&hand.ring.distal, color), - joint_to_line_point(&hand.ring.intermediate, color), - joint_to_line_point(&hand.ring.proximal, color), - joint_to_line_point(&hand.ring.metacarpal, color), - ], - ); - - // palm - Lines::add_list( - token, - &[ - joint_to_line_point(&hand.wrist, color), - joint_to_line_point(&hand.thumb.metacarpal, color), - joint_to_line_point(&hand.index.metacarpal, color), - joint_to_line_point(&hand.middle.metacarpal, color), - joint_to_line_point(&hand.ring.metacarpal, color), - joint_to_line_point(&hand.little.metacarpal, color), - joint_to_line_point(&hand.wrist, color), - ], - ); +} +impl Drop for SkHand { + fn drop(&mut self) { + Input::hand_visible(self.handed, false); } } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 4e65854..b848b8e 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -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, ObjectHandle), play_space: Option<(Arc, ObjectHandle)>, + 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(Connection, OwnedObjectPath, PhantomData); + +impl Clone for ObjectHandle { + fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone(), PhantomData) + } +} impl Drop for ObjectHandle { 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 { + 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 { + 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( diff --git a/src/objects/play_space.rs b/src/objects/play_space.rs index 498ceb4..c392c9b 100644 --- a/src/objects/play_space.rs +++ b/src/objects/play_space.rs @@ -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![] + } } } diff --git a/src/session.rs b/src/session.rs index 8fef44d..144140d 100644 --- a/src/session.rs +++ b/src/session.rs @@ -104,11 +104,7 @@ pub fn connection_env() -> FxHashMap { #[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 } diff --git a/src/wayland/compositor.rs b/src/wayland/compositor.rs index 1527bbc..c57d12b 100644 --- a/src/wayland/compositor.rs +++ b/src/wayland/compositor.rs @@ -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::(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::() { - count = stored_count.fetch_add(1, Ordering::Relaxed); - } - } - data.data_map.get::>().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::() 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::(|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); } } diff --git a/src/wayland/data_device.rs b/src/wayland/data_device.rs index f10840e..5af9078 100644 --- a/src/wayland/data_device.rs +++ b/src/wayland/data_device.rs @@ -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; diff --git a/src/wayland/decoration.rs b/src/wayland/decoration.rs index 92be6c5..4074ca2 100644 --- a/src/wayland/decoration.rs +++ b/src/wayland/decoration.rs @@ -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}, diff --git a/src/wayland/drm.rs b/src/wayland/drm.rs index 65a4c3a..8b9a4f3 100644 --- a/src/wayland/drm.rs +++ b/src/wayland/drm.rs @@ -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, diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index f129787..656709a 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -11,21 +11,21 @@ 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::backend::ClientId; 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 std::sync::OnceLock; use std::{ ffi::c_void, os::unix::{net::UnixListener, prelude::FromRawFd}, @@ -39,7 +39,7 @@ use tokio::{ }; use tracing::{debug_span, info, instrument}; -pub static WAYLAND_DISPLAY: OnceCell = OnceCell::new(); +pub static WAYLAND_DISPLAY: OnceLock = OnceLock::new(); struct EGLRawHandles { display: *const c_void, @@ -87,13 +87,12 @@ impl UnownedFd { } impl Drop for UnownedFd { fn drop(&mut self) { - self.0.take().unwrap().into_inner().into_raw_fd(); + let _ = self.0.take().unwrap().into_inner().into_raw_fd(); } } pub struct Wayland { display: Arc, - pub socket_name: Option, join_handle: JoinHandle>, renderer: GlesRenderer, output: Output, @@ -133,7 +132,6 @@ impl Wayland { Ok(Wayland { display, - socket_name, join_handle, renderer, output, @@ -163,7 +161,7 @@ impl Wayland { let (stream, _) = acc?; let client_state = Arc::new(ClientState { pid: stream.peer_cred().ok().and_then(|c| c.pid()), - id: OnceCell::new(), + id: OnceLock::new(), compositor_state: Default::default(), seat: state.lock().seat.clone(), }); diff --git a/src/wayland/seat.rs b/src/wayland/seat.rs index ddc5baf..bbe3d6a 100644 --- a/src/wayland/seat.rs +++ b/src/wayland/seat.rs @@ -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::{ + Seat, SeatHandler, keyboard::{FilterResult, LedState}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent}, touch::{self, DownEvent, UpEvent}, - Seat, SeatHandler, }, - reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak as WlWeak}, + reexports::wayland_server::{Resource, Weak as WlWeak, protocol::wl_surface::WlSurface}, utils::SERIAL_COUNTER, wayland::compositor, }; @@ -203,7 +200,7 @@ impl SeatWrapper { pointer.frame(&mut state); } - pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec) { + 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 +223,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) { diff --git a/src/wayland/state.rs b/src/wayland/state.rs index aaebb01..711e32c 100644 --- a/src/wayland/state.rs +++ b/src/wayland/state.rs @@ -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}, + 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}, @@ -41,13 +41,13 @@ use smithay::{ shm::{ShmHandler, ShmState}, }, }; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use tokio::sync::mpsc::UnboundedSender; use tracing::{info, warn}; pub struct ClientState { pub pid: Option, - pub id: OnceCell, + pub id: OnceLock, pub compositor_state: CompositorClientState, pub seat: Arc, } @@ -76,6 +76,7 @@ pub struct WaylandState { pub seat_state: SeatState, pub seat: Arc, pub xdg_shell: XdgShellState, + pub popup_manager: PopupManager, pub output: Output, } @@ -158,6 +159,7 @@ impl WaylandState { output.set_preferred(mode); let mut xdg_shell = XdgShellState::new::(&display_handle); + let popup_manager = PopupManager::default(); let mut capabilities = WmCapabilitySet::default(); capabilities.set(WmCapabilities::Maximize); capabilities.set(WmCapabilities::Fullscreen); @@ -182,6 +184,7 @@ impl WaylandState { seat_state, seat: Arc::new(SeatWrapper::new(weak.clone(), seat)), xdg_shell, + popup_manager, output, }) }) diff --git a/src/wayland/surface.rs b/src/wayland/surface.rs index 5044dc8..e4877ae 100644 --- a/src/wayland/surface.rs +++ b/src/wayland/surface.rs @@ -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, mapped_data: Mutex>, - sk_tex: OnceCell>, - sk_mat: OnceCell>, + sk_tex: OnceLock>, + sk_mat: OnceLock>, material_offset: Mutex>, pub pending_material_applications: Registry, } @@ -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::(|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::(|surface_states| { - surface_states - .lock() - .unwrap() - .texture::(renderer.id()) - .cloned() + let locked = surface_states.lock().unwrap(); + locked.texture::(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) { diff --git a/src/wayland/xdg_shell.rs b/src/wayland/xdg_shell.rs index 8d2b83b..e50561b 100644 --- a/src/wayland/xdg_shell.rs +++ b/src/wayland/xdg_shell.rs @@ -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); @@ -515,11 +523,11 @@ impl Backend for XdgBackend { self.seat.pointer_scroll(scroll_distance, scroll_steps) } - fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec) { + 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) {