Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf89b73e8f | ||
|
|
2e252279bb | ||
|
|
9cf43ec535 | ||
|
|
f15578f7df | ||
|
|
f63ca4a25b | ||
|
|
89741508e3 | ||
|
|
81be807749 | ||
|
|
fcdb8a7edf | ||
|
|
90ce185f29 | ||
|
|
d6353035ae | ||
|
|
ceb1b23264 | ||
|
|
199e6f70b3 | ||
|
|
641db4face | ||
|
|
80d292b511 | ||
|
|
7fbcc92d02 | ||
|
|
de46726d01 | ||
|
|
6efa3a909e | ||
|
|
ea0f174da7 | ||
|
|
444146fa21 | ||
|
|
a7930760e8 | ||
|
|
668c32f583 | ||
|
|
927e1c48e2 | ||
|
|
8cc20e054c | ||
|
|
b12b171b53 | ||
|
|
0e61d51072 | ||
|
|
e61c04960e | ||
|
|
5dc82be1a3 | ||
|
|
6861b92972 | ||
|
|
f68f350cd2 | ||
|
|
2820415373 | ||
|
|
f721a57604 | ||
|
|
fb4149eaa7 | ||
|
|
d3746ef787 | ||
|
|
9d4b4bee4d | ||
|
|
5390b0effb | ||
|
|
13da4c8d60 | ||
|
|
1740d55f9c | ||
|
|
52d5e97de6 | ||
|
|
633df045d4 | ||
|
|
415bf5bb04 | ||
|
|
4e2d4a15c9 | ||
|
|
ef0142183d | ||
|
|
e5dfd9d3df | ||
|
|
6773fe2cf3 | ||
|
|
5a6e7e02ca | ||
|
|
c5d8ec2ef1 | ||
|
|
a31781146e | ||
|
|
cb9368cb8e | ||
|
|
629c05e507 | ||
|
|
9123153bf3 | ||
|
|
f3dc632ffc | ||
|
|
c369100d8a | ||
|
|
e10d40ef5e | ||
|
|
d6ca367187 | ||
|
|
88ac8a8b86 | ||
|
|
70fef89e2d | ||
|
|
4d79a59b20 | ||
|
|
c776c1b712 | ||
|
|
d4de15e0b3 | ||
|
|
9d220ec235 | ||
|
|
09c6c010e2 | ||
|
|
c9e185e9f3 | ||
|
|
4737149c85 | ||
|
|
648451b47e | ||
|
|
a9ef2d6f4b | ||
|
|
d6ffcadd76 | ||
|
|
448b7489e8 | ||
|
|
622cf60a65 | ||
|
|
1ab11f1660 | ||
|
|
9654e6cc59 | ||
|
|
44d177858f | ||
|
|
be41f11b83 | ||
|
|
dd2bffc2b1 | ||
|
|
d2ef508607 | ||
|
|
0cc7c7bc24 | ||
|
|
8d65e304cb | ||
|
|
b0dbccbd18 | ||
|
|
a823fbfb57 | ||
|
|
4a864e6519 | ||
|
|
e23d847449 | ||
|
|
8ba199f053 | ||
|
|
23925b4475 | ||
|
|
7ea0220f33 | ||
|
|
969e4de882 | ||
|
|
e5acb3013f | ||
|
|
3d57bed1c0 | ||
|
|
45839ebf60 | ||
|
|
0bb5b53e02 | ||
|
|
4f966b6d71 | ||
|
|
2687a393b5 | ||
|
|
5c605932ef | ||
|
|
bccdc8221e | ||
|
|
bddf17bbef | ||
|
|
e8511e8759 | ||
|
|
85296f538b | ||
|
|
f8ff80b781 | ||
|
|
932fef87f5 | ||
|
|
742780e34e | ||
|
|
41ede661f7 | ||
|
|
8d85460803 | ||
|
|
ac71581db8 | ||
|
|
2f894c4058 | ||
|
|
2b97c98a6e | ||
|
|
98d9f491ba | ||
|
|
16d710e106 | ||
|
|
9ad202e778 | ||
|
|
b3747d623c | ||
|
|
d5ff9281e6 | ||
|
|
18ebd8c522 | ||
|
|
411f71c217 | ||
|
|
3027ae20a9 | ||
|
|
fbce321426 | ||
|
|
74bc3a306e | ||
|
|
a950ad59f1 | ||
|
|
cf840da444 | ||
|
|
173fba35fa | ||
|
|
97fbbec0fe | ||
|
|
400f3a23bf | ||
|
|
1ad3336b6f | ||
|
|
8e9956abe1 | ||
|
|
6ca93ea24c | ||
|
|
49810e8fd1 | ||
|
|
afd0946558 | ||
|
|
fd31d0cd99 | ||
|
|
2f380da62f | ||
|
|
1c6971cd11 | ||
|
|
da4cf084d2 | ||
|
|
ca95ed5461 | ||
|
|
1b06cb6952 | ||
|
|
df89c826bb | ||
|
|
21f7f66440 | ||
|
|
3f1bad18c8 | ||
|
|
0c190cc833 | ||
|
|
5f0df8e7c1 | ||
|
|
d715f2f9ed | ||
|
|
d7fa4e62b8 | ||
|
|
568ebb0060 | ||
|
|
42efc67625 | ||
|
|
dd4b0097a1 | ||
|
|
a483cdbc7d | ||
|
|
84a7546442 | ||
|
|
dd43f238ff | ||
|
|
4f057358c8 | ||
|
|
e20971aef7 | ||
|
|
eb0d3c5bcf | ||
|
|
a18222e3df | ||
|
|
93ca932da9 | ||
|
|
c512b2fef5 | ||
|
|
f53c684377 | ||
|
|
3552166207 | ||
|
|
0b6eb147c5 | ||
|
|
1833ed50f3 | ||
|
|
6cdbfb3bad | ||
|
|
a5e0cb19c9 | ||
|
|
519ab94312 | ||
|
|
f2a8c0ed13 | ||
|
|
b3998f315d | ||
|
|
40bcd61b98 | ||
|
|
7a4d557c61 | ||
|
|
303b3f3ca2 | ||
|
|
ac5e949614 | ||
|
|
60baabb850 | ||
|
|
248e48fd8e | ||
|
|
b9baee7e5f | ||
|
|
3598ffdbb1 | ||
|
|
c171d9e6db | ||
|
|
d7a607a663 | ||
|
|
03ccf9127d | ||
|
|
6a3024657f | ||
|
|
a0058fcc2e | ||
|
|
410cc13c4f | ||
|
|
bc259dbe01 | ||
|
|
3730e20248 | ||
|
|
1be413065d | ||
|
|
2721c20c8b | ||
|
|
80130f6ffd | ||
|
|
8da778eaba | ||
|
|
3c708d1aaf | ||
|
|
1ae1bef3c1 | ||
|
|
7fd0c1fddb | ||
|
|
fd9957b784 | ||
|
|
57da02dbad | ||
|
|
83a5b36ddc | ||
|
|
8c36d73775 | ||
|
|
8396b98f67 | ||
|
|
46d989ce7f | ||
|
|
75ac570486 | ||
|
|
242def9d06 | ||
|
|
959f32009b | ||
|
|
57796c217d | ||
|
|
cea3390e36 | ||
|
|
a756e80064 | ||
|
|
1f61d32877 | ||
|
|
da7e2c5e6e | ||
|
|
fd0940bfe9 | ||
|
|
2b4a495c07 | ||
|
|
cffb968d2e | ||
|
|
201ab3aee8 | ||
|
|
cfa3584dda | ||
|
|
f19ba93958 | ||
|
|
09a2572c3b | ||
|
|
c6316b4e8b | ||
|
|
9cd900b23f | ||
|
|
a6d30cb366 | ||
|
|
3e94a3f62a | ||
|
|
060f8264ff | ||
|
|
b7b3907647 | ||
|
|
1550555df1 | ||
|
|
c42a29a034 | ||
|
|
621bf6b82a |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
||||
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [technobaboo]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
46
.github/workflows/build.yml
vendored
Normal file
46
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build_and_package:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- 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
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build server
|
||||
run: cargo build --release
|
||||
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
||||
chmod +x /usr/local/bin/appimagetool; \
|
||||
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
||||
- name: Install cargo-appimage
|
||||
run: cargo install cargo-appimage
|
||||
|
||||
- name: Generate AppImage
|
||||
run: cargo appimage
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: appimage
|
||||
path: '*.AppImage'
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -2,11 +2,13 @@
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
.vscode/
|
||||
|
||||
# Ignore build results from Nix
|
||||
*result*
|
||||
|
||||
/libs/
|
||||
*.AppImage
|
||||
2742
Cargo.lock
generated
Normal file
2742
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
95
Cargo.toml
95
Cargo.toml
@@ -1,51 +1,92 @@
|
||||
[package]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
name = "stardust-xr-server"
|
||||
version = "0.10.1"
|
||||
version = "0.42.1"
|
||||
authors = ["Nova King <technobaboo@proton.me>"]
|
||||
description = "Stardust XR reference display server"
|
||||
license = "GPLv2"
|
||||
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
||||
homepage = "https://stardustxr.org"
|
||||
|
||||
[[bin]]
|
||||
name = "stardust-xr-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["wayland"]
|
||||
wayland = ["dep:smithay", "dep:xkbcommon"]
|
||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||
profile_app = ["dep:tracing-chrome"]
|
||||
|
||||
[package.metadata.appimage]
|
||||
auto_link = true
|
||||
auto_link_exclude_list = [
|
||||
"libc*",
|
||||
"libdl*",
|
||||
"libpthread*",
|
||||
"ld-linux*",
|
||||
"libGL*",
|
||||
"libEGL*",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
clap = { version = "4.0.8", features = ["derive"] }
|
||||
ctrlc = "3.2.2"
|
||||
dashmap = "5.3.4"
|
||||
flatbuffers = "2.1.2"
|
||||
flexbuffers = "2.0.0"
|
||||
glam = {version = "0.21.3", features = ["mint"]}
|
||||
color-eyre = { version = "0.6.2", default-features = false }
|
||||
clap = { version = "4.2.4", features = ["derive"] }
|
||||
dashmap = "5.4.0"
|
||||
glam = { version = "0.23.0", features = ["mint"] }
|
||||
lazy_static = "1.4.0"
|
||||
mint = "0.5.9"
|
||||
nanoid = "0.4.0"
|
||||
once_cell = "1.12.0"
|
||||
once_cell = "1.17.1"
|
||||
parking_lot = "0.12.1"
|
||||
portable-atomic = {version = "0.3.0", features = ["float", "std"]}
|
||||
rccell = "0.1.3"
|
||||
portable-atomic = { version = "1.2.0", features = ["float", "std"] }
|
||||
rustc-hash = "1.1.0"
|
||||
slab = "0.4.6"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0.31"
|
||||
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal"] }
|
||||
send_wrapper = "0.6.0"
|
||||
prisma = "0.1.1"
|
||||
slog = "2.7.0"
|
||||
slog-stdlog = "4.1.1"
|
||||
xkbcommon = { version = "0.5.0", default-features = false }
|
||||
stardust-xr = "0.5.2"
|
||||
wayland-backend = "=0.1.0-beta.9"
|
||||
wayland-scanner = "=0.30.0-beta.9"
|
||||
directories = "4.0.1"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
xkbcommon = { version = "0.5.0", default-features = false, optional = true }
|
||||
stardust-xr = "0.11.4"
|
||||
directories = "5.0.0"
|
||||
serde = { version = "1.0.160", features = ["derive"] }
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
global_counter = "0.2.2"
|
||||
rand = "0.8.5"
|
||||
atty = "0.2.14"
|
||||
|
||||
[dependencies.stereokit]
|
||||
default-features = false
|
||||
features = ["linux-egl"]
|
||||
version = "0.5.0"
|
||||
version = "0.16.7"
|
||||
|
||||
[dependencies.smithay]
|
||||
git = "https://github.com/technobaboo/smithay.git"
|
||||
branch = "feature/public_input"
|
||||
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
|
||||
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
|
||||
# path = "../smithay"
|
||||
default-features = false
|
||||
features = ["desktop", "renderer_gl", "wayland_frontend"]
|
||||
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
|
||||
version = "*"
|
||||
optional = true
|
||||
|
||||
[dependencies.console-subscriber]
|
||||
version = "0.1.8"
|
||||
optional = true
|
||||
|
||||
[dependencies.tracing-chrome]
|
||||
version = "0.7.1"
|
||||
optional = true
|
||||
|
||||
# [patch.crates-io.stereokit]
|
||||
# path = "../stereokit-rs"
|
||||
# [patch.crates-io.stereokit-sys]
|
||||
# path = "../stereokit-sys"
|
||||
# [patch.crates-io.stardust-xr]
|
||||
# path = "../core/core"
|
||||
# [patch.crates-io.stardust-xr-schemas]
|
||||
# path = "../core/schemas"
|
||||
|
||||
71
README.md
71
README.md
@@ -15,8 +15,71 @@ This project is a usable Linux display server that reinvents human-computer inte
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
The latest stable server is automatically built to an appimage at https://github.com/StardustXR/server/releases for easy testing.
|
||||
|
||||
## Install
|
||||
```bash
|
||||
cargo install
|
||||
```
|
||||
## Usage
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
Stardust won't do anything interesting without clients! Try some from https://github.com/StardustXR.
|
||||
|
||||
### Default Sky
|
||||
|
||||
You can set a default skytex/skylight by putting your favorite HDRI equirectangular sky in `~/.config/stardust/skytex.hdr`. Certain clients can override this.
|
||||
|
||||
Flatscreen mode when the default skybox is [Zhengyang Gate](https://polyhaven.com/a/zhengyang_gate):
|
||||

|
||||
|
||||
### Windowed Mode
|
||||
|
||||
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.
|
||||

|
||||
|
||||
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.
|
||||
|
||||
### Flags
|
||||
#### Flatscreen (-f)
|
||||
|
||||
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:
|
||||

|
||||
|
||||
#### Overlay (-o \<PRIORITY>)
|
||||
|
||||
The server will, if in XR mode, be overlaid using the OpenXR overlay extension with the given priority.
|
||||
|
||||
#### Disable controller (--disable-controller)
|
||||
|
||||
Some runtimes such as Monado may emulate a controller using a hand, and this messes with Stardust's input system. Set this flag to ignore the controllers that the OpenXR runtime provides.
|
||||
|
||||
#### Execute (-e </path/to/executable>)
|
||||
|
||||
When wayland and OpenXR and such are initialized, run the given executable (such as a bash script) with all the environment variables needed to connect all clients of any type to the server. If not set, the server will run the executable at `~/.config/stardust/startup` if it exists. This is how stardust desktop environments can be made.
|
||||
|
||||
#### Help (-h, --help)
|
||||
|
||||
help
|
||||
|
||||
## Test
|
||||
|
||||
##### Gnome Graphical Integration Test
|
||||
|
||||
- `nix build .#gnome-graphical-test`
|
||||
|
||||
This test uses Nix to reproducibly execute a QEMU virtual machine which
|
||||
spawns a full Gnome desktop. It runs `monado-service`, `stardust-xr-server`
|
||||
`flatland` underneath of Gnome and then attaches `weston-cliptest` to the
|
||||
`flatland` process running underneath of `stardust-xr-server`, the result is
|
||||
a screenshot in PNG format that should look like expected. If any process in
|
||||
this test produces an exit code above 0, the test will fail, graphical bugs
|
||||
should be visible in the screenshot. An example of the result is below.
|
||||
|
||||
###### Result
|
||||
|
||||

|
||||
|
||||
##### Everything
|
||||
|
||||
`nix flake check` will build every test underneath of the `checks` attribute in the `flake.nix`
|
||||
405
flake.lock
generated
Normal file
405
flake.lock
generated
Normal file
@@ -0,0 +1,405 @@
|
||||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683786056,
|
||||
"narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "5816c7bbcc385d2e65877631497df3f7d66b354a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"flatland",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678775037,
|
||||
"narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678379998,
|
||||
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "flake-parts",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"hercules-ci-effects",
|
||||
"hercules-ci-agent",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678379998,
|
||||
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flatland": {
|
||||
"inputs": {
|
||||
"fenix": "fenix_2",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683766358,
|
||||
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=",
|
||||
"owner": "StardustXR",
|
||||
"repo": "flatland",
|
||||
"rev": "24613a496841bdf38e5f136608d5295860a75fce",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "StardustXR",
|
||||
"repo": "flatland",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"hercules-ci-effects",
|
||||
"hercules-ci-agent",
|
||||
"pre-commit-hooks-nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1660459072,
|
||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"haskell-flake": {
|
||||
"locked": {
|
||||
"lastModified": 1678138103,
|
||||
"narHash": "sha256-D0lao82bV3t2gEFjHiU6RN233t+1MnkQV+bq8MEu2ic=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "haskell-flake",
|
||||
"rev": "1e1660e6dd00838ba73bc7952e6e73be67da18d1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"ref": "0.1-extraLibraries",
|
||||
"repo": "haskell-flake",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hercules-ci-agent": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts_2",
|
||||
"haskell-flake": "haskell-flake",
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678446614,
|
||||
"narHash": "sha256-Z6Gsba5ahn/N0QlF0vJfIEfnZgCs4qr1IZtXAqjbE7s=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-agent",
|
||||
"rev": "0b90d1a87c117a5861785cb85833dd1c9df0b6ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "hercules-ci-agent",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"hercules-ci-effects": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"hercules-ci-agent": "hercules-ci-agent",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681898675,
|
||||
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-effects",
|
||||
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-effects",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"hercules-ci-effects",
|
||||
"hercules-ci-agent",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1673295039,
|
||||
"narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
|
||||
"owner": "LnL7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "LnL7",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678703398,
|
||||
"narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1678375444,
|
||||
"narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1673800717,
|
||||
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1678293141,
|
||||
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1678891326,
|
||||
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1683408522,
|
||||
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks-nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"hercules-ci-effects",
|
||||
"hercules-ci-agent",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678376203,
|
||||
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flatland": "flatland",
|
||||
"hercules-ci-effects": "hercules-ci-effects",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1683653808,
|
||||
"narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1678695923,
|
||||
"narHash": "sha256-rDhiiU8P6sf6mgj5IKgCuTRN9uYeqWr6xl4XLkKnMWg=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "95497533524537b1cc7a2870ce94b0b14503be8b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
133
flake.nix
Normal file
133
flake.nix
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
nixConfig = {
|
||||
extra-substituters = [ "https://stardustxr.cachix.org" ];
|
||||
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ];
|
||||
};
|
||||
|
||||
# 22.11 does not include PR #218472, hence we use the unstable version
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
|
||||
|
||||
# Since we do not have a monorepo, we have to fetch Flatland in order to use
|
||||
# it to create VM Tests
|
||||
inputs.flatland.url = "github:StardustXR/flatland";
|
||||
|
||||
inputs.fenix.url = github:nix-community/fenix;
|
||||
inputs.fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
|
||||
|
||||
outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }:
|
||||
let
|
||||
name = "server";
|
||||
pkgs = system: import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
shell = pkgs: pkgs.mkShell {
|
||||
inputsFrom = [ self.packages.${pkgs.system}.default ];
|
||||
|
||||
# ---- START package specific dev settings ----
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
# ---- END package specific dev settings ----
|
||||
};
|
||||
package = pkgs:
|
||||
let
|
||||
toolchain = fenix.packages.${pkgs.system}.minimal.toolchain;
|
||||
in
|
||||
(pkgs.makeRustPlatform {
|
||||
cargo = toolchain;
|
||||
rustc = toolchain;
|
||||
}).buildRustPackage rec {
|
||||
pname = "stardust-xr-${name}";
|
||||
src = builtins.path {
|
||||
name = "stardust-xr-source";
|
||||
path = toString ./.;
|
||||
filter = path: type:
|
||||
nixpkgs.lib.all
|
||||
(n: builtins.baseNameOf path != n)
|
||||
[
|
||||
"flake.nix"
|
||||
"flake.lock"
|
||||
"nix"
|
||||
"README.md"
|
||||
];
|
||||
};
|
||||
|
||||
# ---- START package specific settings ----
|
||||
version = "0.10.2";
|
||||
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
|
||||
mkdir -p $sk/build/cpm
|
||||
cp ${pkgs.fetchurl {
|
||||
url = "https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.32.2/CPM.cmake";
|
||||
hash = "sha256-yDHlpqmpAE8CWiwJRoWyaqbuBAg0090G8WJIC2KLHp8=";
|
||||
}} $sk/build/cpm/CPM_0.32.2.cmake
|
||||
'';
|
||||
|
||||
CPM_SOURCE_CACHE = "./build";
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cmake pkg-config llvmPackages.libcxxClang
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
|
||||
];
|
||||
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
# ---- END package specific settings ----
|
||||
};
|
||||
in
|
||||
{
|
||||
overlays.default = final: prev: {
|
||||
stardust-xr = (prev.stardust-xr or {}) // {
|
||||
${name} = package final;
|
||||
};
|
||||
};
|
||||
|
||||
packages."x86_64-linux".default = package (pkgs "x86_64-linux");
|
||||
packages."aarch64-linux".default = package (pkgs "aarch64-linux");
|
||||
|
||||
packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test;
|
||||
packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test;
|
||||
|
||||
checks."x86_64-linux".gnome-graphical-test = (pkgs "x86_64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "x86_64-linux"); inherit self; });
|
||||
checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; });
|
||||
|
||||
devShells."x86_64-linux".default = shell (pkgs "x86_64-linux");
|
||||
devShells."aarch64-linux".default = shell (pkgs "aarch64-linux");
|
||||
|
||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||
|
||||
effects = let
|
||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
|
||||
in { branch, 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 "Branch;${branch}" \
|
||||
--field "Commit ID;${rev}" \
|
||||
--field "Flatland Revision;${flatland.rev}" \
|
||||
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
|
||||
--image "$ADDRESS"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
BIN
img/flatscreen_1.png
Normal file
BIN
img/flatscreen_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
img/flatscreen_2.png
Normal file
BIN
img/flatscreen_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
img/flatscreen_3.png
Normal file
BIN
img/flatscreen_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 789 KiB |
BIN
img/xr_mode_windowed_blank.png
Normal file
BIN
img/xr_mode_windowed_blank.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
155
nix/gnome-graphical-test.nix
Normal file
155
nix/gnome-graphical-test.nix
Normal file
@@ -0,0 +1,155 @@
|
||||
{ pkgs, lib ? pkgs.lib, self, ... }:
|
||||
# Some code is copy-pasted from https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/gnome.nix
|
||||
# TODO: make this less boiler-platey and make a function like mkGnomeTest that does all this and upstream it to nixpkgs
|
||||
{
|
||||
name = "stardust-xr-server-gnome-vmtest";
|
||||
meta = with lib; {
|
||||
maintainers = [ maintainers.matthewcroughan ];
|
||||
};
|
||||
nodes.machine = { ... }: {
|
||||
imports = [ "${pkgs.path}/nixos/tests/common/user-account.nix" ];
|
||||
virtualisation.qemu.options = [
|
||||
"-device virtio-gpu-pci"
|
||||
];
|
||||
environment.systemPackages = [ pkgs.monado ];
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
desktopManager.gnome = {
|
||||
enable = true;
|
||||
debug = true;
|
||||
# Set a nice desktop background that is pleasing to the eyes :3
|
||||
extraGSettingsOverrides = ''
|
||||
[org.gnome.desktop.background]
|
||||
picture-uri='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
|
||||
picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
|
||||
'';
|
||||
};
|
||||
displayManager = {
|
||||
gdm = {
|
||||
enable = true;
|
||||
debug = true;
|
||||
};
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = "alice";
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd.user.services = {
|
||||
"monado" = {
|
||||
after = [ "graphical-session.target" "default.target" "org.gnome.Shell@wayland.service" ];
|
||||
environment = {
|
||||
XRT_COMPOSITOR_FORCE_WAYLAND = "1";
|
||||
WAYLAND_DISPLAY = "wayland-0";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStartPre = [
|
||||
"${pkgs.writeShellScript "sleep" ''
|
||||
sleep 3
|
||||
''}"
|
||||
];
|
||||
ExecStart = let
|
||||
# stdin disappears in NixOS test driver ( machine.succeed() ), requiring us to specify < /dev/ttyS0 to fake stdin
|
||||
exec-monado-service = pkgs.writeShellScript "exec-monado-service" "${pkgs.monado}/bin/monado-service < /dev/ttyS0";
|
||||
in [
|
||||
"${exec-monado-service}"
|
||||
];
|
||||
};
|
||||
};
|
||||
"stardust-xr-server" = {
|
||||
after = [ "monado.service" ];
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
NotifyAccess = "all";
|
||||
ExecStartPre = [
|
||||
"${pkgs.writeShellScript "sleep" ''
|
||||
sleep 3
|
||||
''}"
|
||||
];
|
||||
ExecStart = let
|
||||
notifyReady = pkgs.writeShellScript "notifyReady" "systemd-notify --ready";
|
||||
exec-stardust-xr-server = pkgs.writeShellScript "exec-stardust-xr-server" "${self.packages.${pkgs.hostPlatform.system}.default}/bin/stardust-xr-server -e ${notifyReady}";
|
||||
in [
|
||||
"${exec-stardust-xr-server}"
|
||||
];
|
||||
};
|
||||
};
|
||||
"weston-cliptest" = {
|
||||
after = [ "flatland.service" ];
|
||||
environment.WAYLAND_DISPLAY = "wayland-1";
|
||||
serviceConfig = {
|
||||
ExecStart = [
|
||||
"${pkgs.weston}/bin/weston-cliptest"
|
||||
];
|
||||
};
|
||||
};
|
||||
"flatland" = {
|
||||
after = [ "stardust-xr-server.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = [
|
||||
"${self.inputs.flatland.packages.${pkgs.hostPlatform.system}.default}/bin/flatland"
|
||||
];
|
||||
};
|
||||
};
|
||||
"org.gnome.Shell@wayland" = {
|
||||
wants = [ "monado.service" "stardust-xr-server.service" "flatland.service" "weston-cliptest.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = [
|
||||
# Clear the list before overriding it.
|
||||
""
|
||||
# Eval API is now internal so Shell needs to run in unsafe mode.
|
||||
# TODO: improve test driver so that it supports openqa-like manipulation
|
||||
# that would allow us to drop this mess.
|
||||
"${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
# Keep line widths somewhat managable
|
||||
user = nodes.machine.config.users.users.alice;
|
||||
uid = toString user.uid;
|
||||
bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
|
||||
gdbus = "${bus} gdbus";
|
||||
su = command: "su ${user.name} -c '${command}'";
|
||||
|
||||
# Call javascript in gnome shell, returns a tuple (success, output), where
|
||||
# `success` is true if the dbus call was successful and output is what the
|
||||
# javascript evaluates to.
|
||||
eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
|
||||
|
||||
# False when startup is done
|
||||
startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
|
||||
in ''
|
||||
with subtest("Login to GNOME with GDM"):
|
||||
# wait for gdm to start
|
||||
machine.wait_for_unit("display-manager.service")
|
||||
# wait for the wayland server
|
||||
machine.wait_for_file("/run/user/${uid}/wayland-0")
|
||||
# wait for alice to be logged in
|
||||
machine.wait_for_unit("default.target", "${user.name}")
|
||||
# check that logging in has given the user ownership of devices
|
||||
assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
|
||||
|
||||
with subtest("Wait for GNOME Shell"):
|
||||
# correct output should be (true, 'false')
|
||||
machine.wait_until_succeeds(
|
||||
"${startingUp} | grep -q 'true,..false'"
|
||||
)
|
||||
|
||||
# To allow monado-service to use < /dev/ttyS0
|
||||
machine.succeed("chown alice /dev/ttyS0")
|
||||
|
||||
with subtest("Open Monado and StardustXR"):
|
||||
# Close the Activities view so that Shell can correctly track the focused window.
|
||||
machine.send_key("esc")
|
||||
machine.wait_for_unit("monado.service", "${user.name}")
|
||||
machine.wait_for_unit("stardust-xr-server.service", "${user.name}")
|
||||
machine.wait_for_unit("flatland.service", "${user.name}")
|
||||
machine.wait_for_unit("weston-cliptest.service", "${user.name}")
|
||||
machine.sleep(3)
|
||||
machine.screenshot("screen")
|
||||
'';
|
||||
}
|
||||
@@ -1,136 +1,201 @@
|
||||
use super::{eventloop::EventLoop, scenegraph::Scenegraph};
|
||||
use super::scenegraph::Scenegraph;
|
||||
use crate::{
|
||||
core::registry::Registry,
|
||||
nodes::{data, drawable, fields, hmd, input, items, root::Root, spatial, startup, Node},
|
||||
core::{registry::OwnedRegistry, task},
|
||||
nodes::{
|
||||
audio, data, drawable, fields, hmd, input, items,
|
||||
root::Root,
|
||||
spatial,
|
||||
startup::{self, StartupSettings, STARTUP_SETTINGS},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use stardust_xr::messenger::Messenger;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use tokio::{net::UnixStream, sync::Notify, task::JoinHandle};
|
||||
use rustc_hash::FxHashMap;
|
||||
use stardust_xr::messenger::{self, MessageSenderHandle};
|
||||
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
|
||||
use tokio::{net::UnixStream, task::JoinHandle};
|
||||
use tracing::info;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CLIENTS: Registry<Client> = Registry::new();
|
||||
pub static ref CLIENTS: OwnedRegistry<Client> = OwnedRegistry::new();
|
||||
pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client {
|
||||
event_loop: Weak::new(),
|
||||
index: 0,
|
||||
pid: None,
|
||||
// env: None,
|
||||
exe: None,
|
||||
|
||||
stop_notifier: Default::default(),
|
||||
join_handle: OnceCell::new(),
|
||||
dispatch_join_handle: OnceCell::new(),
|
||||
flush_join_handle: OnceCell::new(),
|
||||
disconnect_status: OnceCell::new(),
|
||||
|
||||
messenger: None,
|
||||
message_sender_handle: None,
|
||||
scenegraph: Default::default(),
|
||||
root: OnceCell::new(),
|
||||
base_resource_prefixes: Default::default(),
|
||||
startup_settings: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
event_loop: Weak<EventLoop>,
|
||||
index: usize,
|
||||
stop_notifier: Arc<Notify>,
|
||||
join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||
pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
|
||||
let env = fs::read_to_string(format!("/proc/{pid}/environ"))?;
|
||||
Ok(FxHashMap::from_iter(
|
||||
env.split('\0')
|
||||
.filter_map(|var| var.split_once('='))
|
||||
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||
))
|
||||
}
|
||||
pub fn startup_settings(env: &FxHashMap<String, String>) -> Option<StartupSettings> {
|
||||
let token = env.get("STARDUST_STARTUP_TOKEN")?;
|
||||
STARTUP_SETTINGS.lock().get(token).cloned()
|
||||
}
|
||||
|
||||
pub messenger: Option<Messenger>,
|
||||
pub scenegraph: Scenegraph,
|
||||
pub struct Client {
|
||||
pid: Option<i32>,
|
||||
// env: Option<FxHashMap<String, String>>,
|
||||
exe: Option<PathBuf>,
|
||||
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||
disconnect_status: OnceCell<Result<()>>,
|
||||
|
||||
pub message_sender_handle: Option<MessageSenderHandle>,
|
||||
pub scenegraph: Arc<Scenegraph>,
|
||||
pub root: OnceCell<Arc<Root>>,
|
||||
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
|
||||
pub startup_settings: Option<StartupSettings>,
|
||||
}
|
||||
impl Client {
|
||||
pub fn from_connection(
|
||||
index: usize,
|
||||
event_loop: &Arc<EventLoop>,
|
||||
connection: UnixStream,
|
||||
) -> Arc<Self> {
|
||||
println!("New client connected");
|
||||
let client = CLIENTS.add(Client {
|
||||
event_loop: Arc::downgrade(event_loop),
|
||||
index,
|
||||
stop_notifier: Default::default(),
|
||||
join_handle: OnceCell::new(),
|
||||
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
|
||||
let pid = connection.peer_cred().ok().and_then(|c| c.pid());
|
||||
let env = pid.and_then(|pid| get_env(pid).ok());
|
||||
let exe = pid.and_then(|pid| fs::read_link(format!("/proc/{}/exe", pid)).ok());
|
||||
info!(
|
||||
pid,
|
||||
exe = exe
|
||||
.as_ref()
|
||||
.and_then(|exe| exe.to_str().map(|s| s.to_string())),
|
||||
"New client connected"
|
||||
);
|
||||
|
||||
messenger: Some(Messenger::new(
|
||||
tokio::runtime::Handle::current(),
|
||||
connection,
|
||||
)),
|
||||
scenegraph: Default::default(),
|
||||
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
|
||||
let scenegraph = Arc::new(Scenegraph::default());
|
||||
let startup_settings = env.as_ref().and_then(startup_settings);
|
||||
|
||||
let client = CLIENTS.add(Client {
|
||||
pid,
|
||||
// env,
|
||||
exe: exe.clone(),
|
||||
|
||||
dispatch_join_handle: OnceCell::new(),
|
||||
flush_join_handle: OnceCell::new(),
|
||||
disconnect_status: OnceCell::new(),
|
||||
|
||||
message_sender_handle: Some(messenger_tx.handle()),
|
||||
scenegraph: scenegraph.clone(),
|
||||
root: OnceCell::new(),
|
||||
base_resource_prefixes: Default::default(),
|
||||
startup_settings,
|
||||
});
|
||||
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
|
||||
let _ = client.root.set(Root::create(&client));
|
||||
hmd::make_alias(&client);
|
||||
spatial::create_interface(&client);
|
||||
fields::create_interface(&client);
|
||||
drawable::create_interface(&client);
|
||||
data::create_interface(&client);
|
||||
items::create_interface(&client);
|
||||
input::create_interface(&client);
|
||||
startup::create_interface(&client);
|
||||
let _ = client.root.set(Root::create(&client)?);
|
||||
hmd::make_alias(&client)?;
|
||||
spatial::create_interface(&client)?;
|
||||
fields::create_interface(&client)?;
|
||||
drawable::create_interface(&client)?;
|
||||
audio::create_interface(&client)?;
|
||||
data::create_interface(&client)?;
|
||||
items::create_interface(&client)?;
|
||||
input::create_interface(&client)?;
|
||||
startup::create_interface(&client)?;
|
||||
|
||||
let _ = client.join_handle.set(tokio::spawn({
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let dispatch_loop = async {
|
||||
loop {
|
||||
client.dispatch().await?
|
||||
let pid_printable = pid
|
||||
.map(|pid| pid.to_string())
|
||||
.unwrap_or_else(|| "??".to_string());
|
||||
let exe_printable = exe
|
||||
.and_then(|exe| {
|
||||
exe.file_name()
|
||||
.and_then(|exe| exe.to_str())
|
||||
.map(|exe| exe.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "??".to_string());
|
||||
let _ = client.dispatch_join_handle.get_or_try_init(|| {
|
||||
task::new(
|
||||
|| {
|
||||
format!(
|
||||
"client dispatch pid={} exe={}",
|
||||
&pid_printable, &exe_printable,
|
||||
)
|
||||
},
|
||||
{
|
||||
let client = client.clone();
|
||||
async move {
|
||||
loop {
|
||||
match messenger_rx.dispatch(&*scenegraph).await {
|
||||
Err(e) => {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let flush_loop = async {
|
||||
loop {
|
||||
client.flush().await?
|
||||
},
|
||||
)
|
||||
});
|
||||
let _ = client.flush_join_handle.get_or_try_init(|| {
|
||||
task::new(
|
||||
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
|
||||
{
|
||||
let client = client.clone();
|
||||
async move {
|
||||
loop {
|
||||
match messenger_tx.flush().await {
|
||||
Err(e) => {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let result = tokio::select! {
|
||||
_ = client.stop_notifier.notified() => Ok(()),
|
||||
e = dispatch_loop => e,
|
||||
e = flush_loop => e,
|
||||
};
|
||||
client.disconnect().await;
|
||||
result
|
||||
}
|
||||
}));
|
||||
client
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
|
||||
self.scenegraph
|
||||
.get_node(path)
|
||||
.ok_or_else(|| anyhow!("{} not found", name))
|
||||
.ok_or_else(|| eyre!("{} not found", name))
|
||||
}
|
||||
|
||||
pub async fn dispatch(&self) -> Result<(), std::io::Error> {
|
||||
match &self.messenger {
|
||||
Some(messenger) => messenger.dispatch(&self.scenegraph).await,
|
||||
None => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)),
|
||||
pub fn disconnect(&self, reason: Result<()>) {
|
||||
let _ = self.disconnect_status.set(reason);
|
||||
if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() {
|
||||
dispatch_join_handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn flush(&self) -> Result<(), std::io::Error> {
|
||||
match &self.messenger {
|
||||
Some(messenger) => messenger.flush().await,
|
||||
None => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn disconnect(&self) {
|
||||
self.stop_notifier.notify_one();
|
||||
if let Some(event_loop) = self.event_loop.upgrade() {
|
||||
event_loop.clients.lock().await.remove(self.index);
|
||||
if let Some(flush_join_handle) = self.flush_join_handle.get() {
|
||||
flush_join_handle.abort();
|
||||
}
|
||||
CLIENTS.remove(self);
|
||||
}
|
||||
}
|
||||
impl Drop for Client {
|
||||
fn drop(&mut self) {
|
||||
self.stop_notifier.notify_one();
|
||||
CLIENTS.remove(self);
|
||||
println!("Client disconnected");
|
||||
info!(
|
||||
pid = self.pid,
|
||||
exe = self
|
||||
.exe
|
||||
.as_ref()
|
||||
.and_then(|exe| exe.to_str().map(|s| s.to_string())),
|
||||
disconnect_status = match self.disconnect_status.take() {
|
||||
Some(Ok(_)) => "Graceful disconnect".to_string(),
|
||||
Some(Err(e)) => format!("Error: {}", e.root_cause()),
|
||||
None => "Unknown".to_string(),
|
||||
},
|
||||
"Client disconnected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
47
src/core/delta.rs
Normal file
47
src/core/delta.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Delta<T> {
|
||||
value: T,
|
||||
changed: bool,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
impl<T> Delta<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Delta {
|
||||
value,
|
||||
changed: false,
|
||||
}
|
||||
}
|
||||
pub fn peek_delta(&self) -> Option<&T> {
|
||||
self.changed.then_some(&self.value)
|
||||
}
|
||||
pub fn delta(&mut self) -> Option<&mut T> {
|
||||
let delta = self.changed.then_some(&mut self.value);
|
||||
self.changed = false;
|
||||
delta
|
||||
}
|
||||
pub fn mark_changed(&mut self) {
|
||||
self.changed = true;
|
||||
}
|
||||
pub const fn value(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
pub fn value_mut(&mut self) -> &mut T {
|
||||
self.mark_changed();
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
impl<T> Deref for Delta<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for Delta<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.mark_changed();
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,36 @@
|
||||
use super::client::Client;
|
||||
use anyhow::Result;
|
||||
use slab::Slab;
|
||||
use stardust_xr::server;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use super::task;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::sync::{Mutex, Notify};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub static FRAME: AtomicU64 = AtomicU64::new(0);
|
||||
use tracing::error;
|
||||
|
||||
pub struct EventLoop {
|
||||
pub socket_path: String,
|
||||
stop_notifier: Arc<Notify>,
|
||||
pub clients: Mutex<Slab<Arc<Client>>>,
|
||||
join_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> Result<(Arc<Self>, JoinHandle<Result<()>>)> {
|
||||
let socket_path = server::get_free_socket_path()
|
||||
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::Other))?;
|
||||
let socket = UnixListener::bind(socket_path.clone())?;
|
||||
pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
|
||||
let socket = UnixListener::bind(socket_path)?;
|
||||
|
||||
let event_loop = Arc::new(EventLoop {
|
||||
socket_path,
|
||||
stop_notifier: Default::default(),
|
||||
clients: Mutex::new(Slab::new()),
|
||||
});
|
||||
|
||||
let event_loop_join_handle = tokio::spawn({
|
||||
let event_loop = event_loop.clone();
|
||||
async move { EventLoop::event_loop(socket, event_loop).await }
|
||||
});
|
||||
|
||||
Ok((event_loop, event_loop_join_handle))
|
||||
}
|
||||
|
||||
async fn event_loop(socket: UnixListener, event_loop: Arc<EventLoop>) -> Result<()> {
|
||||
let event_loop_async = async {
|
||||
let join_handle = task::new(|| "event loop", async move {
|
||||
loop {
|
||||
let (socket, _) = socket.accept().await?;
|
||||
let mut clients = event_loop.clients.lock().await;
|
||||
let vacant_client = clients.vacant_entry();
|
||||
let idx = vacant_client.key();
|
||||
vacant_client.insert(Client::from_connection(idx, &event_loop, socket));
|
||||
let Ok((socket, _)) = socket.accept().await else { continue };
|
||||
if let Err(e) = Client::from_connection(socket) {
|
||||
error!(?e, "Unable to create client from connection");
|
||||
}
|
||||
}
|
||||
};
|
||||
})?;
|
||||
let event_loop = Arc::new(EventLoop { join_handle });
|
||||
|
||||
tokio::select! {
|
||||
_ = event_loop.stop_notifier.notified() => Ok(()),
|
||||
e = event_loop_async => e,
|
||||
}
|
||||
Ok(event_loop)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventLoop {
|
||||
fn drop(&mut self) {
|
||||
self.stop_notifier.notify_one();
|
||||
self.join_handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
pub mod client;
|
||||
pub mod delta;
|
||||
pub mod destroy_queue;
|
||||
pub mod eventloop;
|
||||
pub mod nodelist;
|
||||
pub mod node_collections;
|
||||
pub mod registry;
|
||||
pub mod resource;
|
||||
pub mod scenegraph;
|
||||
pub mod task;
|
||||
|
||||
84
src/core/node_collections.rs
Normal file
84
src/core/node_collections.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::nodes::Node;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
hash::Hash,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LifeLinkedNodeList {
|
||||
nodes: Mutex<Vec<Weak<Node>>>,
|
||||
}
|
||||
impl LifeLinkedNodeList {
|
||||
pub fn add(&self, node: Weak<Node>) {
|
||||
self.nodes.lock().push(node);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.nodes
|
||||
.lock()
|
||||
.iter()
|
||||
.filter_map(|node| node.upgrade())
|
||||
.for_each(|node| {
|
||||
node.destroy();
|
||||
});
|
||||
self.nodes.lock().clear();
|
||||
}
|
||||
}
|
||||
impl Drop for LifeLinkedNodeList {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LifeLinkedNodeMap<K: Hash + Eq> {
|
||||
nodes: Mutex<FxHashMap<K, Weak<Node>>>,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
impl<K: Hash + Eq> LifeLinkedNodeMap<K> {
|
||||
pub fn add(&self, key: K, node: &Arc<Node>) {
|
||||
self.nodes.lock().insert(key, Arc::downgrade(node));
|
||||
}
|
||||
pub fn get<Q>(&self, key: &Q) -> Option<Arc<Node>>
|
||||
where
|
||||
Q: ?Sized,
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
self.nodes.lock().get(key).and_then(|n| n.upgrade())
|
||||
}
|
||||
pub fn nodes(&self) -> Vec<Arc<Node>> {
|
||||
self.nodes
|
||||
.lock()
|
||||
.values()
|
||||
.filter_map(|v| v.upgrade())
|
||||
.collect()
|
||||
}
|
||||
pub fn remove<Q>(&self, key: &Q) -> Option<Arc<Node>>
|
||||
where
|
||||
Q: ?Sized,
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
self.nodes.lock().remove(key).and_then(|n| n.upgrade())
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let mut nodes = self.nodes.lock();
|
||||
nodes
|
||||
.values()
|
||||
.filter_map(|node| node.upgrade())
|
||||
.for_each(|node| {
|
||||
node.destroy();
|
||||
});
|
||||
nodes.clear();
|
||||
}
|
||||
}
|
||||
impl<K: Hash + Eq> Drop for LifeLinkedNodeMap<K> {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use crate::nodes::Node;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Weak;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LifeLinkedNodeList {
|
||||
nodes: Mutex<Vec<Weak<Node>>>,
|
||||
}
|
||||
impl LifeLinkedNodeList {
|
||||
pub fn add(&self, node: Weak<Node>) {
|
||||
self.nodes.lock().push(node);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.nodes
|
||||
.lock()
|
||||
.iter()
|
||||
.filter_map(|node| node.upgrade())
|
||||
.for_each(|node| {
|
||||
node.destroy();
|
||||
});
|
||||
self.nodes.lock().clear();
|
||||
}
|
||||
}
|
||||
impl Drop for LifeLinkedNodeList {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use parking_lot::{const_mutex, MappedMutexGuard, Mutex, MutexGuard};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
pub struct Registry<T: Send + Sync + ?Sized>(Lazy<Mutex<FxHashMap<usize, Weak<T>>>>);
|
||||
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
|
||||
|
||||
impl<T: Send + Sync + ?Sized> Registry<T> {
|
||||
pub const fn new() -> Self {
|
||||
Registry(Lazy::new(|| Mutex::new(FxHashMap::default())))
|
||||
Registry(const_mutex(None))
|
||||
}
|
||||
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
|
||||
MutexGuard::map(self.0.lock(), |r| {
|
||||
r.get_or_insert_with(|| FxHashMap::default())
|
||||
})
|
||||
}
|
||||
pub fn add(&self, t: T) -> Arc<T>
|
||||
where
|
||||
@@ -22,23 +25,81 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
||||
t_arc
|
||||
}
|
||||
pub fn add_raw(&self, t: &Arc<T>) {
|
||||
self.0
|
||||
.lock()
|
||||
self.lock()
|
||||
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t));
|
||||
}
|
||||
pub fn contains(&self, t: &T) -> bool {
|
||||
self.lock()
|
||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
||||
}
|
||||
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
|
||||
self.0
|
||||
.lock()
|
||||
self.lock()
|
||||
.iter()
|
||||
.filter_map(|pair| pair.1.upgrade())
|
||||
.collect()
|
||||
}
|
||||
pub fn remove(&self, t: &T) {
|
||||
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
|
||||
self.0
|
||||
.lock()
|
||||
.take()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|pair| pair.1.upgrade())
|
||||
.collect()
|
||||
}
|
||||
pub fn remove(&self, t: &T) {
|
||||
self.lock()
|
||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
self.0.lock().clear();
|
||||
self.lock().clear();
|
||||
}
|
||||
}
|
||||
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Mutex::new(self.0.lock().clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
|
||||
|
||||
impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
|
||||
pub const fn new() -> Self {
|
||||
OwnedRegistry(const_mutex(None))
|
||||
}
|
||||
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
|
||||
MutexGuard::map(self.0.lock(), |r| {
|
||||
r.get_or_insert_with(|| FxHashMap::default())
|
||||
})
|
||||
}
|
||||
pub fn add(&self, t: T) -> Arc<T>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
let t_arc = Arc::new(t);
|
||||
self.add_raw(t_arc.clone());
|
||||
t_arc
|
||||
}
|
||||
pub fn add_raw(&self, t: Arc<T>) {
|
||||
self.lock().insert(Arc::as_ptr(&t) as *const () as usize, t);
|
||||
}
|
||||
pub fn get_vec(&self) -> Vec<Arc<T>> {
|
||||
self.lock().values().cloned().collect::<Vec<_>>()
|
||||
}
|
||||
pub fn contains(&self, t: &T) -> bool {
|
||||
self.lock()
|
||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
||||
}
|
||||
pub fn remove(&self, t: &T) {
|
||||
self.lock()
|
||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
self.lock().clear();
|
||||
}
|
||||
}
|
||||
impl<T: Send + Sync + ?Sized> Clone for OwnedRegistry<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Mutex::new(self.0.lock().clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,43 @@
|
||||
use anyhow::anyhow;
|
||||
use color_eyre::eyre::eyre;
|
||||
use serde::{de::Visitor, Deserialize};
|
||||
use std::path::PathBuf;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResourceID {
|
||||
File(PathBuf),
|
||||
Namespaced { namespace: String, path: PathBuf },
|
||||
}
|
||||
impl ResourceID {
|
||||
pub fn get_file(&self, prefixes: &[PathBuf]) -> Option<PathBuf> {
|
||||
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
|
||||
match self {
|
||||
ResourceID::File(file) => (file.is_absolute() && file.exists()).then_some(file.clone()),
|
||||
ResourceID::File(file) => (file.is_absolute()
|
||||
&& file.exists() && Self::has_extension(file, extensions))
|
||||
.then_some(file.clone()),
|
||||
ResourceID::Namespaced { namespace, path } => {
|
||||
for prefix in prefixes {
|
||||
let mut test_path = prefix.clone();
|
||||
test_path.push(namespace.clone());
|
||||
test_path.push(path.clone());
|
||||
|
||||
if test_path.as_path().exists() {
|
||||
return Some(test_path);
|
||||
}
|
||||
}
|
||||
None
|
||||
let file_name = path.file_name()?;
|
||||
prefixes
|
||||
.iter()
|
||||
.filter_map(|prefix| {
|
||||
let prefixed_path = prefix.clone().join(namespace).join(path);
|
||||
let parent = prefixed_path.parent()?;
|
||||
std::fs::read_dir(parent).ok()
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|item| item.ok())
|
||||
.map(|dir_entry| dir_entry.path())
|
||||
.filter(|path| path.file_stem() == Some(file_name))
|
||||
.find(|path| Self::has_extension(path, extensions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
|
||||
if let Some(path_extension) = path.extension() {
|
||||
extensions.contains(&path_extension)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for ResourceID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
@@ -56,9 +70,7 @@ impl<'de> Visitor<'de> for ResourceVisitor {
|
||||
path: PathBuf::from(path),
|
||||
}
|
||||
} else {
|
||||
return Err(serde::de::Error::custom(anyhow!(
|
||||
"Invalid format for string"
|
||||
)));
|
||||
return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::Node;
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use stardust_xr::scenegraph;
|
||||
use stardust_xr::scenegraph::ScenegraphError;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::{debug, debug_span, instrument};
|
||||
|
||||
use core::hash::BuildHasherDefault;
|
||||
use dashmap::DashMap;
|
||||
@@ -17,8 +18,8 @@ pub struct Scenegraph {
|
||||
}
|
||||
|
||||
impl Scenegraph {
|
||||
pub fn get_client(&self) -> Arc<Client> {
|
||||
self.client.get().unwrap().upgrade().unwrap()
|
||||
pub fn get_client(&self) -> Option<Arc<Client>> {
|
||||
self.client.get()?.upgrade()
|
||||
}
|
||||
|
||||
pub fn add_node(&self, node: Node) -> Arc<Node> {
|
||||
@@ -27,19 +28,22 @@ impl Scenegraph {
|
||||
node_arc
|
||||
}
|
||||
pub fn add_node_raw(&self, node: Arc<Node>) {
|
||||
debug!(node = ?&*node, "Add node");
|
||||
let path = node.get_path().to_string();
|
||||
self.nodes.insert(path, node);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
|
||||
let mut node = self.nodes.get(path)?.clone();
|
||||
if let Some(alias) = node.alias.get() {
|
||||
while let Some(alias) = node.alias.get() {
|
||||
node = alias.original.upgrade()?;
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
|
||||
debug!(path, "Remove node");
|
||||
let (_, node) = self.nodes.remove(path)?;
|
||||
Some(node)
|
||||
}
|
||||
@@ -47,9 +51,12 @@ impl Scenegraph {
|
||||
|
||||
impl scenegraph::Scenegraph for Scenegraph {
|
||||
fn send_signal(&self, path: &str, method: &str, data: &[u8]) -> Result<(), ScenegraphError> {
|
||||
self.get_node(path)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.send_local_signal(self.get_client(), method, data)
|
||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)};
|
||||
debug_span!("Handle signal", path, method).in_scope(|| {
|
||||
self.get_node(path)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.send_local_signal(client, method, data)
|
||||
})
|
||||
}
|
||||
fn execute_method(
|
||||
&self,
|
||||
@@ -57,8 +64,11 @@ impl scenegraph::Scenegraph for Scenegraph {
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, ScenegraphError> {
|
||||
self.get_node(path)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.execute_local_method(self.get_client(), method, data)
|
||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)};
|
||||
debug_span!("Handle method", path, method).in_scope(|| {
|
||||
self.get_node(path)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.execute_local_method(client, method, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
24
src/core/task.rs
Normal file
24
src/core/task.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use std::future::Future;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::instrument;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn new<
|
||||
F: FnOnce() -> S,
|
||||
S: AsRef<str>,
|
||||
A: Future<Output = O> + Send + 'static,
|
||||
O: Send + 'static,
|
||||
>(
|
||||
name_fn: F,
|
||||
async_future: A,
|
||||
) -> Result<JoinHandle<O>> {
|
||||
#[cfg(not(feature = "profile_tokio"))]
|
||||
let result = Ok(tokio::task::spawn(async_future));
|
||||
#[cfg(feature = "profile_tokio")]
|
||||
let result = tokio::task::Builder::new()
|
||||
.name(name_fn().as_ref())
|
||||
.spawn(async_future);
|
||||
result
|
||||
}
|
||||
384
src/main.rs
384
src/main.rs
@@ -1,27 +1,36 @@
|
||||
mod core;
|
||||
mod nodes;
|
||||
mod objects;
|
||||
#[cfg(feature = "wayland")]
|
||||
mod wayland;
|
||||
|
||||
use crate::core::destroy_queue;
|
||||
use crate::nodes::{drawable, hmd, input};
|
||||
use crate::nodes::{audio, drawable, hmd, input};
|
||||
use crate::objects::input::eye_pointer::EyePointer;
|
||||
use crate::objects::input::mouse_pointer::MousePointer;
|
||||
use crate::objects::input::sk_controller::SkController;
|
||||
use crate::objects::input::sk_hand::SkHand;
|
||||
use crate::wayland::Wayland;
|
||||
use crate::objects::play_space::PlaySpace;
|
||||
|
||||
use self::core::eventloop::EventLoop;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use directories::ProjectDirs;
|
||||
use slog::Drain;
|
||||
use once_cell::sync::OnceCell;
|
||||
use stardust_xr::server;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use stereokit::input::Handed;
|
||||
use stereokit::lifecycle::DepthMode;
|
||||
use stereokit::render::SphericalHarmonics;
|
||||
use stereokit::texture::Texture;
|
||||
use stereokit::{lifecycle::DisplayMode, Settings};
|
||||
use std::time::Duration;
|
||||
use stereokit::{
|
||||
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread,
|
||||
TextureFormat, TextureType,
|
||||
};
|
||||
use stereokit::{DisplayBlend, Sk};
|
||||
use tokio::{runtime::Handle, sync::oneshot};
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing::{debug_span, error, info};
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
@@ -33,140 +42,305 @@ struct CliArgs {
|
||||
/// Run Stardust XR as an overlay with given priority
|
||||
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
|
||||
overlay_priority: Option<u32>,
|
||||
|
||||
/// Don't create a tip input for controller because SOME RUNTIMES will lie
|
||||
#[clap(long, action)]
|
||||
disable_controller: bool,
|
||||
|
||||
/// Run a script when ready for clients to connect. If this is not set the script at $HOME/.config/stardust/startup will be ran if it exists.
|
||||
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
|
||||
startup_script: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let project_dirs = ProjectDirs::from("", "", "stardust").unwrap();
|
||||
let cli_args = Arc::new(CliArgs::parse());
|
||||
let log = ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), slog::o!());
|
||||
slog_stdlog::init()?;
|
||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
|
||||
|
||||
let mut stereokit = Settings::default()
|
||||
.app_name("Stardust XR")
|
||||
.overlay_app(cli_args.overlay_priority.is_some())
|
||||
.overlay_priority(cli_args.overlay_priority.unwrap_or(u32::MAX))
|
||||
.disable_desktop_input_window(true)
|
||||
.display_preference(if cli_args.flatscreen {
|
||||
struct EventLoopInfo {
|
||||
tokio_handle: Handle,
|
||||
socket_path: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let registry = tracing_subscriber::registry();
|
||||
#[cfg(feature = "profile_app")]
|
||||
let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new()
|
||||
.include_args(true)
|
||||
.build();
|
||||
#[cfg(feature = "profile_app")]
|
||||
let registry = registry.with(chrome_layer);
|
||||
|
||||
#[cfg(feature = "profile_tokio")]
|
||||
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
|
||||
#[cfg(feature = "profile_tokio")]
|
||||
let registry = registry.with(console_layer);
|
||||
|
||||
let log_layer = fmt::Layer::new()
|
||||
.with_thread_names(true)
|
||||
.with_ansi(true)
|
||||
.with_line_number(true)
|
||||
.with_filter(EnvFilter::from_default_env());
|
||||
registry.with(log_layer).init();
|
||||
|
||||
let project_dirs = ProjectDirs::from("", "", "stardust");
|
||||
if project_dirs.is_none() {
|
||||
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
|
||||
}
|
||||
let cli_args = Arc::new(CliArgs::parse());
|
||||
|
||||
let sk = stereokit::Settings {
|
||||
app_name: "Stardust XR".to_string(),
|
||||
display_preference: if cli_args.flatscreen {
|
||||
DisplayMode::Flatscreen
|
||||
} else {
|
||||
DisplayMode::MixedReality
|
||||
})
|
||||
.depth_mode(DepthMode::D32)
|
||||
.init()
|
||||
.expect("StereoKit failed to initialize");
|
||||
println!("Init StereoKit");
|
||||
},
|
||||
blend_preference: DisplayBlend::AnyTransparent,
|
||||
depth_mode: DepthMode::D32,
|
||||
log_filter: match EnvFilter::from_default_env().max_level_hint() {
|
||||
Some(LevelFilter::ERROR) => LogLevel::Error,
|
||||
Some(LevelFilter::WARN) => LogLevel::Warning,
|
||||
Some(LevelFilter::INFO) => LogLevel::Inform,
|
||||
Some(LevelFilter::DEBUG) => LogLevel::Diagnostic,
|
||||
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
|
||||
Some(LevelFilter::OFF) => LogLevel::None,
|
||||
None => LogLevel::Warning,
|
||||
},
|
||||
overlay_app: cli_args.overlay_priority.is_some(),
|
||||
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
|
||||
disable_desktop_input_window: true,
|
||||
..Default::default()
|
||||
}
|
||||
.init()
|
||||
.expect("StereoKit failed to initialize");
|
||||
let _ = SK_MULTITHREAD.set(sk.multithreaded());
|
||||
info!("Init StereoKit");
|
||||
|
||||
sk.material_set_shader(
|
||||
sk.material_find("default/material_pbr").unwrap(),
|
||||
sk.shader_find("default/shader_pbr_clip").unwrap(),
|
||||
);
|
||||
|
||||
// Skytex/light stuff
|
||||
{
|
||||
let skytex_path = project_dirs.config_dir().join("skytex.hdr");
|
||||
if let Some((tex, light)) = skytex_path
|
||||
.exists()
|
||||
.then(|| Texture::from_cubemap_equirectangular(&stereokit, &skytex_path, true, 100))
|
||||
if let Some((light, tex)) = project_dirs
|
||||
.as_ref()
|
||||
.and_then(|dirs| {
|
||||
let skytex_path = dirs.config_dir().join("skytex.hdr");
|
||||
skytex_path
|
||||
.exists()
|
||||
.then(|| sk.tex_create_cubemap_file(&skytex_path, true, 100).ok())
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
stereokit.set_skytex(&tex);
|
||||
stereokit.set_skylight(&light);
|
||||
} else if let Some(tex) = Texture::cubemap_from_spherical_harmonics(
|
||||
&stereokit,
|
||||
&SphericalHarmonics::default(),
|
||||
16,
|
||||
0.0,
|
||||
0.0,
|
||||
) {
|
||||
stereokit.set_skytex(&tex);
|
||||
sk.render_set_skytex(&tex);
|
||||
sk.render_set_skylight(light);
|
||||
} else {
|
||||
sk.render_set_skytex(sk.tex_gen_color(
|
||||
BLACK,
|
||||
1,
|
||||
1,
|
||||
TextureType::CUBEMAP,
|
||||
TextureFormat::RGBA32,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mouse_pointer = cli_args.flatscreen.then(MousePointer::new);
|
||||
let mut hands =
|
||||
(!cli_args.flatscreen).then(|| [SkHand::new(Handed::Left), SkHand::new(Handed::Right)]);
|
||||
let mut controllers = (!cli_args.flatscreen).then(|| {
|
||||
[
|
||||
SkController::new(Handed::Left),
|
||||
SkController::new(Handed::Right),
|
||||
]
|
||||
});
|
||||
let mouse_pointer = cli_args
|
||||
.flatscreen
|
||||
.then(MousePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
let mut hands = (!cli_args.flatscreen)
|
||||
.then(|| {
|
||||
let left = SkHand::new(Handed::Left).ok();
|
||||
let right = SkHand::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
|
||||
.then(|| {
|
||||
let left = SkController::new(Handed::Left).ok();
|
||||
let right = SkController::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze())
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
|
||||
if hands.is_none() {
|
||||
unsafe {
|
||||
stereokit::sys::input_hand_visible(stereokit::sys::handed__handed_left, false as i32);
|
||||
stereokit::sys::input_hand_visible(stereokit::sys::handed__handed_right, false as i32);
|
||||
}
|
||||
sk.input_hand_visible(Handed::Left, false);
|
||||
sk.input_hand_visible(Handed::Right, false);
|
||||
}
|
||||
|
||||
let play_space = sk
|
||||
.world_has_bounds()
|
||||
.then(|| PlaySpace::new().ok())
|
||||
.flatten();
|
||||
|
||||
let (event_stop_tx, event_stop_rx) = oneshot::channel::<()>();
|
||||
let (handle_sender, handle_receiver) = oneshot::channel::<Handle>();
|
||||
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
|
||||
let event_thread = std::thread::Builder::new()
|
||||
.name("event_loop".to_owned())
|
||||
.spawn(move || event_loop(handle_sender, event_stop_rx))?;
|
||||
let _tokio_handle = handle_receiver.blocking_recv()?.enter();
|
||||
.spawn(move || event_loop(info_sender, event_stop_rx))
|
||||
.unwrap();
|
||||
let event_loop_info = info_receiver.blocking_recv().unwrap();
|
||||
let _tokio_handle = event_loop_info.tokio_handle.enter();
|
||||
|
||||
let mut wayland = Wayland::new(log)?;
|
||||
println!("Stardust ready!");
|
||||
stereokit.run(
|
||||
|sk, draw_ctx| {
|
||||
hmd::frame(sk);
|
||||
wayland.frame(sk);
|
||||
destroy_queue::clear();
|
||||
#[cfg(feature = "wayland")]
|
||||
let mut wayland = wayland::Wayland::new().unwrap();
|
||||
info!("Stardust ready!");
|
||||
|
||||
if let Some(mouse_pointer) = &mouse_pointer {
|
||||
mouse_pointer.update(sk);
|
||||
}
|
||||
if let Some(hands) = &mut hands {
|
||||
hands[0].update(sk);
|
||||
hands[1].update(sk);
|
||||
}
|
||||
if let Some(controllers) = &mut controllers {
|
||||
controllers[0].update(sk);
|
||||
controllers[1].update(sk);
|
||||
}
|
||||
input::process_input();
|
||||
nodes::root::Root::logic_step(sk.time_elapsed());
|
||||
drawable::draw(sk, draw_ctx);
|
||||
if let Some(project_dirs) = project_dirs.as_ref() {
|
||||
let startup_script_path = cli_args
|
||||
.startup_script
|
||||
.clone()
|
||||
.and_then(|p| p.canonicalize().ok())
|
||||
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
|
||||
let _startup = Command::new(startup_script_path)
|
||||
.stdin(Stdio::null())
|
||||
.env(
|
||||
"FLAT_WAYLAND_DISPLAY",
|
||||
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
|
||||
)
|
||||
.env("WAYLAND_DISPLAY", &wayland.socket_name)
|
||||
.env(
|
||||
"STARDUST_INSTANCE",
|
||||
event_loop_info
|
||||
.socket_path
|
||||
.file_name()
|
||||
.expect("Stardust socket path not found"),
|
||||
)
|
||||
.env("GDK_BACKEND", "wayland")
|
||||
.env("QT_QPA_PLATFORM", "wayland")
|
||||
.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
.env("CLUTTER_BACKEND", "wayland")
|
||||
.env("SDL_VIDEODRIVER", "wayland")
|
||||
.spawn();
|
||||
}
|
||||
|
||||
wayland.make_context_current();
|
||||
},
|
||||
|_| {
|
||||
println!("Cleanly shut down StereoKit");
|
||||
},
|
||||
);
|
||||
let mut last_frame_delta = Duration::ZERO;
|
||||
let mut sleep_duration = Duration::ZERO;
|
||||
debug_span!("StereoKit").in_scope(|| {
|
||||
sk.run(
|
||||
|sk| {
|
||||
let _span = debug_span!("StereoKit step");
|
||||
let _span = _span.enter();
|
||||
|
||||
drop(wayland);
|
||||
hmd::frame(sk);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.frame_event(sk);
|
||||
destroy_queue::clear();
|
||||
|
||||
if let Some(mouse_pointer) = &mouse_pointer {
|
||||
mouse_pointer.update(sk);
|
||||
}
|
||||
if let Some((left_hand, right_hand)) = &mut hands {
|
||||
left_hand.update(sk);
|
||||
right_hand.update(sk);
|
||||
}
|
||||
if let Some((left_controller, right_controller)) = &mut controllers {
|
||||
left_controller.update(sk);
|
||||
right_controller.update(sk);
|
||||
}
|
||||
if let Some(eye_pointer) = &eye_pointer {
|
||||
eye_pointer.update(sk);
|
||||
}
|
||||
if let Some(play_space) = &play_space {
|
||||
play_space.update(sk);
|
||||
}
|
||||
input::process_input();
|
||||
nodes::root::Root::send_frame_events(sk.time_elapsed_unscaled());
|
||||
adaptive_sleep(
|
||||
sk,
|
||||
&mut last_frame_delta,
|
||||
&mut sleep_duration,
|
||||
Duration::from_micros(250),
|
||||
);
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.update(sk);
|
||||
drawable::draw(sk);
|
||||
audio::update(sk);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.make_context_current();
|
||||
},
|
||||
|_| {
|
||||
info!("Cleanly shut down StereoKit");
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
let _wayland = ManuallyDrop::new(wayland);
|
||||
|
||||
let _ = event_stop_tx.send(());
|
||||
event_thread
|
||||
.join()
|
||||
.expect("Failed to cleanly shut down event loop")?;
|
||||
println!("Cleanly shut down Stardust");
|
||||
Ok(())
|
||||
.expect("Failed to cleanly shut down event loop")
|
||||
.unwrap();
|
||||
info!("Cleanly shut down Stardust");
|
||||
}
|
||||
|
||||
// #[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
fn adaptive_sleep(
|
||||
sk: &impl StereoKitMultiThread,
|
||||
last_frame_delta: &mut Duration,
|
||||
sleep_duration: &mut Duration,
|
||||
sleep_duration_increase: Duration,
|
||||
) {
|
||||
let frame_delta = Duration::from_secs_f64(sk.time_elapsed_unscaled());
|
||||
if *last_frame_delta < frame_delta {
|
||||
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_frame_delta) {
|
||||
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
|
||||
*sleep_duration = new_sleep_duration;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*sleep_duration += sleep_duration_increase;
|
||||
}
|
||||
|
||||
debug_span!("Sleep", ?sleep_duration, ?frame_delta, ?last_frame_delta).in_scope(|| {
|
||||
*last_frame_delta = frame_delta;
|
||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
// #[tokio::main(flavor = "current_thread")]
|
||||
async fn event_loop(
|
||||
handle_sender: oneshot::Sender<Handle>,
|
||||
info_sender: oneshot::Sender<EventLoopInfo>,
|
||||
stop_rx: oneshot::Receiver<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
let _ = handle_sender.send(Handle::current());
|
||||
// console_subscriber::init();
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
let socket_path =
|
||||
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
|
||||
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
|
||||
let _event_loop = EventLoop::new(socket_path.clone()).expect("Couldn't create server socket");
|
||||
info!("Init event loop");
|
||||
info!(
|
||||
socket_path = ?socket_path.display(),
|
||||
"Stardust socket created"
|
||||
);
|
||||
let _ = info_sender.send(EventLoopInfo {
|
||||
tokio_handle: Handle::current(),
|
||||
socket_path,
|
||||
});
|
||||
|
||||
let (event_loop, event_loop_join_handle) =
|
||||
EventLoop::new().expect("Couldn't create server socket");
|
||||
println!("Init event loop");
|
||||
println!("Stardust socket created at {}", event_loop.socket_path);
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
stop_rx.await?;
|
||||
} else {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = tokio::signal::ctrl_c() => (),
|
||||
_ = stop_rx => (),
|
||||
};
|
||||
}
|
||||
|
||||
let result = tokio::select! {
|
||||
biased;
|
||||
_ = tokio::signal::ctrl_c() => Ok(()),
|
||||
_ = stop_rx => Ok(()),
|
||||
e = event_loop_join_handle => e?,
|
||||
};
|
||||
|
||||
println!("Cleanly shut down event loop");
|
||||
info!("Cleanly shut down event loop");
|
||||
|
||||
unsafe {
|
||||
stereokit::sys::sk_quit();
|
||||
}
|
||||
|
||||
result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::core::client::Client;
|
||||
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct AliasInfo {
|
||||
pub(super) local_signals: Vec<&'static str>,
|
||||
pub(super) local_methods: Vec<&'static str>,
|
||||
pub(super) remote_signals: Vec<&'static str>,
|
||||
pub(super) server_signals: Vec<&'static str>,
|
||||
pub(super) server_methods: Vec<&'static str>,
|
||||
pub(super) client_signals: Vec<&'static str>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -18,14 +18,22 @@ pub struct Alias {
|
||||
pub info: AliasInfo,
|
||||
}
|
||||
impl Alias {
|
||||
pub fn new(
|
||||
pub fn create(
|
||||
client: &Arc<Client>,
|
||||
parent: &str,
|
||||
name: &str,
|
||||
original: &Arc<Node>,
|
||||
info: AliasInfo,
|
||||
) -> Arc<Node> {
|
||||
let node = Node::create(client, parent, name, true).add_to_scenegraph();
|
||||
) -> Result<Arc<Node>> {
|
||||
ensure!(
|
||||
client
|
||||
.scenegraph
|
||||
.get_node(&(parent.to_string() + "/" + name))
|
||||
.is_none(),
|
||||
"Node already exists"
|
||||
);
|
||||
|
||||
let node = Node::create(client, parent, name, true).add_to_scenegraph()?;
|
||||
let alias = Alias {
|
||||
node: Arc::downgrade(&node),
|
||||
original: Arc::downgrade(original),
|
||||
@@ -33,6 +41,6 @@ impl Alias {
|
||||
};
|
||||
let alias = original.aliases.add(alias);
|
||||
let _ = node.alias.set(alias);
|
||||
node
|
||||
Ok(node)
|
||||
}
|
||||
}
|
||||
|
||||
136
src/nodes/audio.rs
Normal file
136
src/nodes/audio.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::destroy_queue;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::resource::ResourceID;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use glam::{vec3, Vec4Swizzles};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use send_wrapper::SendWrapper;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use std::ops::DerefMut;
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
|
||||
|
||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||
|
||||
pub struct Sound {
|
||||
space: Arc<Spatial>,
|
||||
|
||||
volume: f32,
|
||||
pending_audio_path: PathBuf,
|
||||
sk_sound: OnceCell<SendWrapper<SkSound>>,
|
||||
instance: Mutex<Option<SoundInstance>>,
|
||||
stop: Mutex<Option<()>>,
|
||||
play: Mutex<Option<()>>,
|
||||
}
|
||||
|
||||
impl Sound {
|
||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
let pending_audio_path = resource_id
|
||||
.get_file(
|
||||
&node
|
||||
.get_client()
|
||||
.ok_or_else(|| eyre!("Client not found"))?
|
||||
.base_resource_prefixes
|
||||
.lock()
|
||||
.clone(),
|
||||
&[OsStr::new("wav"), OsStr::new("mp3")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
let sound = Sound {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
volume: 1.0,
|
||||
pending_audio_path,
|
||||
sk_sound: OnceCell::new(),
|
||||
instance: Mutex::new(None),
|
||||
stop: Mutex::new(None),
|
||||
play: Mutex::new(None),
|
||||
};
|
||||
let sound_arc = SOUND_REGISTRY.add(sound);
|
||||
node.add_local_signal("play", Sound::play_flex);
|
||||
node.add_local_signal("stop", Sound::stop_flex);
|
||||
let _ = node.sound.set(sound_arc.clone());
|
||||
Ok(sound_arc)
|
||||
}
|
||||
|
||||
fn update(&self, sk: &impl StereoKitDraw) {
|
||||
let sound = self.sk_sound.get_or_init(|| {
|
||||
SendWrapper::new(sk.sound_create(self.pending_audio_path.clone()).unwrap())
|
||||
});
|
||||
if self.stop.lock().take().is_some() {
|
||||
if let Some(instance) = self.instance.lock().take() {
|
||||
sk.sound_inst_stop(instance);
|
||||
}
|
||||
}
|
||||
if self.play.lock().is_some() && self.instance.lock().is_none() {
|
||||
self.instance.lock().replace(sk.sound_play(
|
||||
sound.as_ref(),
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
self.volume,
|
||||
));
|
||||
}
|
||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
||||
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
|
||||
}
|
||||
}
|
||||
|
||||
fn play_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
let sound = node.sound.get().unwrap();
|
||||
sound.play.lock().replace(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
let sound = node.sound.get().unwrap();
|
||||
sound.stop.lock().replace(());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(sk: &impl StereoKitDraw) {
|
||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||
sound.update(sk)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "audio", false);
|
||||
node.add_local_signal("create_sound", create_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateSoundInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
resource: ResourceID,
|
||||
}
|
||||
let info: CreateSoundInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/audio/sound", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Sound::add_to(&node, info.resource)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Drop for Sound {
|
||||
fn drop(&mut self) {
|
||||
if let Some(instance) = self.instance.lock().take() {
|
||||
destroy_queue::add(instance);
|
||||
}
|
||||
SOUND_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,28 @@ use super::fields::Field;
|
||||
use super::spatial::{parse_transform, Spatial};
|
||||
use super::{Alias, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::nodelist::LifeLinkedNodeList;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::fields::find_field;
|
||||
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO};
|
||||
use crate::nodes::spatial::find_spatial_parent;
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use glam::vec3a;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use mint::{Quaternion, Vector3};
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
|
||||
static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
||||
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
||||
|
||||
fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
|
||||
pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
|
||||
(|| -> Result<_> {
|
||||
for key in mask_map_lesser.get_mask()?.iter_keys() {
|
||||
let lesser_key_type = mask_map_lesser.get_mask()?.index(key)?.flexbuffer_type();
|
||||
let greater_key_type = mask_map_greater.get_mask()?.index(key)?.flexbuffer_type();
|
||||
if lesser_key_type != greater_key_type {
|
||||
let lesser_key = mask_map_lesser.get_mask()?.index(key)?;
|
||||
let greater_key = mask_map_greater.get_mask()?.index(key)?;
|
||||
if lesser_key.to_string() != greater_key.to_string() {
|
||||
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
||||
}
|
||||
}
|
||||
@@ -32,120 +33,145 @@ fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
type MaskMapGetFn = fn(&[u8]) -> Result<flexbuffers::MapReader<&[u8]>>;
|
||||
pub struct Mask {
|
||||
binary: Vec<u8>,
|
||||
get_fn: MaskMapGetFn,
|
||||
}
|
||||
pub struct Mask(pub Vec<u8>);
|
||||
impl Mask {
|
||||
pub fn from_struct<T: Default + Serialize>() -> Self {
|
||||
let mut serializer = flexbuffers::FlexbufferSerializer::new();
|
||||
T::default().serialize(&mut serializer).unwrap();
|
||||
Mask(serializer.take_buffer())
|
||||
}
|
||||
pub fn get_mask(&self) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
(self.get_fn)(self.binary.as_slice())
|
||||
flexbuffers::Reader::get_root(self.0.as_slice())
|
||||
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
|
||||
.get_map()
|
||||
.map_err(|_| eyre!("Mask is not a valid map"))
|
||||
}
|
||||
pub fn set_mask(&mut self, binary: Vec<u8>, get_fn: MaskMapGetFn) {
|
||||
self.binary = binary;
|
||||
self.get_fn = get_fn;
|
||||
}
|
||||
}
|
||||
impl Default for Mask {
|
||||
fn default() -> Self {
|
||||
Mask {
|
||||
binary: Default::default(),
|
||||
get_fn: mask_get_err,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mask_get_err(_binary: &[u8]) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
Err(anyhow!("You need to call setMask to set the mask!"))
|
||||
}
|
||||
fn mask_get_map_at_root(binary: &[u8]) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
flexbuffers::Reader::get_root(binary)
|
||||
.map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
|
||||
.get_map()
|
||||
.map_err(|_| anyhow!("Mask is not a valid map"))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SendDataInfo<'a> {
|
||||
uid: &'a str,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct PulseSender {
|
||||
mask: Mutex<Mask>,
|
||||
aliases: LifeLinkedNodeList,
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
pub mask: Mask,
|
||||
aliases: LifeLinkedNodeMap<String>,
|
||||
}
|
||||
impl PulseSender {
|
||||
pub fn add_to(node: &Arc<Node>) -> Result<()> {
|
||||
pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
|
||||
let sender = Default::default();
|
||||
let sender = PulseSender {
|
||||
uid: nanoid!(),
|
||||
node: Arc::downgrade(node),
|
||||
mask,
|
||||
aliases: LifeLinkedNodeMap::default(),
|
||||
};
|
||||
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
||||
let _ = node.pulse_sender.set(sender);
|
||||
node.add_local_signal("setMask", PulseSender::set_mask_flex);
|
||||
node.add_local_method("getReceivers", PulseSender::get_receivers_flex);
|
||||
Ok(())
|
||||
let _ = node.pulse_sender.set(sender.clone());
|
||||
node.add_local_signal("send_data", PulseSender::send_data_flex);
|
||||
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_new_receiver(&receiver);
|
||||
}
|
||||
Ok(sender.clone())
|
||||
}
|
||||
pub fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
ensure!(
|
||||
node.pulse_sender.get().is_some(),
|
||||
"Internal: Node does not have a pulse sender aspect"
|
||||
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 rx_alias = Alias::create(
|
||||
&tx_client,
|
||||
tx_node.get_path(),
|
||||
receiver.uid.as_str(),
|
||||
&rx_node,
|
||||
AliasInfo {
|
||||
server_methods: vec!["sendData", "getTransform"],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
node.pulse_sender
|
||||
.get()
|
||||
.unwrap()
|
||||
.mask
|
||||
.lock()
|
||||
.set_mask(data.to_vec(), mask_get_map_at_root);
|
||||
Ok(())
|
||||
}
|
||||
fn get_receivers_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
_data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let sender_spatial = node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("Node does not have a spatial aspect!"))?;
|
||||
let sender = node
|
||||
.pulse_sender
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("Node does not have a sender aspect!"))?;
|
||||
let valid_receivers = PULSE_RECEIVER_REGISTRY.get_valid_contents();
|
||||
let mut distance_sorted_receivers: Vec<(f32, &PulseReceiver)> = valid_receivers
|
||||
.iter()
|
||||
.filter(|receiver| receiver.get_field().is_some())
|
||||
.filter(|receiver| mask_matches(&*sender.mask.lock(), &*receiver.mask.lock()))
|
||||
.map(|receiver| {
|
||||
(
|
||||
receiver
|
||||
.get_field()
|
||||
.unwrap()
|
||||
.distance(sender_spatial, vec3a(0_f32, 0_f32, 0_f32)),
|
||||
receiver.as_ref(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
distance_sorted_receivers.sort_by(|(d1, _), (d2, _)| d1.partial_cmp(d2).unwrap());
|
||||
sender.aliases.clear();
|
||||
let uids: Vec<String> = distance_sorted_receivers
|
||||
.into_iter()
|
||||
.map(|(_, receiver)| {
|
||||
let receiver_alias = Alias::new(
|
||||
&calling_client,
|
||||
node.get_path(),
|
||||
receiver.uid.as_str(),
|
||||
receiver.node.upgrade().as_ref().unwrap(),
|
||||
AliasInfo {
|
||||
local_methods: vec!["sendData"],
|
||||
..Default::default()
|
||||
},
|
||||
if let Ok(rx_alias) = rx_alias {
|
||||
self.aliases.add(receiver.uid.clone(), &rx_alias);
|
||||
|
||||
if let Some(rx_field_node) = receiver.field.spatial_ref().node.upgrade() {
|
||||
// Receiver's field
|
||||
let rx_field_alias = Alias::create(
|
||||
&tx_client,
|
||||
rx_alias.get_path(),
|
||||
"field",
|
||||
&rx_field_node,
|
||||
FIELD_ALIAS_INFO.clone(),
|
||||
);
|
||||
sender.aliases.add(Arc::downgrade(&receiver_alias));
|
||||
if let Ok(rx_field_alias) = rx_field_alias {
|
||||
self.aliases
|
||||
.add(receiver.uid.clone() + "-field", &rx_field_alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receiver.uid.clone()
|
||||
})
|
||||
.collect();
|
||||
#[derive(Serialize)]
|
||||
struct NewReceiverInfo<'a> {
|
||||
uid: &'a str,
|
||||
distance: f32,
|
||||
position: Vector3<f32>,
|
||||
rotation: Quaternion<f32>,
|
||||
}
|
||||
|
||||
serialize(uids).map_err(|e| e.into())
|
||||
let (_, rotation, position) = Spatial::space_to_space_matrix(
|
||||
rx_node.spatial.get().map(|s| s.as_ref()),
|
||||
tx_node.spatial.get().map(|s| s.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
let info = NewReceiverInfo {
|
||||
uid: &receiver.uid,
|
||||
distance: receiver
|
||||
.field
|
||||
.distance(tx_node.spatial.get().unwrap(), vec3a(0.0, 0.0, 0.0)),
|
||||
position: position.into(),
|
||||
rotation: rotation.into(),
|
||||
};
|
||||
|
||||
let Ok(data) = serialize(info) else {return};
|
||||
let _ = tx_node.send_remote_signal("new_receiver", &data);
|
||||
}
|
||||
|
||||
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
|
||||
let uid = receiver.uid.as_str();
|
||||
self.aliases.remove(uid);
|
||||
self.aliases.remove(&(uid.to_string() + "-field"));
|
||||
let Some(tx_node) = self.node.upgrade() else {return};
|
||||
let Ok(data) = serialize(&uid) else {return};
|
||||
let _ = tx_node.send_remote_signal("drop_receiver", &data);
|
||||
}
|
||||
|
||||
fn send_data_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let info: SendDataInfo = deserialize(data)?;
|
||||
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
|
||||
let receiver =
|
||||
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
|
||||
let receiver_mask = &receiver_node
|
||||
.get_aspect("Pulse receiver", "pulse receiver", |node| {
|
||||
&node.pulse_receiver
|
||||
})?
|
||||
.mask;
|
||||
|
||||
let data_mask = Mask(info.data);
|
||||
data_mask.get_mask()?;
|
||||
ensure!(
|
||||
mask_matches(receiver_mask, &data_mask),
|
||||
"Message does not contain the same keys as the receiver's mask"
|
||||
);
|
||||
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
|
||||
}
|
||||
}
|
||||
impl Drop for PulseSender {
|
||||
@@ -156,62 +182,36 @@ impl Drop for PulseSender {
|
||||
|
||||
pub struct PulseReceiver {
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
pub mask: Mutex<Mask>,
|
||||
field: Weak<Field>,
|
||||
pub node: Weak<Node>,
|
||||
pub field: Arc<Field>,
|
||||
pub mask: Mask,
|
||||
}
|
||||
impl PulseReceiver {
|
||||
pub fn add_to(node: &Arc<Node>, field: Arc<Field>) -> Result<()> {
|
||||
pub fn add_to(node: &Arc<Node>, field: Arc<Field>, mask: Mask) -> Result<Arc<PulseReceiver>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
|
||||
let receiver = PulseReceiver {
|
||||
uid: node.uid.clone(),
|
||||
uid: nanoid!(),
|
||||
node: Arc::downgrade(node),
|
||||
field: Arc::downgrade(&field),
|
||||
mask: Default::default(),
|
||||
field,
|
||||
mask,
|
||||
};
|
||||
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
||||
let _ = node.pulse_receiver.set(receiver);
|
||||
node.add_local_signal("setMask", PulseReceiver::set_mask_flex);
|
||||
node.add_local_signal("sendData", PulseReceiver::send_data_flex);
|
||||
Ok(())
|
||||
}
|
||||
fn get_field(&self) -> Option<Arc<Field>> {
|
||||
self.field.upgrade()
|
||||
}
|
||||
fn send_data_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
ensure!(
|
||||
node.pulse_receiver.get().is_some(),
|
||||
"Internal: Node does not have a pulse receiver aspect"
|
||||
);
|
||||
let receiver_mask = node.pulse_receiver.get().unwrap().mask.lock();
|
||||
let data_mask = Mask {
|
||||
binary: data.to_vec(),
|
||||
get_fn: mask_get_map_at_root,
|
||||
};
|
||||
if !mask_matches(&receiver_mask, &data_mask) {
|
||||
return Err(anyhow!(
|
||||
"Message does not contain the same keys as the receiver mask"
|
||||
));
|
||||
|
||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_new_receiver(&receiver);
|
||||
}
|
||||
drop(receiver_mask);
|
||||
node.send_remote_signal("pulse", data)?;
|
||||
Ok(())
|
||||
let _ = node.pulse_receiver.set(receiver.clone());
|
||||
Ok(receiver)
|
||||
}
|
||||
fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
ensure!(
|
||||
node.pulse_receiver.get().is_some(),
|
||||
"Internal: Node does not have a pulse receiver aspect"
|
||||
);
|
||||
node.pulse_receiver
|
||||
.get()
|
||||
.unwrap()
|
||||
.mask
|
||||
.lock()
|
||||
.set_mask(data.to_vec(), mask_get_map_at_root);
|
||||
|
||||
pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> {
|
||||
if let Some(node) = self.node.upgrade() {
|
||||
node.send_remote_signal("data", &serialize(SendDataInfo { uid, data })?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -219,24 +219,19 @@ impl PulseReceiver {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "data", false);
|
||||
node.add_local_signal("createPulseSender", create_pulse_sender_flex);
|
||||
node.add_local_signal("createPulseReceiver", create_pulse_receiver_flex);
|
||||
node.add_to_scenegraph();
|
||||
node.add_local_signal("create_pulse_sender", create_pulse_sender_flex);
|
||||
node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
// pub fn mask_get_map_pulse_sender_create_args(mask: &Mask) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
// flexbuffers::Reader::get_root(mask.binary.as_slice())
|
||||
// .map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
|
||||
// .get_vector()?
|
||||
// .index(4)?
|
||||
// .get_map()
|
||||
// .map_err(|_| anyhow!("Mask is not a valid map"))
|
||||
// }
|
||||
pub fn create_pulse_sender_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
@@ -247,27 +242,22 @@ pub fn create_pulse_sender_flex(
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
mask: Vec<u8>,
|
||||
}
|
||||
let info: CreatePulseSenderInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/data/sender", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
PulseSender::add_to(&node)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
|
||||
let mask = Mask(info.mask);
|
||||
mask.get_mask()?;
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
PulseSender::add_to(&node, mask)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn mask_get_map_pulse_receiver_create_args(
|
||||
// mask: &Mask,
|
||||
// ) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||
// flexbuffers::Reader::get_root(mask.binary.as_slice())
|
||||
// .map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
|
||||
// .get_vector()?
|
||||
// .index(5)?
|
||||
// .get_map()
|
||||
// .map_err(|_| anyhow!("Mask is not a valid map"))
|
||||
// }
|
||||
pub fn create_pulse_receiver_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
@@ -279,15 +269,18 @@ pub fn create_pulse_receiver_flex(
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
mask: Vec<u8>,
|
||||
}
|
||||
let info: CreatePulseReceiverInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/data/sender", info.name, true);
|
||||
let node = Node::create(&calling_client, "/data/receiver", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let field = find_field(&calling_client, info.field_path)?;
|
||||
let mask = Mask(info.mask);
|
||||
mask.get_mask()?;
|
||||
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
PulseReceiver::add_to(&node, field)?;
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
PulseReceiver::add_to(&node, field, mask)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
157
src/nodes/drawable/lines.rs
Normal file
157
src/nodes/drawable/lines.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
nodes::{
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{bail, ensure, Result};
|
||||
use glam::Vec3A;
|
||||
use mint::Vector3;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
use prisma::{Flatten, Lerp, Rgba};
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::{schemas::flex::deserialize, values::Transform};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
|
||||
|
||||
use super::Drawable;
|
||||
|
||||
static LINES_REGISTRY: Registry<Lines> = Registry::new();
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct LinePointRaw {
|
||||
point: Vector3<f32>,
|
||||
thickness: f32,
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LineData {
|
||||
points: Vec<LinePointRaw>,
|
||||
cyclic: bool,
|
||||
}
|
||||
|
||||
pub struct Lines {
|
||||
enabled: Arc<AtomicBool>,
|
||||
space: Arc<Spatial>,
|
||||
data: Mutex<LineData>,
|
||||
}
|
||||
impl Lines {
|
||||
fn add_to(node: &Arc<Node>, points: Vec<LinePointRaw>, cyclic: bool) -> Result<Arc<Lines>> {
|
||||
ensure!(
|
||||
node.drawable.get().is_none(),
|
||||
"Internal: Node already has a drawable attached!"
|
||||
);
|
||||
|
||||
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
|
||||
let mut bounds = Bounds::default();
|
||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {return bounds};
|
||||
for point in &lines.data.lock().points {
|
||||
bounds = bounds_grow_to_fit_pt(bounds, point.point);
|
||||
}
|
||||
|
||||
bounds
|
||||
});
|
||||
|
||||
let lines = LINES_REGISTRY.add(Lines {
|
||||
enabled: node.enabled.clone(),
|
||||
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(),
|
||||
data: Mutex::new(LineData { points, cyclic }),
|
||||
});
|
||||
node.add_local_signal("set_points", Lines::set_points_flex);
|
||||
node.add_local_signal("set_cyclic", Lines::set_cyclic_flex);
|
||||
let _ = node.drawable.set(Drawable::Lines(lines.clone()));
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
|
||||
let transform_mat = self.space.global_transform();
|
||||
let data = self.data.lock().clone();
|
||||
let mut points: VecDeque<SkLinePoint> = data
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| SkLinePoint {
|
||||
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
|
||||
thickness: p.thickness,
|
||||
color: p.color.map(|c| (c * 255.0) as u8).into(),
|
||||
})
|
||||
.collect();
|
||||
if data.cyclic && !points.is_empty() {
|
||||
let first = data.points.first().unwrap();
|
||||
let last = data.points.last().unwrap();
|
||||
let color = Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5);
|
||||
let connect_point = SkLinePoint {
|
||||
pt: transform_mat
|
||||
.transform_point3a(Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5))
|
||||
.into(),
|
||||
thickness: (first.thickness + last.thickness) * 0.5,
|
||||
color: Color128::from([color.red(), color.green(), color.blue(), color.alpha()])
|
||||
.into(),
|
||||
};
|
||||
points.push_front(connect_point.clone());
|
||||
points.push_back(connect_point);
|
||||
}
|
||||
draw_ctx.line_add_listv(points.make_contiguous());
|
||||
}
|
||||
|
||||
pub fn set_points_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
let mut points: Vec<LinePointRaw> = deserialize(data)?;
|
||||
for p in &mut points {
|
||||
p.color[0] = p.color[0].powf(2.2);
|
||||
p.color[1] = p.color[1].powf(2.2);
|
||||
p.color[2] = p.color[2].powf(2.2);
|
||||
}
|
||||
lines.data.lock().points = points;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_cyclic_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
lines.data.lock().cyclic = deserialize(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Lines {
|
||||
fn drop(&mut self) {
|
||||
LINES_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
|
||||
for lines in LINES_REGISTRY.get_valid_contents() {
|
||||
if lines.enabled.load(Ordering::Relaxed) {
|
||||
lines.draw(draw_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateTextInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
points: Vec<LinePointRaw>,
|
||||
cyclic: bool,
|
||||
}
|
||||
let mut info: CreateTextInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/drawable/lines", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
|
||||
for p in &mut info.points {
|
||||
p.color[0] = p.color[0].powf(2.2);
|
||||
p.color[1] = p.color[1].powf(2.2);
|
||||
p.color[2] = p.color[2].powf(2.2);
|
||||
}
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Lines::add_to(&node, info.points, info.cyclic)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,47 +1,54 @@
|
||||
pub mod lines;
|
||||
pub mod model;
|
||||
pub mod shaders;
|
||||
pub mod text;
|
||||
|
||||
use self::{
|
||||
lines::Lines,
|
||||
model::{Model, ModelPart},
|
||||
text::Text,
|
||||
};
|
||||
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use stereokit::{lifecycle::DrawContext, texture::Texture, StereoKit};
|
||||
use stereokit::StereoKitDraw;
|
||||
use tracing::instrument;
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "drawable", false);
|
||||
node.add_local_signal("createModel", model::create_flex);
|
||||
node.add_local_signal("createText", text::create_flex);
|
||||
node.add_local_signal("setSkyFile", set_sky_file_flex);
|
||||
node.add_to_scenegraph();
|
||||
node.add_local_signal("create_lines", lines::create_flex);
|
||||
node.add_local_signal("create_model", model::create_flex);
|
||||
node.add_local_signal("create_text", text::create_flex);
|
||||
node.add_local_signal("set_sky_file", set_sky_file_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn draw(sk: &mut StereoKit, draw_ctx: &DrawContext) {
|
||||
model::draw_all(sk, draw_ctx);
|
||||
text::draw_all(sk, draw_ctx);
|
||||
pub enum Drawable {
|
||||
Lines(Arc<Lines>),
|
||||
Model(Arc<Model>),
|
||||
ModelPart(Arc<ModelPart>),
|
||||
Text(Arc<Text>),
|
||||
}
|
||||
|
||||
let new_skytex = QUEUED_SKYTEX.lock().take();
|
||||
let mut new_skylight = QUEUED_SKYLIGHT.lock().take();
|
||||
let same_file = new_skytex == new_skylight;
|
||||
#[instrument(level = "debug", skip(sk))]
|
||||
pub fn draw(sk: &impl StereoKitDraw) {
|
||||
lines::draw_all(sk);
|
||||
model::draw_all(sk);
|
||||
text::draw_all(sk);
|
||||
|
||||
if let Some(skytex) = new_skytex {
|
||||
if let Some((skytex, skylight)) =
|
||||
Texture::from_cubemap_equirectangular(sk, &skytex, true, i32::MAX)
|
||||
{
|
||||
sk.set_skytex(&skytex);
|
||||
if same_file {
|
||||
sk.set_skylight(&skylight);
|
||||
new_skylight = None;
|
||||
}
|
||||
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
|
||||
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
|
||||
sk.render_set_skytex(&skytex);
|
||||
}
|
||||
}
|
||||
if let Some(skylight) = new_skylight {
|
||||
if let Some((_, skylight)) =
|
||||
Texture::from_cubemap_equirectangular(sk, &skylight, true, i32::MAX)
|
||||
{
|
||||
sk.set_skylight(&skylight);
|
||||
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
||||
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
|
||||
sk.render_set_skylight(skylight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,273 @@
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::destroy_queue;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::resource::ResourceID;
|
||||
use crate::nodes::drawable::Drawable;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use crate::SK_MULTITHREAD;
|
||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||
use glam::Mat4;
|
||||
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use prisma::{Rgb, Rgba};
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
use rustc_hash::FxHashMap;
|
||||
use send_wrapper::SendWrapper;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use std::fmt::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use stereokit::lifecycle::DrawContext;
|
||||
use stereokit::material::Material;
|
||||
use stereokit::model::Model as SKModel;
|
||||
use stereokit::render::RenderLayer;
|
||||
use stereokit::texture::Texture;
|
||||
use stereokit::StereoKit;
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit::named_colors::WHITE;
|
||||
use stereokit::{
|
||||
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
|
||||
StereoKitMultiThread,
|
||||
};
|
||||
|
||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum MaterialParameter {
|
||||
Texture(PathBuf),
|
||||
Float(f32),
|
||||
Vector2(Vector2<f32>),
|
||||
Vector3(Vector3<f32>),
|
||||
Vector4(Vector4<f32>),
|
||||
Color([f32; 4]),
|
||||
Int(i32),
|
||||
Int2(Vector2<i32>),
|
||||
Int3(Vector3<i32>),
|
||||
Int4(Vector4<i32>),
|
||||
Bool(bool),
|
||||
UInt(u32),
|
||||
UInt2(Vector2<u32>),
|
||||
UInt3(Vector3<u32>),
|
||||
UInt4(Vector4<u32>),
|
||||
Matrix(ColumnMatrix4<f32>),
|
||||
Texture(ResourceID),
|
||||
}
|
||||
impl MaterialParameter {
|
||||
fn apply_to_material(
|
||||
&self,
|
||||
client: &Client,
|
||||
sk: &impl StereoKitMultiThread,
|
||||
material: &Material,
|
||||
parameter_name: &str,
|
||||
) {
|
||||
match self {
|
||||
MaterialParameter::Float(val) => {
|
||||
sk.material_set_float(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::Vector2(val) => {
|
||||
sk.material_set_vector2(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::Vector3(val) => {
|
||||
sk.material_set_vector3(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::Vector4(val) => {
|
||||
sk.material_set_vector4(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::Color(val) => {
|
||||
sk.material_set_color(material, parameter_name, Color128::from(val.clone()));
|
||||
}
|
||||
MaterialParameter::Int(val) => {
|
||||
sk.material_set_int(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::Int2(val) => {
|
||||
sk.material_set_int2(material, parameter_name, val.x, val.y);
|
||||
}
|
||||
MaterialParameter::Int3(val) => {
|
||||
sk.material_set_int3(material, parameter_name, val.x, val.y, val.z);
|
||||
}
|
||||
MaterialParameter::Int4(val) => {
|
||||
sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z);
|
||||
}
|
||||
MaterialParameter::Bool(val) => {
|
||||
sk.material_set_bool(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::UInt(val) => {
|
||||
sk.material_set_uint(material, parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::UInt2(val) => {
|
||||
sk.material_set_uint2(material, parameter_name, val.x, val.y);
|
||||
}
|
||||
MaterialParameter::UInt3(val) => {
|
||||
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z);
|
||||
}
|
||||
MaterialParameter::UInt4(val) => {
|
||||
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z);
|
||||
}
|
||||
MaterialParameter::Matrix(val) => {
|
||||
sk.material_set_matrix(material, parameter_name, Mat4::from(*val));
|
||||
}
|
||||
MaterialParameter::Texture(resource) => {
|
||||
let Some(texture_path) = resource.get_file(
|
||||
&client.base_resource_prefixes.lock().clone(),
|
||||
&[OsStr::new("png"), OsStr::new("jpg")],
|
||||
) else {return};
|
||||
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
|
||||
sk.material_set_texture(material, parameter_name, &tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelPart {
|
||||
id: i32,
|
||||
path: PathBuf,
|
||||
space: Arc<Spatial>,
|
||||
model: Weak<Model>,
|
||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
||||
pending_material_replacement: Mutex<Option<Arc<SendWrapper<Material>>>>,
|
||||
}
|
||||
impl ModelPart {
|
||||
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
|
||||
let first_root_part = sk.model_node_get_root(sk_model);
|
||||
let mut current_option_part = Some(first_root_part);
|
||||
|
||||
while let Some(current_part) = &mut current_option_part {
|
||||
ModelPart::create(sk, model, sk_model, *current_part);
|
||||
|
||||
if let Some(child) = sk.model_node_child(sk_model, *current_part) {
|
||||
*current_part = child;
|
||||
} else if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
|
||||
*current_part = sibling;
|
||||
} else {
|
||||
while let Some(current_part) = &mut current_option_part {
|
||||
if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
|
||||
*current_part = sibling;
|
||||
break;
|
||||
}
|
||||
current_option_part = sk.model_node_parent(sk_model, *current_part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create(
|
||||
sk: &impl StereoKitMultiThread,
|
||||
model: &Arc<Model>,
|
||||
sk_model: &SKModel,
|
||||
id: i32,
|
||||
) -> Option<Arc<Self>> {
|
||||
let parent_node = sk
|
||||
.model_node_parent(sk_model, id)
|
||||
.and_then(|id| model.parts.get(&id));
|
||||
let parent_part = parent_node
|
||||
.as_ref()
|
||||
.and_then(|node| match node.drawable.get() {
|
||||
Some(Drawable::ModelPart(model_part)) => Some(model_part),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let stardust_model_part = model.space.node()?;
|
||||
let client = stardust_model_part.get_client()?;
|
||||
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default();
|
||||
part_path.push(sk.model_node_get_name(sk_model, id)?);
|
||||
let node = client.scenegraph.add_node(Node::create(
|
||||
&client,
|
||||
stardust_model_part.get_path(),
|
||||
part_path.to_str()?,
|
||||
false,
|
||||
));
|
||||
let spatial_parent = parent_node
|
||||
.and_then(|n| n.spatial.get().cloned())
|
||||
.unwrap_or_else(|| model.space.clone());
|
||||
let space = Spatial::add_to(
|
||||
&node,
|
||||
Some(spatial_parent),
|
||||
sk.model_node_get_transform_local(sk_model, id),
|
||||
false,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
|
||||
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {return Bounds::default()};
|
||||
let Some(sk) = SK_MULTITHREAD.get() else {return Bounds::default()};
|
||||
let Some(model) = model_part.model.upgrade() else {return Bounds::default()};
|
||||
let Some(sk_model) = model.sk_model.get() else {return Bounds::default()};
|
||||
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {return Bounds::default()};
|
||||
sk.mesh_get_bounds(sk_mesh)
|
||||
});
|
||||
|
||||
let model_part = Arc::new(ModelPart {
|
||||
id,
|
||||
path: part_path,
|
||||
space,
|
||||
model: Arc::downgrade(model),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacement: Mutex::new(None),
|
||||
});
|
||||
node.add_local_signal(
|
||||
"set_material_parameter",
|
||||
ModelPart::set_material_parameter_flex,
|
||||
);
|
||||
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
|
||||
model.parts.add(id, &node);
|
||||
Some(model_part)
|
||||
}
|
||||
|
||||
fn set_material_parameter_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
let (name, value): (String, MaterialParameter) = deserialize(data)?;
|
||||
|
||||
model_part
|
||||
.pending_material_parameters
|
||||
.lock()
|
||||
.insert(name, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
|
||||
self.pending_material_replacement
|
||||
.lock()
|
||||
.replace(replacement);
|
||||
}
|
||||
|
||||
fn update(&self, sk: &impl StereoKitDraw) {
|
||||
let Some(model) = self.model.upgrade() else {return};
|
||||
let Some(sk_model) = model.sk_model.get() else {return};
|
||||
let Some(node) = model.space.node() else {return};
|
||||
let Some(client) = node.get_client() else {return};
|
||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
||||
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref());
|
||||
}
|
||||
|
||||
let mut material_parameters = self.pending_material_parameters.lock();
|
||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {continue};
|
||||
let new_material = sk.material_copy(material);
|
||||
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
|
||||
sk.model_node_set_material(sk_model, self.id, &new_material);
|
||||
}
|
||||
|
||||
sk.model_node_set_transform_model(
|
||||
sk_model,
|
||||
self.id,
|
||||
Spatial::space_to_space_matrix(Some(&self.space), Some(&model.space)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
self_ref: Weak<Model>,
|
||||
enabled: Arc<AtomicBool>,
|
||||
space: Arc<Spatial>,
|
||||
resource_id: ResourceID,
|
||||
pending_model_path: OnceCell<PathBuf>,
|
||||
pending_material_parameters: Mutex<FxHashMap<(u32, String), MaterialParameter>>,
|
||||
pub pending_material_replacements: Mutex<FxHashMap<u32, Arc<SendWrapper<Material>>>>,
|
||||
sk_model: OnceCell<SendWrapper<SKModel>>,
|
||||
_resource_id: ResourceID,
|
||||
sk_model: OnceCell<SKModel>,
|
||||
parts: LifeLinkedNodeMap<i32>,
|
||||
}
|
||||
unsafe impl Send for Model {}
|
||||
unsafe impl Sync for Model {}
|
||||
|
||||
impl Model {
|
||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
||||
@@ -47,121 +276,69 @@ impl Model {
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
ensure!(
|
||||
node.model.get().is_none(),
|
||||
"Internal: Node already has a model attached!"
|
||||
node.drawable.get().is_none(),
|
||||
"Internal: Node already has a drawable attached!"
|
||||
);
|
||||
let model = Model {
|
||||
|
||||
let pending_model_path = resource_id
|
||||
.get_file(
|
||||
&node
|
||||
.get_client()
|
||||
.ok_or_else(|| eyre!("Client not found"))?
|
||||
.base_resource_prefixes
|
||||
.lock()
|
||||
.clone(),
|
||||
&[OsStr::new("glb"), OsStr::new("gltf")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
|
||||
let model = Arc::new_cyclic(|self_ref| Model {
|
||||
self_ref: self_ref.clone(),
|
||||
enabled: node.enabled.clone(),
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
resource_id,
|
||||
pending_model_path: OnceCell::new(),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacements: Mutex::new(FxHashMap::default()),
|
||||
_resource_id: resource_id,
|
||||
sk_model: OnceCell::new(),
|
||||
};
|
||||
node.add_local_signal("setMaterialParameter", Model::set_material_parameter);
|
||||
let model_arc = MODEL_REGISTRY.add(model);
|
||||
let _ = model_arc.pending_model_path.set(
|
||||
model_arc
|
||||
.resource_id
|
||||
.get_file(
|
||||
&node
|
||||
.get_client()
|
||||
.ok_or_else(|| anyhow!("Client not found"))?
|
||||
.base_resource_prefixes
|
||||
.lock()
|
||||
.clone(),
|
||||
)
|
||||
.ok_or_else(|| anyhow!("Resource not found"))?,
|
||||
parts: LifeLinkedNodeMap::default(),
|
||||
});
|
||||
MODEL_REGISTRY.add_raw(&model);
|
||||
|
||||
let sk = SK_MULTITHREAD.get().unwrap();
|
||||
let sk_model = sk.model_copy(
|
||||
sk.model_create_file(pending_model_path.to_str().unwrap(), None::<Shader>)?,
|
||||
);
|
||||
let _ = node.model.set(model_arc.clone());
|
||||
Ok(model_arc)
|
||||
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
|
||||
let _ = model.sk_model.set(sk_model);
|
||||
let _ = node.drawable.set(Drawable::Model(model.clone()));
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
fn set_material_parameter(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct MaterialParameterInfo {
|
||||
idx: u32,
|
||||
name: String,
|
||||
value: MaterialParameter,
|
||||
fn draw(&self, sk: &impl StereoKitDraw) {
|
||||
let Some(sk_model) = self.sk_model.get() else {return};
|
||||
for model_node_node in self.parts.nodes() {
|
||||
let Some(Drawable::ModelPart(model_node)) = model_node_node.drawable.get() else {continue};
|
||||
model_node.update(sk);
|
||||
}
|
||||
let info: MaterialParameterInfo = deserialize(data)?;
|
||||
|
||||
node.model
|
||||
.get()
|
||||
.unwrap()
|
||||
.pending_material_parameters
|
||||
.lock()
|
||||
.insert((info.idx, info.name), info.value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, sk: &StereoKit, draw_ctx: &DrawContext) {
|
||||
let sk_model = self
|
||||
.sk_model
|
||||
.get_or_try_init(|| {
|
||||
self.pending_model_path
|
||||
.get()
|
||||
.and_then(|path| SKModel::from_file(sk, path.as_path(), None))
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.map(SendWrapper::new)
|
||||
.ok_or(Error)
|
||||
})
|
||||
.ok();
|
||||
|
||||
if let Some(sk_model) = sk_model {
|
||||
{
|
||||
let mut material_replacements = self.pending_material_replacements.lock();
|
||||
for (material_idx, replacement_material) in material_replacements.iter() {
|
||||
sk_model.set_material(*material_idx as i32, replacement_material);
|
||||
}
|
||||
material_replacements.clear();
|
||||
}
|
||||
|
||||
{
|
||||
let mut material_parameters = self.pending_material_parameters.lock();
|
||||
for ((material_idx, parameter_name), parameter_value) in material_parameters.iter()
|
||||
{
|
||||
if let Some(material) = sk_model.get_material(sk, *material_idx as i32) {
|
||||
match parameter_value {
|
||||
MaterialParameter::Texture(path) => {
|
||||
if let Some(tex) = Texture::from_file(sk, path.as_path(), true, 0) {
|
||||
material.set_parameter(parameter_name.as_str(), &tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
material_parameters.clear();
|
||||
}
|
||||
|
||||
let global_transform = self.space.global_transform().into();
|
||||
sk_model.draw(
|
||||
draw_ctx,
|
||||
global_transform,
|
||||
Rgba::new(Rgb::new(1_f32, 1_f32, 1_f32), 1_f32),
|
||||
RenderLayer::Layer0,
|
||||
);
|
||||
}
|
||||
sk.model_draw(
|
||||
sk_model,
|
||||
self.space.global_transform(),
|
||||
WHITE,
|
||||
RenderLayer::LAYER0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Model {
|
||||
fn drop(&mut self) {
|
||||
if let Some(model) = self.sk_model.take() {
|
||||
destroy_queue::add(model);
|
||||
}
|
||||
MODEL_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) {
|
||||
pub fn draw_all(sk: &impl StereoKitDraw) {
|
||||
for model in MODEL_REGISTRY.get_valid_contents() {
|
||||
model.draw(sk, draw_ctx);
|
||||
if model.enabled.load(Ordering::Relaxed) {
|
||||
model.draw(sk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +353,9 @@ pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Re
|
||||
let info: CreateModelInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/drawable/model", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Model::add_to(&node, info.resource)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
139
src/nodes/drawable/shaders.rs
Normal file
139
src/nodes/drawable/shaders.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
||||
GlesError,
|
||||
};
|
||||
use std::mem::transmute;
|
||||
use stereokit::Shader;
|
||||
use tracing::error;
|
||||
|
||||
struct FfiAssetHeader {
|
||||
asset_type: i32,
|
||||
asset_state: i32,
|
||||
id: u64,
|
||||
index: u64,
|
||||
refs: i32,
|
||||
debug: *mut u8,
|
||||
}
|
||||
|
||||
struct FfiSkgShader {
|
||||
meta: *mut u8,
|
||||
vertex: u32,
|
||||
pixel: u32,
|
||||
program: u32,
|
||||
compute: u32,
|
||||
}
|
||||
|
||||
struct FfiShader {
|
||||
header: FfiAssetHeader,
|
||||
shader: FfiSkgShader,
|
||||
}
|
||||
|
||||
unsafe fn compile_shader(
|
||||
gl: &ffi::Gles2,
|
||||
variant: ffi::types::GLuint,
|
||||
src: &str,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let shader = gl.CreateShader(variant);
|
||||
if shader == 0 {
|
||||
return Err(GlesError::CreateShaderObject);
|
||||
}
|
||||
|
||||
gl.ShaderSource(
|
||||
shader,
|
||||
1,
|
||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
||||
&(src.len() as i32) as *const _,
|
||||
);
|
||||
gl.CompileShader(shader);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetShaderInfoLog(
|
||||
shader,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteShader(shader);
|
||||
return Err(GlesError::ShaderCompileError);
|
||||
}
|
||||
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
unsafe fn link_program(
|
||||
gl: &ffi::Gles2,
|
||||
vert: ffi::types::GLuint,
|
||||
frag: ffi::types::GLuint,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let program = gl.CreateProgram();
|
||||
gl.AttachShader(program, vert);
|
||||
gl.AttachShader(program, frag);
|
||||
gl.LinkProgram(program);
|
||||
// gl.DetachShader(program, vert);
|
||||
// gl.DetachShader(program, frag);
|
||||
// gl.DeleteShader(vert);
|
||||
// gl.DeleteShader(frag);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetProgramInfoLog(
|
||||
program,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteProgram(program);
|
||||
return Err(GlesError::ProgramLinkError);
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
pub unsafe fn shader_inject(
|
||||
c: &Gles2,
|
||||
sk_shader: &mut Shader,
|
||||
vert_str: &str,
|
||||
frag_str: &str,
|
||||
) -> Result<(), GlesError> {
|
||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
||||
|
||||
let shader: *mut FfiShader = transmute(sk_shader.0.as_mut());
|
||||
if let Some(shader) = shader.as_mut() {
|
||||
shader.shader.vertex = gl_vert;
|
||||
shader.shader.pixel = gl_frag;
|
||||
shader.shader.program = gl_prog;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
use crate::{
|
||||
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
|
||||
nodes::{
|
||||
drawable::Drawable,
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||
use glam::{vec3, Mat4, Vec2};
|
||||
use mint::Vector2;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use prisma::{Flatten, Rgb, Rgba};
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
use prisma::{Flatten, Rgba};
|
||||
use send_wrapper::SendWrapper;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::{schemas::flex::deserialize, values::Transform};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use stereokit::{
|
||||
font::Font,
|
||||
lifecycle::DrawContext,
|
||||
text::{self, TextAlign, TextFit, TextStyle},
|
||||
StereoKit,
|
||||
};
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle};
|
||||
|
||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||
|
||||
@@ -35,6 +32,7 @@ struct TextData {
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
enabled: Arc<AtomicBool>,
|
||||
space: Arc<Spatial>,
|
||||
font_path: Option<PathBuf>,
|
||||
style: OnceCell<SendWrapper<TextStyle>>,
|
||||
@@ -59,17 +57,20 @@ impl Text {
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
ensure!(
|
||||
node.model.get().is_none(),
|
||||
"Internal: Node already has text attached!"
|
||||
node.drawable.get().is_none(),
|
||||
"Internal: Node already has a drawable attached!"
|
||||
);
|
||||
|
||||
let client = node
|
||||
.get_client()
|
||||
.ok_or_else(|| anyhow!("Client not found"))?;
|
||||
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
|
||||
let text = TEXT_REGISTRY.add(Text {
|
||||
enabled: node.enabled.clone(),
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
font_path: font_resource_id
|
||||
.and_then(|res| res.get_file(&client.base_resource_prefixes.lock().clone())),
|
||||
font_path: font_resource_id.and_then(|res| {
|
||||
res.get_file(
|
||||
&client.base_resource_prefixes.lock().clone(),
|
||||
&[OsStr::new("ttf"), OsStr::new("otf")],
|
||||
)
|
||||
}),
|
||||
style: OnceCell::new(),
|
||||
|
||||
data: Mutex::new(TextData {
|
||||
@@ -82,29 +83,26 @@ impl Text {
|
||||
color,
|
||||
}),
|
||||
});
|
||||
node.add_local_signal("setCharacterHeight", Text::set_character_height_flex);
|
||||
node.add_local_signal("setText", Text::set_text_flex);
|
||||
let _ = node.text.set(text.clone());
|
||||
node.add_local_signal("set_character_height", Text::set_character_height_flex);
|
||||
node.add_local_signal("set_text", Text::set_text_flex);
|
||||
let _ = node.drawable.set(Drawable::Text(text.clone()));
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
fn draw(&self, sk: &StereoKit, draw_ctx: &DrawContext) {
|
||||
let style =
|
||||
self.style
|
||||
.get_or_try_init(|| -> Result<SendWrapper<TextStyle>, anyhow::Error> {
|
||||
let font = if let Some(path) = self.font_path.as_deref() {
|
||||
Font::from_file(sk, path)
|
||||
} else {
|
||||
Some(Font::default(sk))
|
||||
};
|
||||
Ok(SendWrapper::new(TextStyle::new(
|
||||
sk,
|
||||
font.ok_or(std::fmt::Error)?,
|
||||
1.0,
|
||||
Rgba::new(Rgb::new(1.0, 1.0, 1.0), 1.0),
|
||||
)))
|
||||
});
|
||||
fn draw(&self, sk: &impl StereoKitDraw) {
|
||||
let style = self.style.get_or_try_init(
|
||||
|| -> Result<SendWrapper<TextStyle>, color_eyre::eyre::Error> {
|
||||
let font = self
|
||||
.font_path
|
||||
.as_deref()
|
||||
.and_then(|path| sk.font_create(path).ok())
|
||||
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
|
||||
Ok(SendWrapper::new(unsafe {
|
||||
sk.text_make_style(font, 1.0, WHITE)
|
||||
}))
|
||||
},
|
||||
);
|
||||
|
||||
if let Ok(style) = style {
|
||||
let data = self.data.lock();
|
||||
@@ -115,28 +113,36 @@ impl Text {
|
||||
data.character_height,
|
||||
));
|
||||
if let Some(bounds) = data.bounds {
|
||||
text::draw_in(
|
||||
draw_ctx,
|
||||
sk.text_add_in(
|
||||
&data.text,
|
||||
transform,
|
||||
bounds / data.character_height,
|
||||
data.fit,
|
||||
style,
|
||||
**style,
|
||||
data.bounds_align,
|
||||
data.text_align,
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
data.color,
|
||||
Color128::from([
|
||||
data.color.red(),
|
||||
data.color.green(),
|
||||
data.color.blue(),
|
||||
data.color.alpha(),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
text::draw_at(
|
||||
draw_ctx,
|
||||
sk.text_add_at(
|
||||
&data.text,
|
||||
transform,
|
||||
style,
|
||||
**style,
|
||||
data.bounds_align,
|
||||
data.text_align,
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
data.color,
|
||||
Color128::from([
|
||||
data.color.red(),
|
||||
data.color.green(),
|
||||
data.color.blue(),
|
||||
data.color.alpha(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -147,14 +153,16 @@ impl Text {
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let height = flexbuffers::Reader::get_root(data)?.get_f64()? as f32;
|
||||
node.text.get().unwrap().data.lock().character_height = height;
|
||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
text.data.lock().character_height = deserialize(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_text_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let text = flexbuffers::Reader::get_root(data)?.get_str()?.to_string();
|
||||
node.text.get().unwrap().data.lock().text = text;
|
||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
text.data.lock().text = deserialize(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -167,9 +175,11 @@ impl Drop for Text {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) {
|
||||
pub fn draw_all(sk: &impl StereoKitDraw) {
|
||||
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||
text.draw(sk, draw_ctx);
|
||||
if text.enabled.load(Ordering::Relaxed) {
|
||||
text.draw(sk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,11 +201,11 @@ pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Re
|
||||
let info: CreateTextInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let color = Rgba::from_slice(&info.color);
|
||||
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Text::add_to(
|
||||
&node,
|
||||
info.font_resource,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use anyhow::{ensure, Result};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{vec3, vec3a, Vec3, Vec3A};
|
||||
use mint::Vector3;
|
||||
use parking_lot::Mutex;
|
||||
@@ -16,7 +16,7 @@ pub struct BoxField {
|
||||
}
|
||||
|
||||
impl BoxField {
|
||||
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<()> {
|
||||
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<Arc<Field>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
@@ -30,9 +30,10 @@ impl BoxField {
|
||||
size: Mutex::new(size.into()),
|
||||
};
|
||||
box_field.add_field_methods(node);
|
||||
node.add_local_signal("setSize", BoxField::set_size_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Box(box_field)));
|
||||
Ok(())
|
||||
node.add_local_signal("set_size", BoxField::set_size_flex);
|
||||
let field = Arc::new(Field::Box(box_field));
|
||||
let _ = node.field.set(field.clone());
|
||||
Ok(field)
|
||||
}
|
||||
|
||||
pub fn set_size(&self, size: Vector3<f32>) {
|
||||
@@ -40,9 +41,9 @@ impl BoxField {
|
||||
}
|
||||
|
||||
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
if let Field::Box(box_field) = node.field.get().unwrap().as_ref() {
|
||||
box_field.set_size(deserialize(data)?);
|
||||
}
|
||||
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
box_field.set_size(deserialize(data)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -74,9 +75,9 @@ pub fn create_box_field_flex(_node: &Node, calling_client: Arc<Client>, data: &[
|
||||
let info: CreateFieldInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
BoxField::add_to(&node, info.size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use anyhow::{ensure, Result};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{swizzles::*, vec2, Vec3A};
|
||||
use portable_atomic::AtomicF32;
|
||||
use serde::Deserialize;
|
||||
@@ -33,7 +33,7 @@ impl CylinderField {
|
||||
radius: AtomicF32::new(radius.abs()),
|
||||
};
|
||||
cylinder_field.add_field_methods(node);
|
||||
node.add_local_signal("setSize", CylinderField::set_size_flex);
|
||||
node.add_local_signal("set_size", CylinderField::set_size_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field)));
|
||||
Ok(())
|
||||
}
|
||||
@@ -44,10 +44,9 @@ impl CylinderField {
|
||||
}
|
||||
|
||||
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
if let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() {
|
||||
let (length, radius) = deserialize(data)?;
|
||||
cylinder_field.set_size(length, radius);
|
||||
}
|
||||
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
let (length, radius) = deserialize(data)?;
|
||||
cylinder_field.set_size(length, radius);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -81,9 +80,9 @@ pub fn create_cylinder_field_flex(
|
||||
let info: CreateFieldInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
CylinderField::add_to(&node, dbg!(info.length), dbg!(info.radius))?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
CylinderField::add_to(&node, info.length, info.radius)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
mod r#box;
|
||||
pub mod r#box;
|
||||
mod cylinder;
|
||||
mod sphere;
|
||||
mod torus;
|
||||
|
||||
use self::cylinder::{create_cylinder_field_flex, CylinderField};
|
||||
use self::r#box::{create_box_field_flex, BoxField};
|
||||
use self::sphere::{create_sphere_field_flex, SphereField};
|
||||
use self::torus::{create_torus_field_flex, TorusField};
|
||||
|
||||
use super::alias::AliasInfo;
|
||||
use super::spatial::Spatial;
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::find_reference_space;
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec2, vec3a, Vec3, Vec3A};
|
||||
use mint::Vector3;
|
||||
use serde::Deserialize;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
|
||||
server_methods: vec!["distance", "normal", "closest_point", "ray_march"],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
pub trait FieldTrait {
|
||||
fn add_field_methods(&self, node: &Arc<Node>) {
|
||||
node.add_local_method("distance", field_distance_flex);
|
||||
node.add_local_method("normal", field_normal_flex);
|
||||
node.add_local_method("closest_point", field_closest_point_flex);
|
||||
node.add_local_method("ray_march", field_ray_march_flex);
|
||||
}
|
||||
fn spatial_ref(&self) -> &Spatial;
|
||||
|
||||
fn local_distance(&self, p: Vec3A) -> f32;
|
||||
fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
|
||||
let d = self.local_distance(p);
|
||||
@@ -61,15 +78,61 @@ pub trait FieldTrait {
|
||||
.transform_point3a(self.local_closest_point(local_p, r))
|
||||
}
|
||||
|
||||
fn add_field_methods(&self, node: &Arc<Node>) {
|
||||
node.add_local_method("distance", field_distance_flex);
|
||||
node.add_local_method("normal", field_normal_flex);
|
||||
node.add_local_method("closest_point", field_closest_point_flex);
|
||||
}
|
||||
fn ray_march(&self, ray: Ray) -> RayMarchResult {
|
||||
let mut result = RayMarchResult {
|
||||
min_distance: f32::MAX,
|
||||
deepest_point_distance: 0_f32,
|
||||
ray_length: 0_f32,
|
||||
ray_steps: 0,
|
||||
};
|
||||
|
||||
fn spatial_ref(&self) -> &Spatial;
|
||||
let ray_to_field_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
|
||||
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
|
||||
let ray_direction = ray_to_field_matrix.transform_vector3a(ray.direction.into());
|
||||
|
||||
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
|
||||
let distance = self.local_distance(ray_point);
|
||||
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
|
||||
|
||||
result.ray_length += march_distance;
|
||||
ray_point += ray_direction * march_distance;
|
||||
|
||||
if result.min_distance > distance {
|
||||
result.deepest_point_distance = result.ray_length;
|
||||
result.min_distance = distance;
|
||||
}
|
||||
|
||||
result.ray_steps += 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub space: Arc<Spatial>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RayMarchResult {
|
||||
pub min_distance: f32,
|
||||
pub deepest_point_distance: f32,
|
||||
pub ray_length: f32,
|
||||
pub ray_steps: u32,
|
||||
}
|
||||
|
||||
// const MIN_RAY_STEPS: u32 = 0;
|
||||
const MAX_RAY_STEPS: u32 = 1000;
|
||||
|
||||
const MIN_RAY_MARCH: f32 = 0.001_f32;
|
||||
const MAX_RAY_MARCH: f32 = f32::MAX;
|
||||
|
||||
// const MIN_RAY_LENGTH: f32 = 0_f32;
|
||||
const MAX_RAY_LENGTH: f32 = 1000_f32;
|
||||
|
||||
fn field_distance_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
@@ -124,11 +187,29 @@ fn field_closest_point_flex(
|
||||
);
|
||||
Ok(serialize(mint::Vector3::from(closest_point))?)
|
||||
}
|
||||
fn field_ray_march_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
reference_space_path: &'a str,
|
||||
ray_origin: Vector3<f32>,
|
||||
ray_direction: Vector3<f32>,
|
||||
}
|
||||
let args: FieldInfoArgs = deserialize(data)?;
|
||||
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
|
||||
|
||||
let ray_march_result = node.field.get().unwrap().ray_march(Ray {
|
||||
origin: args.ray_origin.into(),
|
||||
direction: args.ray_direction.into(),
|
||||
space: reference_space,
|
||||
});
|
||||
Ok(serialize(ray_march_result)?)
|
||||
}
|
||||
|
||||
pub enum Field {
|
||||
Box(BoxField),
|
||||
Cylinder(CylinderField),
|
||||
Sphere(SphereField),
|
||||
Torus(TorusField),
|
||||
}
|
||||
|
||||
impl Deref for Field {
|
||||
@@ -138,75 +219,23 @@ impl Deref for Field {
|
||||
Field::Box(field) => field,
|
||||
Field::Cylinder(field) => field,
|
||||
Field::Sphere(field) => field,
|
||||
Field::Torus(field) => field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "field", false);
|
||||
node.add_local_signal("createBoxField", create_box_field_flex);
|
||||
node.add_local_signal("createCylinderField", create_cylinder_field_flex);
|
||||
node.add_local_signal("createSphereField", create_sphere_field_flex);
|
||||
node.add_to_scenegraph();
|
||||
}
|
||||
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub space: Arc<Spatial>,
|
||||
}
|
||||
|
||||
pub struct RayMarchResult {
|
||||
pub ray: Ray,
|
||||
pub distance: f32,
|
||||
pub deepest_point_distance: f32,
|
||||
pub ray_length: f32,
|
||||
pub ray_steps: u32,
|
||||
}
|
||||
|
||||
// const MIN_RAY_STEPS: u32 = 0;
|
||||
const MAX_RAY_STEPS: u32 = 1000;
|
||||
|
||||
const MIN_RAY_MARCH: f32 = 0.001_f32;
|
||||
const MAX_RAY_MARCH: f32 = f32::MAX;
|
||||
|
||||
// const MIN_RAY_LENGTH: f32 = 0_f32;
|
||||
const MAX_RAY_LENGTH: f32 = 1000_f32;
|
||||
|
||||
pub fn ray_march(ray: Ray, field: &Field) -> RayMarchResult {
|
||||
let mut result = RayMarchResult {
|
||||
ray,
|
||||
distance: f32::MAX,
|
||||
deepest_point_distance: 0_f32,
|
||||
ray_length: 0_f32,
|
||||
ray_steps: 0,
|
||||
};
|
||||
|
||||
let ray_to_field_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&result.ray.space), Some(field.spatial_ref()));
|
||||
let mut ray_point = ray_to_field_matrix.transform_point3a(result.ray.origin.into());
|
||||
let ray_direction = ray_to_field_matrix.transform_vector3a(result.ray.direction.into());
|
||||
|
||||
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
|
||||
let distance = field.local_distance(ray_point);
|
||||
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
|
||||
|
||||
result.ray_length += march_distance;
|
||||
ray_point += ray_direction * march_distance;
|
||||
|
||||
if result.distance > distance {
|
||||
result.deepest_point_distance = result.ray_length;
|
||||
}
|
||||
result.distance = distance.min(result.distance);
|
||||
|
||||
result.ray_steps += 1;
|
||||
}
|
||||
|
||||
result
|
||||
node.add_local_signal("create_box_field", create_box_field_flex);
|
||||
node.add_local_signal("create_cylinder_field", create_cylinder_field_flex);
|
||||
node.add_local_signal("create_sphere_field", create_sphere_field_flex);
|
||||
node.add_local_signal("create_torus_field", create_torus_field_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn find_field(client: &Client, path: &str) -> Result<Arc<Field>> {
|
||||
Ok(client
|
||||
client
|
||||
.get_node("Field", path)?
|
||||
.get_aspect("Field", "info", |n| &n.field)?)
|
||||
.get_aspect("Field", "info", |n| &n.field)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, Spatial};
|
||||
use anyhow::{ensure, Result};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{Mat4, Vec3A};
|
||||
use mint::Vector3;
|
||||
use portable_atomic::AtomicF32;
|
||||
@@ -30,7 +30,7 @@ impl SphereField {
|
||||
radius: AtomicF32::new(radius),
|
||||
};
|
||||
sphere_field.add_field_methods(node);
|
||||
node.add_local_signal("setRadius", SphereField::set_radius_flex);
|
||||
node.add_local_signal("set_radius", SphereField::set_radius_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Sphere(sphere_field)));
|
||||
Ok(())
|
||||
}
|
||||
@@ -40,10 +40,8 @@ impl SphereField {
|
||||
}
|
||||
|
||||
pub fn set_radius_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let radius = flexbuffers::Reader::get_root(data)?.get_f64()? as f32;
|
||||
if let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() {
|
||||
sphere_field.set_radius(radius);
|
||||
}
|
||||
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
sphere_field.set_radius(deserialize(data)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -83,8 +81,8 @@ pub fn create_sphere_field_flex(
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]))
|
||||
.into(),
|
||||
);
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
SphereField::add_to(&node, info.radius)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
88
src/nodes/fields/torus.rs
Normal file
88
src/nodes/fields/torus.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{swizzles::*, vec2, Vec3A};
|
||||
use portable_atomic::AtomicF32;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct TorusField {
|
||||
space: Arc<Spatial>,
|
||||
radius_a: AtomicF32,
|
||||
radius_b: AtomicF32,
|
||||
}
|
||||
|
||||
impl TorusField {
|
||||
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) -> Result<()> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
ensure!(
|
||||
node.field.get().is_none(),
|
||||
"Internal: Node already has a field attached!"
|
||||
);
|
||||
let torus_field = TorusField {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
radius_a: AtomicF32::new(radius_a.abs()),
|
||||
radius_b: AtomicF32::new(radius_b.abs()),
|
||||
};
|
||||
torus_field.add_field_methods(node);
|
||||
node.add_local_signal("set_size", TorusField::set_size_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Torus(torus_field)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_size(&self, radius_a: f32, radius_b: f32) {
|
||||
self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
|
||||
self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
let (radius_a, radius_b) = deserialize(data)?;
|
||||
torus_field.set_size(radius_a, radius_b);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldTrait for TorusField {
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
let radius_a = self.radius_a.load(Ordering::Relaxed);
|
||||
let radius_b = self.radius_b.load(Ordering::Relaxed);
|
||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||
q.length() - radius_b
|
||||
}
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
self.space.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_torus_field_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateFieldInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
radius_a: f32,
|
||||
radius_b: f32,
|
||||
}
|
||||
let info: CreateFieldInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
TorusField::add_to(&node, info.radius_a, info.radius_b)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,9 +3,11 @@ use crate::{
|
||||
core::client::{Client, INTERNAL_CLIENT},
|
||||
nodes::alias::AliasInfo,
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3, Mat4};
|
||||
use std::sync::Arc;
|
||||
use stereokit::StereoKit;
|
||||
use stereokit::StereoKitMultiThread;
|
||||
use tracing::instrument;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HMD: Arc<Node> = create();
|
||||
@@ -13,13 +15,17 @@ lazy_static::lazy_static! {
|
||||
|
||||
fn create() -> Arc<Node> {
|
||||
let node = Arc::new(Node::create(&INTERNAL_CLIENT, "", "hmd", false));
|
||||
Spatial::add_to(&node, None, Mat4::IDENTITY).unwrap();
|
||||
Spatial::add_to(&node, None, Mat4::IDENTITY, false).expect("Unable to make spatial for HMD");
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
pub fn frame(sk: &StereoKit) {
|
||||
let spatial = HMD.spatial.get().unwrap();
|
||||
#[instrument(level = "debug", name = "Update HMD Pose", skip(sk))]
|
||||
pub fn frame(sk: &impl StereoKitMultiThread) {
|
||||
let spatial = HMD
|
||||
.spatial
|
||||
.get()
|
||||
.expect("Unable to get spatial to update HMD");
|
||||
let hmd_pose = sk.input_head();
|
||||
*spatial.transform.lock() = Mat4::from_scale_rotation_translation(
|
||||
vec3(1.0, 1.0, 1.0),
|
||||
@@ -28,14 +34,14 @@ pub fn frame(sk: &StereoKit) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn make_alias(client: &Arc<Client>) -> Arc<Node> {
|
||||
Alias::new(
|
||||
pub fn make_alias(client: &Arc<Client>) -> Result<Arc<Node>> {
|
||||
Alias::create(
|
||||
client,
|
||||
"",
|
||||
"hmd",
|
||||
&HMD,
|
||||
AliasInfo {
|
||||
local_signals: vec!["getTransform"],
|
||||
server_signals: vec!["get_bounds", "get_transform"],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -11,7 +11,10 @@ pub struct Hand {
|
||||
pub base: FlatHand,
|
||||
}
|
||||
impl InputSpecialization for Hand {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
self.true_distance(space, field).abs()
|
||||
}
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
let mut min_distance = f32::MAX;
|
||||
|
||||
for tip in [
|
||||
@@ -60,9 +63,8 @@ impl InputSpecialization for Hand {
|
||||
]);
|
||||
|
||||
for joint in joints {
|
||||
let joint_matrix =
|
||||
Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into())
|
||||
* local_to_handler_matrix;
|
||||
let joint_matrix = local_to_handler_matrix
|
||||
* Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into());
|
||||
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
|
||||
joint.position = position.into();
|
||||
joint.rotation = rotation.into();
|
||||
|
||||
@@ -6,30 +6,37 @@ use self::hand::Hand;
|
||||
use self::pointer::Pointer;
|
||||
use self::tip::Tip;
|
||||
|
||||
use super::fields::Field;
|
||||
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::eventloop::FRAME;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::fields::find_field;
|
||||
use anyhow::{ensure, Result};
|
||||
use super::{
|
||||
alias::{Alias, AliasInfo},
|
||||
fields::{find_field, Field, FIELD_ALIAS_INFO},
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Node,
|
||||
};
|
||||
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
|
||||
use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::Mat4;
|
||||
use nanoid::nanoid;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::AtomicBool;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flat::{Datamap, InputDataType};
|
||||
use stardust_xr::schemas::{flat::InputData, flex::deserialize};
|
||||
use stardust_xr::schemas::{
|
||||
flat::{Datamap, InputDataType},
|
||||
flex::serialize,
|
||||
};
|
||||
use stardust_xr::values::Transform;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::{debug_span, instrument};
|
||||
|
||||
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
|
||||
static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
|
||||
|
||||
pub trait InputSpecialization: Send + Sync {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||
fn serialize(
|
||||
&self,
|
||||
distance_link: &DistanceLink,
|
||||
@@ -53,63 +60,133 @@ impl Deref for InputType {
|
||||
}
|
||||
|
||||
pub struct InputMethod {
|
||||
pub uid: String,
|
||||
node: Weak<Node>,
|
||||
uid: String,
|
||||
pub enabled: Mutex<bool>,
|
||||
pub spatial: Arc<Spatial>,
|
||||
pub specialization: Mutex<InputType>,
|
||||
pub captures: Registry<InputHandler>,
|
||||
captures: Registry<InputHandler>,
|
||||
pub datamap: Mutex<Option<Datamap>>,
|
||||
handler_aliases: LifeLinkedNodeMap<String>,
|
||||
handler_order: OnceCell<Mutex<Vec<Weak<InputHandler>>>>,
|
||||
}
|
||||
impl InputMethod {
|
||||
pub fn new(spatial: Arc<Spatial>, specialization: InputType) -> Arc<InputMethod> {
|
||||
let method = InputMethod {
|
||||
uid: nanoid!(),
|
||||
enabled: Mutex::new(true),
|
||||
spatial,
|
||||
specialization: Mutex::new(specialization),
|
||||
captures: Registry::new(),
|
||||
datamap: Mutex::new(None),
|
||||
};
|
||||
INPUT_METHOD_REGISTRY.add(method)
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
specialization: InputType,
|
||||
datamap: Option<Datamap>,
|
||||
) -> Result<()> {
|
||||
) -> Result<Arc<InputMethod>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
|
||||
node.add_local_signal("setDatamap", InputMethod::set_datamap);
|
||||
node.add_local_signal("capture", InputMethod::capture_flex);
|
||||
node.add_local_signal("set_datamap", InputMethod::set_datamap_flex);
|
||||
node.add_local_signal("set_handlers", InputMethod::set_handlers_flex);
|
||||
|
||||
let method = InputMethod {
|
||||
node: Arc::downgrade(node),
|
||||
uid: node.uid.clone(),
|
||||
enabled: Mutex::new(true),
|
||||
spatial: node.spatial.get().unwrap().clone(),
|
||||
specialization: Mutex::new(specialization),
|
||||
captures: Registry::new(),
|
||||
datamap: Mutex::new(datamap),
|
||||
handler_aliases: LifeLinkedNodeMap::default(),
|
||||
handler_order: OnceCell::new(),
|
||||
};
|
||||
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||
method.handle_new_handler(&handler);
|
||||
}
|
||||
let method = INPUT_METHOD_REGISTRY.add(method);
|
||||
let _ = node.input_method.set(method);
|
||||
let _ = node.input_method.set(method.clone());
|
||||
Ok(method)
|
||||
}
|
||||
fn get(node: &Node) -> Result<Arc<Self>> {
|
||||
node.get_aspect("Input Method", "input method", |n| &n.input_method)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
let handler = InputHandler::find(&calling_client, deserialize(data)?)?;
|
||||
|
||||
method.captures.add_raw(&handler);
|
||||
node.send_remote_signal("capture", data)
|
||||
}
|
||||
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
method.datamap.lock().replace(Datamap::new(data.to_vec())?);
|
||||
Ok(())
|
||||
}
|
||||
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
let handler_paths: Vec<&str> = deserialize(data)?;
|
||||
let handlers: Vec<Weak<InputHandler>> = handler_paths
|
||||
.into_iter()
|
||||
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
|
||||
.map(|h| Arc::downgrade(&h))
|
||||
.collect();
|
||||
|
||||
*method
|
||||
.handler_order
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock() = handlers;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_datamap(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
node.input_method
|
||||
.get()
|
||||
.unwrap()
|
||||
.datamap
|
||||
fn compare_distance(&self, to: &Field) -> f32 {
|
||||
self.specialization
|
||||
.lock()
|
||||
.replace(Datamap::new(data.to_vec())?);
|
||||
Ok(())
|
||||
.compare_distance(&self.spatial, to)
|
||||
}
|
||||
fn true_distance(&self, to: &Field) -> f32 {
|
||||
self.specialization.lock().true_distance(&self.spatial, to)
|
||||
}
|
||||
|
||||
fn distance(&self, to: &Field) -> f32 {
|
||||
self.specialization.lock().distance(&self.spatial, to)
|
||||
fn handle_new_handler(&self, handler: &InputHandler) {
|
||||
let Some(method_node) = self.node.upgrade() else { return };
|
||||
let Some(method_client) = method_node.get_client() else { return };
|
||||
let Some(handler_node) = handler.node.upgrade() else { return };
|
||||
// Receiver itself
|
||||
let Ok(handler_alias) = Alias::create(
|
||||
&method_client,
|
||||
method_node.get_path(),
|
||||
handler.uid.as_str(),
|
||||
&handler_node,
|
||||
AliasInfo {
|
||||
server_methods: vec!["getTransform"],
|
||||
..Default::default()
|
||||
},
|
||||
) else {return};
|
||||
self.handler_aliases
|
||||
.add(handler.uid.clone(), &handler_alias);
|
||||
|
||||
if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() {
|
||||
// Handler's field
|
||||
let Ok(rx_field_alias) = Alias::create(
|
||||
&method_client,
|
||||
handler_alias.get_path(),
|
||||
"field",
|
||||
&handler_field_node,
|
||||
FIELD_ALIAS_INFO.clone(),
|
||||
) else {return};
|
||||
self.handler_aliases
|
||||
.add(handler.uid.clone() + "-field", &rx_field_alias);
|
||||
}
|
||||
|
||||
let Ok(data) = serialize(&handler.uid) else {return};
|
||||
let _ = method_node.send_remote_signal("handler_created", &data);
|
||||
}
|
||||
fn handle_drop_handler(&self, handler: &InputHandler) {
|
||||
let uid = handler.uid.as_str();
|
||||
self.handler_aliases.remove(uid);
|
||||
self.handler_aliases.remove(&(uid.to_string() + "-field"));
|
||||
let Some(tx_node) = self.node.upgrade() else {return};
|
||||
let Ok(data) = serialize(&uid) else {return};
|
||||
let _ = tx_node.send_remote_signal("handler_destroyed", &data);
|
||||
}
|
||||
}
|
||||
impl Drop for InputMethod {
|
||||
@@ -119,26 +196,37 @@ impl Drop for InputMethod {
|
||||
}
|
||||
|
||||
pub struct DistanceLink {
|
||||
pub distance: f32,
|
||||
pub method: Arc<InputMethod>,
|
||||
pub handler: Arc<InputHandler>,
|
||||
pub handler_field: Arc<Field>,
|
||||
distance: f32,
|
||||
method: Arc<InputMethod>,
|
||||
handler: Arc<InputHandler>,
|
||||
}
|
||||
impl DistanceLink {
|
||||
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> {
|
||||
let handler_field = handler.field.upgrade()?;
|
||||
let handler_node = handler.node.upgrade()?;
|
||||
let method_alias = Alias::create(
|
||||
&handler_node.get_client()?,
|
||||
handler_node.get_path(),
|
||||
&method.uid,
|
||||
&method.node.upgrade()?,
|
||||
AliasInfo {
|
||||
server_signals: vec!["capture"],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
handler.method_aliases.add(Arc::downgrade(&method_alias));
|
||||
Some(DistanceLink {
|
||||
distance: method.distance(&handler_field),
|
||||
distance: method.compare_distance(&handler.field),
|
||||
method,
|
||||
handler,
|
||||
handler_field,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_input(&self, frame: u64, datamap: Datamap) {
|
||||
self.handler.send_input(frame, self, datamap);
|
||||
fn send_input(&self, order: u32, datamap: Datamap) {
|
||||
self.handler.send_input(order, self, datamap);
|
||||
}
|
||||
fn serialize(&self, datamap: Datamap) -> Vec<u8> {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn serialize(&self, order: u32, datamap: Datamap) -> Vec<u8> {
|
||||
let input = self.method.specialization.lock().serialize(
|
||||
self,
|
||||
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
|
||||
@@ -147,17 +235,21 @@ impl DistanceLink {
|
||||
let root = InputData {
|
||||
uid: self.method.uid.clone(),
|
||||
input,
|
||||
distance: self.distance,
|
||||
distance: self.method.true_distance(&self.handler.field),
|
||||
datamap,
|
||||
order,
|
||||
};
|
||||
root.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputHandler {
|
||||
enabled: Arc<AtomicBool>,
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
spatial: Arc<Spatial>,
|
||||
pub field: Weak<Field>,
|
||||
field: Arc<Field>,
|
||||
method_aliases: LifeLinkedNodeList,
|
||||
}
|
||||
impl InputHandler {
|
||||
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
|
||||
@@ -167,52 +259,54 @@ impl InputHandler {
|
||||
);
|
||||
|
||||
let handler = InputHandler {
|
||||
enabled: node.enabled.clone(),
|
||||
uid: node.uid.clone(),
|
||||
node: Arc::downgrade(node),
|
||||
spatial: node.spatial.get().unwrap().clone(),
|
||||
field: Arc::downgrade(field),
|
||||
field: field.clone(),
|
||||
method_aliases: LifeLinkedNodeList::default(),
|
||||
};
|
||||
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
|
||||
method.handle_new_handler(&handler);
|
||||
}
|
||||
let handler = INPUT_HANDLER_REGISTRY.add(handler);
|
||||
let _ = node.input_handler.set(handler);
|
||||
Ok(())
|
||||
}
|
||||
fn find(client: &Client, path: &str) -> Result<Arc<Self>> {
|
||||
InputHandler::get(&*client.get_node("Input Handler", path)?)
|
||||
}
|
||||
fn get(node: &Node) -> Result<Arc<Self>> {
|
||||
node.get_aspect("Input Handler", "input handler", |n| &n.input_handler)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn send_input(&self, frame: u64, distance_link: &DistanceLink, datamap: Datamap) {
|
||||
let data = distance_link.serialize(datamap);
|
||||
let node = self.node.upgrade().unwrap();
|
||||
let method = Arc::downgrade(&distance_link.method);
|
||||
let handler = Arc::downgrade(&distance_link.handler);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let data = node.execute_remote_method("input", data).await;
|
||||
if frame == FRAME.load(Ordering::Relaxed) {
|
||||
if let Ok(data) = data {
|
||||
let capture = flexbuffers::Reader::get_root(data.as_slice())
|
||||
.and_then(|data| data.get_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
if let Some(method) = method.upgrade() {
|
||||
if let Some(handler) = handler.upgrade() {
|
||||
if capture {
|
||||
method.captures.add_raw(&handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
#[instrument(level = "debug", skip(self, distance_link))]
|
||||
fn send_input(&self, order: u32, distance_link: &DistanceLink, datamap: Datamap) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let _ = node.send_remote_signal("input", &distance_link.serialize(order, datamap));
|
||||
}
|
||||
}
|
||||
impl PartialEq for InputHandler {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.spatial == other.spatial
|
||||
}
|
||||
}
|
||||
impl Drop for InputHandler {
|
||||
fn drop(&mut self) {
|
||||
INPUT_HANDLER_REGISTRY.remove(self);
|
||||
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
|
||||
method.handle_drop_handler(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "input", false);
|
||||
node.add_local_signal("createInputHandler", create_input_handler_flex);
|
||||
node.add_local_signal("createInputMethodTip", tip::create_tip_flex);
|
||||
node.add_to_scenegraph();
|
||||
node.add_local_signal("create_input_handler", create_input_handler_flex);
|
||||
node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
|
||||
node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn create_input_handler_flex(
|
||||
@@ -229,50 +323,73 @@ pub fn create_input_handler_flex(
|
||||
}
|
||||
let info: CreateInputHandlerInfo = deserialize(data)?;
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let field = find_field(&calling_client, info.field_path)?;
|
||||
|
||||
let node = Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let node =
|
||||
Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
InputHandler::add_to(&node, &field)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn process_input() {
|
||||
for method in INPUT_METHOD_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|method| *method.enabled.lock())
|
||||
.filter(|method| method.datamap.lock().is_some())
|
||||
{
|
||||
let mut distance_links: Vec<DistanceLink> = INPUT_HANDLER_REGISTRY
|
||||
// Iterate over all valid input methods
|
||||
let methods = debug_span!("Get valid methods").in_scope(|| {
|
||||
INPUT_METHOD_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|handler| handler.field.upgrade().is_some())
|
||||
.filter_map(|handler| DistanceLink::from(method.clone(), handler))
|
||||
.collect();
|
||||
distance_links.sort_unstable_by(|a, b| {
|
||||
a.distance
|
||||
.abs()
|
||||
.partial_cmp(&b.distance.abs())
|
||||
.unwrap()
|
||||
.reverse()
|
||||
});
|
||||
.filter(|method| *method.enabled.lock())
|
||||
.filter(|method| method.datamap.lock().is_some())
|
||||
});
|
||||
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
|
||||
for handler in &handlers {
|
||||
handler.method_aliases.clear();
|
||||
}
|
||||
for method in methods {
|
||||
debug_span!("Process input method").in_scope(|| {
|
||||
// Get all valid input handlers and convert them to DistanceLink objects
|
||||
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links")
|
||||
.in_scope(|| {
|
||||
if let Some(handler_order) = method.handler_order.get() {
|
||||
let handler_order = handler_order.lock();
|
||||
handler_order
|
||||
.iter()
|
||||
.filter_map(|h| h.upgrade())
|
||||
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
|
||||
.filter_map(|handler| DistanceLink::from(method.clone(), handler))
|
||||
.collect()
|
||||
} else {
|
||||
let mut distance_links: Vec<_> = handlers
|
||||
.iter()
|
||||
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
|
||||
.filter_map(|handler| {
|
||||
DistanceLink::from(method.clone(), handler.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut last_distance = 0.0;
|
||||
let frame = FRAME.load(Ordering::Relaxed);
|
||||
let captures = method.captures.get_valid_contents();
|
||||
for distance_link in distance_links {
|
||||
distance_link.send_input(frame, method.datamap.lock().clone().unwrap());
|
||||
if last_distance != distance_link.distance
|
||||
&& captures
|
||||
.iter()
|
||||
.any(|c| Arc::ptr_eq(c, &distance_link.handler))
|
||||
{
|
||||
break;
|
||||
// Sort the distance links by their distance in ascending order
|
||||
debug_span!("Sort distance links").in_scope(|| {
|
||||
distance_links.sort_unstable_by(|a, b| {
|
||||
a.distance.abs().partial_cmp(&b.distance.abs()).unwrap()
|
||||
});
|
||||
});
|
||||
|
||||
distance_links
|
||||
}
|
||||
});
|
||||
|
||||
let captures = method.captures.take_valid_contents();
|
||||
// Iterate over the distance links and send input to them
|
||||
for (i, distance_link) in distance_links.into_iter().enumerate() {
|
||||
distance_link.send_input(i as u32, method.datamap.lock().clone().unwrap());
|
||||
|
||||
// If the current distance link is in the list of captured input handlers,
|
||||
// break out of the loop to avoid sending input to the remaining distance links
|
||||
if captures.contains(&distance_link.handler) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
last_distance = distance_link.distance;
|
||||
}
|
||||
method.captures.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use super::{DistanceLink, InputSpecialization};
|
||||
use crate::nodes::fields::{ray_march, Field, Ray, RayMarchResult};
|
||||
use crate::nodes::spatial::Spatial;
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::fields::{Field, Ray, RayMarchResult};
|
||||
use crate::nodes::input::{InputMethod, InputType};
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::Node;
|
||||
use glam::{vec3, Mat4};
|
||||
use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flat::{Datamap, InputDataType, Pointer as FlatPointer};
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pointer {}
|
||||
pub struct Pointer;
|
||||
// impl Default for Pointer {
|
||||
// fn default() -> Self {
|
||||
// Pointer {
|
||||
@@ -17,20 +23,24 @@ pub struct Pointer {}
|
||||
// }
|
||||
impl Pointer {
|
||||
fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
|
||||
ray_march(
|
||||
Ray {
|
||||
origin: vec3(0_f32, 0_f32, 0_f32),
|
||||
direction: vec3(0_f32, 0_f32, 1_f32),
|
||||
space: space.clone(),
|
||||
},
|
||||
field,
|
||||
)
|
||||
field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: space.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSpecialization for Pointer {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
self.ray_march(space, field).distance
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
let ray_info = self.ray_march(space, field);
|
||||
ray_info
|
||||
.deepest_point_distance
|
||||
.hypot(ray_info.min_distance.recip())
|
||||
}
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
let ray_info = self.ray_march(space, field);
|
||||
ray_info.min_distance
|
||||
}
|
||||
fn serialize(
|
||||
&self,
|
||||
@@ -38,11 +48,8 @@ impl InputSpecialization for Pointer {
|
||||
local_to_handler_matrix: Mat4,
|
||||
) -> InputDataType {
|
||||
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
||||
let direction = local_to_handler_matrix.transform_vector3(vec3(0_f32, 0_f32, 1_f32));
|
||||
let ray_march = self.ray_march(
|
||||
&distance_link.method.spatial,
|
||||
&distance_link.handler.field.upgrade().unwrap(),
|
||||
);
|
||||
let direction = local_to_handler_matrix.transform_vector3(vec3(0.0, 0.0, -1.0));
|
||||
let ray_march = self.ray_march(&distance_link.method.spatial, &distance_link.handler.field);
|
||||
let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
|
||||
|
||||
InputDataType::Pointer(FlatPointer {
|
||||
@@ -52,3 +59,30 @@ impl InputSpecialization for Pointer {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_pointer_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreatePointerInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
datamap: Option<Vec<u8>>,
|
||||
}
|
||||
let info: CreatePointerInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
InputMethod::add_to(
|
||||
&node,
|
||||
InputType::Pointer(Pointer),
|
||||
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::nodes::fields::Field;
|
||||
use crate::nodes::input::{InputMethod, InputType};
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::Node;
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3a, Mat4};
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip};
|
||||
@@ -25,7 +25,10 @@ impl Tip {
|
||||
}
|
||||
}
|
||||
impl InputSpecialization for Tip {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
|
||||
}
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
field.distance(space, vec3a(0.0, 0.0, 0.0))
|
||||
}
|
||||
fn serialize(
|
||||
@@ -54,10 +57,10 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
|
||||
let info: CreateTipInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/input/method/tip", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
InputMethod::add_to(
|
||||
&node,
|
||||
InputType::Tip(Tip {
|
||||
@@ -65,6 +68,6 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
|
||||
}),
|
||||
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
|
||||
)?;
|
||||
node.add_local_signal("setRadius", Tip::set_radius);
|
||||
node.add_local_signal("set_radius", Tip::set_radius);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
use super::{Item, ItemSpecialization, ItemType, ITEM_TYPE_INFO_ENVIRONMENT};
|
||||
use super::{Item, ItemSpecialization, ItemType};
|
||||
use crate::{
|
||||
core::client::{Client, INTERNAL_CLIENT},
|
||||
core::{
|
||||
client::{Client, INTERNAL_CLIENT},
|
||||
registry::Registry,
|
||||
},
|
||||
nodes::{
|
||||
items::TypeInfo,
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use nanoid::nanoid;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::{
|
||||
schemas::flex::{deserialize, serialize},
|
||||
schemas::flex::{deserialize, flexbuffers, serialize},
|
||||
values::Transform,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
lazy_static! {
|
||||
pub(super) static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
|
||||
type_name: "environment",
|
||||
aliased_local_signals: vec!["apply_sky_tex", "apply_sky_light"],
|
||||
aliased_local_methods: vec![],
|
||||
aliased_remote_signals: vec![],
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
};
|
||||
}
|
||||
|
||||
pub struct EnvironmentItem {
|
||||
path: String,
|
||||
}
|
||||
@@ -21,18 +39,18 @@ impl EnvironmentItem {
|
||||
pub fn add_to(node: &Arc<Node>, path: String) {
|
||||
Item::add_to(
|
||||
node,
|
||||
nanoid!(),
|
||||
&ITEM_TYPE_INFO_ENVIRONMENT,
|
||||
ItemType::Environment(EnvironmentItem { path }),
|
||||
);
|
||||
node.add_local_method("getPath", EnvironmentItem::get_path_flex);
|
||||
node.add_local_method("get_path", EnvironmentItem::get_path_flex);
|
||||
}
|
||||
|
||||
fn get_path_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<Vec<u8>> {
|
||||
let path: Result<String> = match &node.item.get().unwrap().specialization {
|
||||
ItemType::Environment(env) => Ok(env.path.clone()),
|
||||
_ => Err(anyhow!("")),
|
||||
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
|
||||
return Err(eyre!("Wrong item type?"))
|
||||
};
|
||||
Ok(flexbuffers::singleton(path?.as_str()))
|
||||
Ok(flexbuffers::singleton(environment_item.path.as_str()))
|
||||
}
|
||||
}
|
||||
impl ItemSpecialization for EnvironmentItem {
|
||||
@@ -54,32 +72,17 @@ pub(super) fn create_environment_item_flex(
|
||||
item_data: String,
|
||||
}
|
||||
let info: CreateEnvironmentItemInfo = deserialize(data)?;
|
||||
let parent_name = format!("/item/{}/item/", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
|
||||
let node = Node::create(&INTERNAL_CLIENT, &parent_name, info.name, true);
|
||||
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
|
||||
let space = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, None, transform * space.global_transform())?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
|
||||
let node =
|
||||
Node::create(&INTERNAL_CLIENT, &parent_name, info.name, false).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, None, transform * space.global_transform(), false)?;
|
||||
EnvironmentItem::add_to(&node, info.item_data);
|
||||
node.item
|
||||
.get()
|
||||
.unwrap()
|
||||
.make_alias(&calling_client, &parent_name);
|
||||
.make_alias_named(&calling_client, &parent_name, info.name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn create_environment_item_acceptor_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
super::create_item_acceptor_flex(calling_client, data, &ITEM_TYPE_INFO_ENVIRONMENT)
|
||||
}
|
||||
|
||||
pub(super) fn register_environment_item_ui_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
_data: &[u8],
|
||||
) -> Result<()> {
|
||||
super::register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_ENVIRONMENT)
|
||||
}
|
||||
|
||||
@@ -1,66 +1,59 @@
|
||||
mod environment;
|
||||
|
||||
use self::environment::EnvironmentItem;
|
||||
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
|
||||
use super::fields::Field;
|
||||
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use super::{Alias, Node};
|
||||
use crate::core::client::{Client, INTERNAL_CLIENT};
|
||||
use crate::core::nodelist::LifeLinkedNodeList;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::alias::AliasInfo;
|
||||
use crate::nodes::fields::find_field;
|
||||
use crate::wayland::panel_item::{register_panel_item_ui_flex, PanelItem};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
#[cfg(feature = "wayland")]
|
||||
use crate::wayland::panel_item::{PanelItem, ITEM_TYPE_INFO_PANEL};
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::Ordering;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![
|
||||
"getTransform",
|
||||
"setTransform",
|
||||
"setSpatialParent",
|
||||
"setSpatialParentInPlace",
|
||||
"setZoneable",
|
||||
"get_bounds",
|
||||
"get_transform",
|
||||
"set_transform",
|
||||
"set_spatial_parent",
|
||||
"set_spatial_parent_in_place",
|
||||
"set_zoneable",
|
||||
"release",
|
||||
];
|
||||
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec!["captureInto"];
|
||||
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec![];
|
||||
static ref ITEM_ALIAS_REMOTE_SIGNALS: Vec<&'static str> = vec![];
|
||||
static ref ITEM_ALIAS_REMOTE_METHODS: Vec<&'static str> = vec![];
|
||||
static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
|
||||
type_name: "environment",
|
||||
aliased_local_signals: vec!["applySkyTex", "applySkyLight"],
|
||||
aliased_local_methods: vec![],
|
||||
aliased_remote_signals: vec![],
|
||||
aliased_remote_methods: vec![],
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||
if item.captured_acceptor.lock().upgrade().is_some() {
|
||||
release(item);
|
||||
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
|
||||
release(item, Some(&acceptor));
|
||||
}
|
||||
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
|
||||
acceptor.handle_capture(item);
|
||||
if let Some(ui) = item.type_info.ui.lock().upgrade() {
|
||||
ui.handle_capture(item);
|
||||
ui.handle_capture_item(item, acceptor);
|
||||
}
|
||||
}
|
||||
fn release(item: &Arc<Item>) {
|
||||
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
|
||||
*item.captured_acceptor.lock() = Weak::default();
|
||||
fn release(item: &Item, acceptor: Option<&ItemAcceptor>) {
|
||||
let mut captured_acceptor = item.captured_acceptor.lock();
|
||||
if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) {
|
||||
*captured_acceptor = Weak::default();
|
||||
acceptor.handle_release(item);
|
||||
if let Some(ui) = item.type_info.ui.lock().upgrade() {
|
||||
ui.handle_release(item);
|
||||
ui.handle_release_item(item, &acceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,27 +63,21 @@ pub struct TypeInfo {
|
||||
pub aliased_local_signals: Vec<&'static str>,
|
||||
pub aliased_local_methods: Vec<&'static str>,
|
||||
pub aliased_remote_signals: Vec<&'static str>,
|
||||
pub aliased_remote_methods: Vec<&'static str>,
|
||||
pub ui: Mutex<Weak<ItemUI>>,
|
||||
pub items: Registry<Item>,
|
||||
pub acceptors: Registry<ItemAcceptor>,
|
||||
}
|
||||
|
||||
fn capture_into_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let acceptor_path = flexbuffers::Reader::get_root(data)?
|
||||
.get_str()
|
||||
.map_err(|_| anyhow!("Acceptor path is not a string"))?;
|
||||
let acceptor = calling_client
|
||||
.scenegraph
|
||||
.get_node(acceptor_path)
|
||||
.ok_or_else(|| anyhow!("Acceptor node not found"))?;
|
||||
let acceptor = acceptor
|
||||
.item_acceptor
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("Acceptor node is not an acceptor!"))?;
|
||||
capture(node.item.get().unwrap(), acceptor);
|
||||
Ok(())
|
||||
impl Hash for TypeInfo {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.type_name.hash(state);
|
||||
}
|
||||
}
|
||||
impl PartialEq for TypeInfo {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.type_name == other.type_name
|
||||
}
|
||||
}
|
||||
impl Eq for TypeInfo {}
|
||||
|
||||
pub struct Item {
|
||||
node: Weak<Node>,
|
||||
@@ -102,55 +89,82 @@ pub struct Item {
|
||||
impl Item {
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
uid: String,
|
||||
type_info: &'static TypeInfo,
|
||||
specialization: ItemType,
|
||||
) -> Arc<Self> {
|
||||
node.add_local_signal("captureInto", capture_into_flex);
|
||||
let item = Item {
|
||||
node: Arc::downgrade(node),
|
||||
uid: nanoid!(),
|
||||
uid,
|
||||
type_info,
|
||||
captured_acceptor: Default::default(),
|
||||
specialization,
|
||||
};
|
||||
let item = type_info.items.add(item);
|
||||
|
||||
node.add_local_signal("release", Item::release_flex);
|
||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||
ui.handle_create_item(&item);
|
||||
}
|
||||
let _ = node.item.set(item.clone());
|
||||
|
||||
if let Some(auto_acceptor) = node.get_client().and_then(|client| {
|
||||
client
|
||||
.startup_settings
|
||||
.as_ref()
|
||||
.and_then(|settings| settings.acceptors.get(type_info))
|
||||
.and_then(|acceptor| acceptor.upgrade())
|
||||
}) {
|
||||
capture(&item, &auto_acceptor);
|
||||
}
|
||||
|
||||
item
|
||||
}
|
||||
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> (Arc<Node>, Arc<Alias>) {
|
||||
let node = Alias::new(
|
||||
fn make_alias_named(
|
||||
&self,
|
||||
client: &Arc<Client>,
|
||||
parent: &str,
|
||||
name: &str,
|
||||
) -> Result<Arc<Node>> {
|
||||
Alias::create(
|
||||
client,
|
||||
parent,
|
||||
&self.uid,
|
||||
name,
|
||||
&self.node.upgrade().unwrap(),
|
||||
AliasInfo {
|
||||
local_signals: [
|
||||
server_signals: [
|
||||
&self.type_info.aliased_local_signals,
|
||||
ITEM_ALIAS_LOCAL_SIGNALS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
local_methods: [
|
||||
server_methods: [
|
||||
&self.type_info.aliased_local_methods,
|
||||
ITEM_ALIAS_LOCAL_METHODS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
remote_signals: [
|
||||
client_signals: [
|
||||
&self.type_info.aliased_remote_signals,
|
||||
ITEM_ALIAS_REMOTE_SIGNALS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
},
|
||||
);
|
||||
let alias = node.alias.get().unwrap().clone();
|
||||
(node, alias)
|
||||
)
|
||||
}
|
||||
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> Result<Arc<Node>> {
|
||||
self.make_alias_named(client, parent, &self.uid)
|
||||
}
|
||||
|
||||
fn release_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
let item = node.get_aspect("Item", "item", |n| &n.item)?;
|
||||
release(item, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Item {
|
||||
fn drop(&mut self) {
|
||||
self.type_info.items.remove(self);
|
||||
release(self, None);
|
||||
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||
ui.handle_destroy_item(self);
|
||||
}
|
||||
@@ -163,7 +177,8 @@ pub trait ItemSpecialization {
|
||||
|
||||
pub enum ItemType {
|
||||
Environment(EnvironmentItem),
|
||||
Panel(PanelItem),
|
||||
#[cfg(feature = "wayland")]
|
||||
Panel(Arc<PanelItem>),
|
||||
}
|
||||
impl Deref for ItemType {
|
||||
type Target = dyn ItemSpecialization;
|
||||
@@ -171,7 +186,8 @@ impl Deref for ItemType {
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
ItemType::Environment(item) => item,
|
||||
ItemType::Panel(item) => item,
|
||||
#[cfg(feature = "wayland")]
|
||||
ItemType::Panel(item) => item.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +195,9 @@ impl Deref for ItemType {
|
||||
pub struct ItemUI {
|
||||
node: Weak<Node>,
|
||||
type_info: &'static TypeInfo,
|
||||
aliases: LifeLinkedNodeList,
|
||||
item_aliases: LifeLinkedNodeMap<String>,
|
||||
acceptor_aliases: LifeLinkedNodeMap<String>,
|
||||
acceptor_field_aliases: LifeLinkedNodeMap<String>,
|
||||
}
|
||||
impl ItemUI {
|
||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
|
||||
@@ -191,7 +209,9 @@ impl ItemUI {
|
||||
let ui = Arc::new(ItemUI {
|
||||
node: Arc::downgrade(node),
|
||||
type_info,
|
||||
aliases: Default::default(),
|
||||
item_aliases: Default::default(),
|
||||
acceptor_aliases: Default::default(),
|
||||
acceptor_field_aliases: Default::default(),
|
||||
});
|
||||
*type_info.ui.lock() = Arc::downgrade(&ui);
|
||||
let _ = node.item_ui.set(ui.clone());
|
||||
@@ -199,6 +219,9 @@ impl ItemUI {
|
||||
for item in type_info.items.get_valid_contents() {
|
||||
ui.handle_create_item(&item);
|
||||
}
|
||||
for acceptor in type_info.acceptors.get_valid_contents() {
|
||||
ui.handle_create_acceptor(&acceptor);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn send_state(&self, state: &str, name: &str) {
|
||||
@@ -210,40 +233,56 @@ impl ItemUI {
|
||||
}
|
||||
|
||||
fn handle_create_item(&self, item: &Item) {
|
||||
let node = self.node.upgrade().unwrap();
|
||||
if let Some(client) = node.get_client() {
|
||||
let (alias_node, _) =
|
||||
item.make_alias(&client, &(node.get_path().to_string() + "/item"));
|
||||
self.aliases.add(Arc::downgrade(&alias_node));
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
let Some(client) = node.get_client() else { return };
|
||||
|
||||
let _ = node.send_remote_signal(
|
||||
"create",
|
||||
&item.specialization.serialize_start_data(&item.uid),
|
||||
);
|
||||
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
|
||||
self.item_aliases.add(item.uid.clone(), &alias_node);
|
||||
}
|
||||
|
||||
let _ = node.send_remote_signal(
|
||||
"create_item",
|
||||
&item.specialization.serialize_start_data(&item.uid),
|
||||
);
|
||||
}
|
||||
fn handle_destroy_item(&self, item: &Item) {
|
||||
self.send_state("destroy", item.uid.as_str());
|
||||
self.item_aliases.remove(&item.uid);
|
||||
self.send_state("destroy_item", item.uid.as_str());
|
||||
}
|
||||
fn handle_capture(&self, item: &Item) {
|
||||
self.send_state("capture", item.uid.as_str());
|
||||
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
|
||||
let _ = node.send_remote_signal(
|
||||
"capture_item",
|
||||
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
|
||||
);
|
||||
}
|
||||
fn handle_release(&self, item: &Item) {
|
||||
self.send_state("release", item.uid.as_str());
|
||||
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
|
||||
let _ = node.send_remote_signal(
|
||||
"release_item",
|
||||
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
|
||||
);
|
||||
}
|
||||
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||
let node = self.node.upgrade().unwrap();
|
||||
if let Some(client) = node.get_client() {
|
||||
let aliases = acceptor.make_aliases(
|
||||
&client,
|
||||
&format!("/item/{}/acceptor", self.type_info.type_name),
|
||||
);
|
||||
aliases
|
||||
.iter()
|
||||
.for_each(|alias| self.aliases.add(Arc::downgrade(alias)));
|
||||
}
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
let Some(client) = node.get_client() else { return };
|
||||
|
||||
let Ok((alias, field_alias)) = acceptor.make_aliases(
|
||||
&client,
|
||||
&format!("/item/{}/acceptor", self.type_info.type_name),
|
||||
) else {return};
|
||||
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
|
||||
self.acceptor_field_aliases
|
||||
.add(acceptor.uid.clone(), &field_alias);
|
||||
let _ = node.send_remote_signal("create_acceptor", &serialize(&acceptor.uid).unwrap());
|
||||
}
|
||||
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||
self.send_state("destroy_acceptor", acceptor.uid.as_str());
|
||||
self.acceptor_aliases.remove(&acceptor.uid);
|
||||
self.acceptor_field_aliases.remove(&acceptor.uid);
|
||||
}
|
||||
fn handle_destroy_acceptor(&self, _acceptor: &ItemAcceptor) {}
|
||||
}
|
||||
impl Drop for ItemUI {
|
||||
fn drop(&mut self) {
|
||||
@@ -252,127 +291,156 @@ impl Drop for ItemUI {
|
||||
}
|
||||
|
||||
pub struct ItemAcceptor {
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
type_info: &'static TypeInfo,
|
||||
field: Mutex<Weak<Field>>,
|
||||
accepted: Registry<Item>,
|
||||
pub type_info: &'static TypeInfo,
|
||||
field: Arc<Field>,
|
||||
accepted_aliases: LifeLinkedNodeMap<String>,
|
||||
accepted_registry: Registry<Item>,
|
||||
}
|
||||
impl ItemAcceptor {
|
||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Weak<Field>) {
|
||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
|
||||
let acceptor = type_info.acceptors.add(ItemAcceptor {
|
||||
uid: nanoid!(),
|
||||
node: Arc::downgrade(node),
|
||||
type_info,
|
||||
field: Mutex::new(field),
|
||||
accepted: Registry::new(),
|
||||
field,
|
||||
accepted_aliases: Default::default(),
|
||||
accepted_registry: Registry::new(),
|
||||
});
|
||||
node.add_local_signal("capture", ItemAcceptor::capture_flex);
|
||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||
ui.handle_create_acceptor(&acceptor);
|
||||
}
|
||||
let _ = node.item_acceptor.set(acceptor);
|
||||
}
|
||||
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Vec<Arc<Node>> {
|
||||
let mut aliases = Vec::new();
|
||||
|
||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
if !node.enabled.load(Ordering::Relaxed) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let acceptor = node.item_acceptor.get().unwrap();
|
||||
let item_path: &str = deserialize(data)?;
|
||||
let item_node = calling_client.get_node("Item", item_path)?;
|
||||
let item = item_node.get_aspect("Item", "item", |n| &n.item)?;
|
||||
capture(item, acceptor);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Result<(Arc<Node>, Arc<Node>)> {
|
||||
let acceptor_node = &self.node.upgrade().unwrap();
|
||||
let acceptor_alias = Alias::new(
|
||||
let acceptor_alias = Alias::create(
|
||||
client,
|
||||
parent,
|
||||
acceptor_node.uid.as_str(),
|
||||
&self.uid,
|
||||
acceptor_node,
|
||||
AliasInfo {
|
||||
local_signals: vec!["release"],
|
||||
server_signals: vec!["capture"],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
if let Some(field) = self.field.lock().upgrade() {
|
||||
let acceptor_field_alias = Alias::new(
|
||||
client,
|
||||
acceptor_alias.get_path(),
|
||||
"field",
|
||||
&field.spatial_ref().node.upgrade().unwrap(),
|
||||
AliasInfo::default(),
|
||||
);
|
||||
)?;
|
||||
|
||||
aliases.push(acceptor_field_alias);
|
||||
}
|
||||
aliases.push(acceptor_alias);
|
||||
aliases
|
||||
}
|
||||
fn send_event(&self, state: &str, name: &str) {
|
||||
let _ = self
|
||||
.node
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.send_remote_signal(state, flexbuffers::singleton(name).as_slice());
|
||||
let acceptor_field_alias = Alias::create(
|
||||
client,
|
||||
acceptor_alias.get_path(),
|
||||
"field",
|
||||
&self.field.spatial_ref().node.upgrade().unwrap(),
|
||||
AliasInfo::default(),
|
||||
)?;
|
||||
|
||||
Ok((acceptor_alias, acceptor_field_alias))
|
||||
}
|
||||
fn handle_capture(&self, item: &Arc<Item>) {
|
||||
self.accepted.add_raw(item);
|
||||
self.send_event("capture", item.uid.as_str());
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
let Some(client) = node.get_client() else { return };
|
||||
|
||||
self.accepted_registry.add_raw(item);
|
||||
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
|
||||
self.accepted_aliases.add(item.uid.clone(), &alias_node);
|
||||
}
|
||||
let _ = node.send_remote_signal(
|
||||
"capture",
|
||||
&item.specialization.serialize_start_data(&item.uid),
|
||||
);
|
||||
}
|
||||
fn handle_release(&self, item: &Item) {
|
||||
self.accepted.remove(item);
|
||||
self.send_event("release", item.uid.as_str());
|
||||
let Some(node) = self.node.upgrade() else { return };
|
||||
|
||||
self.accepted_registry.remove(item);
|
||||
self.accepted_aliases.remove(&item.uid);
|
||||
let _ = node.send_remote_signal("release", &serialize(&item.uid).unwrap());
|
||||
}
|
||||
}
|
||||
impl Drop for ItemAcceptor {
|
||||
fn drop(&mut self) {
|
||||
self.type_info.acceptors.remove(self);
|
||||
for item in self.accepted_registry.get_valid_contents() {
|
||||
release(&item, Some(self));
|
||||
}
|
||||
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||
ui.handle_destroy_acceptor(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "item", false);
|
||||
node.add_local_signal(
|
||||
"createEnvironmentItem",
|
||||
"create_environment_item",
|
||||
environment::create_environment_item_flex,
|
||||
);
|
||||
node.add_local_signal(
|
||||
"registerEnvironmentItemUI",
|
||||
environment::register_environment_item_ui_flex,
|
||||
);
|
||||
node.add_local_signal("registerPanelItemUI", register_panel_item_ui_flex);
|
||||
node.add_local_signal(
|
||||
"createEnvironmentItemAcceptor",
|
||||
environment::create_environment_item_acceptor_flex,
|
||||
);
|
||||
node.add_to_scenegraph();
|
||||
node.add_local_signal("register_item_ui", register_item_ui_flex);
|
||||
node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub(self) fn create_item_acceptor_flex(
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
type_info: &'static TypeInfo,
|
||||
) -> Result<()> {
|
||||
fn type_info(name: &str) -> Result<&'static TypeInfo> {
|
||||
match name {
|
||||
"environment" => Ok(&ITEM_TYPE_INFO_ENVIRONMENT),
|
||||
#[cfg(feature = "wayland")]
|
||||
"panel" => Ok(&ITEM_TYPE_INFO_PANEL),
|
||||
_ => Err(eyre!("Invalid item type")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_item_ui_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct RegisterItemUIInfo<'a> {
|
||||
item_type: &'a str,
|
||||
}
|
||||
let info: RegisterItemUIInfo = deserialize(data)?;
|
||||
let type_info = type_info(info.item_type)?;
|
||||
let ui =
|
||||
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
|
||||
ItemUI::add_to(&ui, type_info)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_item_acceptor_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateItemAcceptorInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
item_type: &'a str,
|
||||
}
|
||||
let info: CreateItemAcceptorInfo = deserialize(data)?;
|
||||
let parent_name = format!("/item/{}/acceptor/", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
|
||||
let space = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let field = find_field(&calling_client, info.field_path)?;
|
||||
let type_info = type_info(info.item_type)?;
|
||||
|
||||
let node = Node::create(&INTERNAL_CLIENT, &parent_name, info.name, true).add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(space), transform)?;
|
||||
ItemAcceptor::add_to(&node, type_info, Arc::downgrade(&field));
|
||||
node.item
|
||||
.get()
|
||||
.unwrap()
|
||||
.make_alias(&calling_client, &parent_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_item_ui_flex(
|
||||
calling_client: Arc<Client>,
|
||||
type_info: &'static TypeInfo,
|
||||
) -> Result<()> {
|
||||
let ui = Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph();
|
||||
ItemUI::add_to(&ui, type_info)?;
|
||||
let node = Node::create(
|
||||
&calling_client,
|
||||
&format!("/item/{}/acceptor", type_info.type_name),
|
||||
info.name,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(space), transform, false)?;
|
||||
ItemAcceptor::add_to(&node, type_info, field);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
151
src/nodes/mod.rs
151
src/nodes/mod.rs
@@ -1,4 +1,5 @@
|
||||
pub mod alias;
|
||||
pub mod audio;
|
||||
pub mod data;
|
||||
pub mod drawable;
|
||||
pub mod fields;
|
||||
@@ -9,30 +10,33 @@ pub mod root;
|
||||
pub mod spatial;
|
||||
pub mod startup;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use core::hash::BuildHasherDefault;
|
||||
use dashmap::DashMap;
|
||||
use nanoid::nanoid;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
use rustc_hash::FxHasher;
|
||||
use stardust_xr::messenger::MessageSenderHandle;
|
||||
use stardust_xr::scenegraph::ScenegraphError;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::vec::Vec;
|
||||
|
||||
use core::hash::BuildHasherDefault;
|
||||
use dashmap::DashMap;
|
||||
use rustc_hash::FxHasher;
|
||||
use tracing::{debug_span, instrument};
|
||||
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
|
||||
use self::alias::Alias;
|
||||
use self::audio::Sound;
|
||||
use self::data::{PulseReceiver, PulseSender};
|
||||
|
||||
use self::drawable::model::Model;
|
||||
use self::drawable::text::Text;
|
||||
use self::drawable::Drawable;
|
||||
use self::fields::Field;
|
||||
use self::input::{InputHandler, InputMethod};
|
||||
use self::items::{Item, ItemAcceptor, ItemUI};
|
||||
use self::spatial::zone::Zone;
|
||||
use self::spatial::Spatial;
|
||||
use self::startup::StartupSettings;
|
||||
|
||||
@@ -40,26 +44,29 @@ pub type Signal = fn(&Node, Arc<Client>, &[u8]) -> Result<()>;
|
||||
pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>;
|
||||
|
||||
pub struct Node {
|
||||
pub enabled: Arc<AtomicBool>,
|
||||
pub(super) uid: String,
|
||||
path: String,
|
||||
client: Weak<Client>,
|
||||
message_sender_handle: Option<MessageSenderHandle>,
|
||||
// trailing_slash_pos: usize,
|
||||
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>,
|
||||
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>,
|
||||
destroyable: AtomicBool,
|
||||
destroyable: bool,
|
||||
|
||||
pub alias: OnceCell<Arc<Alias>>,
|
||||
aliases: Registry<Alias>,
|
||||
|
||||
pub spatial: OnceCell<Arc<Spatial>>,
|
||||
pub field: OnceCell<Arc<Field>>,
|
||||
pub zone: OnceCell<Arc<Zone>>,
|
||||
|
||||
// Data
|
||||
pub pulse_sender: OnceCell<Arc<PulseSender>>,
|
||||
pub pulse_receiver: OnceCell<Arc<PulseReceiver>>,
|
||||
|
||||
// Drawable
|
||||
pub model: OnceCell<Arc<Model>>,
|
||||
pub text: OnceCell<Arc<Text>>,
|
||||
pub drawable: OnceCell<Drawable>,
|
||||
|
||||
// Input
|
||||
pub input_method: OnceCell<Arc<InputMethod>>,
|
||||
@@ -70,10 +77,11 @@ pub struct Node {
|
||||
pub item_acceptor: OnceCell<Arc<ItemAcceptor>>,
|
||||
pub item_ui: OnceCell<Arc<ItemUI>>,
|
||||
|
||||
// Sound
|
||||
pub sound: OnceCell<Arc<Sound>>,
|
||||
|
||||
// Startup
|
||||
pub startup_settings: OnceCell<Mutex<StartupSettings>>,
|
||||
|
||||
pub(crate) client: Weak<Client>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@@ -86,53 +94,62 @@ impl Node {
|
||||
pub fn get_path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
pub fn is_destroyable(&self) -> bool {
|
||||
self.destroyable.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self {
|
||||
let mut path = parent.to_string();
|
||||
path.push('/');
|
||||
path.push_str(name);
|
||||
let node = Node {
|
||||
enabled: Arc::new(AtomicBool::new(true)),
|
||||
uid: nanoid!(),
|
||||
client: Arc::downgrade(client),
|
||||
message_sender_handle: client.message_sender_handle.clone(),
|
||||
path,
|
||||
// trailing_slash_pos: parent.len(),
|
||||
local_signals: Default::default(),
|
||||
local_methods: Default::default(),
|
||||
destroyable: AtomicBool::from(destroyable),
|
||||
destroyable,
|
||||
|
||||
alias: OnceCell::new(),
|
||||
aliases: Registry::new(),
|
||||
|
||||
spatial: OnceCell::new(),
|
||||
field: OnceCell::new(),
|
||||
zone: OnceCell::new(),
|
||||
pulse_sender: OnceCell::new(),
|
||||
pulse_receiver: OnceCell::new(),
|
||||
model: OnceCell::new(),
|
||||
text: OnceCell::new(),
|
||||
drawable: OnceCell::new(),
|
||||
input_method: OnceCell::new(),
|
||||
input_handler: OnceCell::new(),
|
||||
item: OnceCell::new(),
|
||||
item_acceptor: OnceCell::new(),
|
||||
item_ui: OnceCell::new(),
|
||||
sound: OnceCell::new(),
|
||||
startup_settings: OnceCell::new(),
|
||||
};
|
||||
node.add_local_signal("set_enabled", Node::set_enabled_flex);
|
||||
node.add_local_signal("destroy", Node::destroy_flex);
|
||||
node
|
||||
}
|
||||
pub fn add_to_scenegraph(self) -> Arc<Node> {
|
||||
self.get_client().unwrap().scenegraph.add_node(self)
|
||||
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
||||
Ok(self
|
||||
.get_client()
|
||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
||||
.scenegraph
|
||||
.add_node(self))
|
||||
}
|
||||
pub fn destroy(&self) {
|
||||
let _ = self
|
||||
.get_client()
|
||||
.map(|c| c.scenegraph.remove_node(self.get_path()));
|
||||
if let Some(client) = self.get_client() {
|
||||
client.scenegraph.remove_node(self.get_path());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enabled_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
node.enabled.store(deserialize(data)?, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
if node.is_destroyable() {
|
||||
if node.destroyable {
|
||||
node.destroy();
|
||||
}
|
||||
Ok(())
|
||||
@@ -150,14 +167,13 @@ impl Node {
|
||||
node_name: &'static str,
|
||||
aspect_type: &'static str,
|
||||
aspect_fn: F,
|
||||
) -> Result<Arc<T>>
|
||||
) -> Result<&T>
|
||||
where
|
||||
F: FnOnce(&Node) -> &OnceCell<Arc<T>>,
|
||||
F: FnOnce(&Node) -> &OnceCell<T>,
|
||||
{
|
||||
aspect_fn(self)
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("{} is not a {} node", node_name, aspect_type))
|
||||
.cloned()
|
||||
.ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
|
||||
}
|
||||
|
||||
pub fn send_local_signal(
|
||||
@@ -167,7 +183,7 @@ impl Node {
|
||||
data: &[u8],
|
||||
) -> Result<(), ScenegraphError> {
|
||||
if let Some(alias) = self.alias.get() {
|
||||
if !alias.info.local_signals.iter().any(|e| e == &method) {
|
||||
if !alias.info.server_signals.iter().any(|e| e == &method) {
|
||||
return Err(ScenegraphError::SignalNotFound);
|
||||
}
|
||||
alias
|
||||
@@ -180,8 +196,9 @@ impl Node {
|
||||
.local_signals
|
||||
.get(method)
|
||||
.ok_or(ScenegraphError::SignalNotFound)?;
|
||||
signal(self, calling_client, data)
|
||||
.map_err(|error| ScenegraphError::SignalError { error })
|
||||
signal(self, calling_client, data).map_err(|error| ScenegraphError::SignalError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn execute_local_method(
|
||||
@@ -191,7 +208,7 @@ impl Node {
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, ScenegraphError> {
|
||||
if let Some(alias) = self.alias.get() {
|
||||
if !alias.info.local_methods.iter().any(|e| e == &method) {
|
||||
if !alias.info.server_methods.iter().any(|e| e == &method) {
|
||||
return Err(ScenegraphError::MethodNotFound);
|
||||
}
|
||||
alias
|
||||
@@ -204,44 +221,58 @@ impl Node {
|
||||
.local_methods
|
||||
.get(method)
|
||||
.ok_or(ScenegraphError::MethodNotFound)?;
|
||||
method(self, calling_client, data)
|
||||
.map_err(|error| ScenegraphError::MethodError { error })
|
||||
|
||||
debug_span!("Handle method").in_scope(|| {
|
||||
method(self, calling_client, data).map_err(|error| ScenegraphError::MethodError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> {
|
||||
self.aliases
|
||||
.get_valid_contents()
|
||||
.iter()
|
||||
.filter(|alias| alias.info.remote_signals.iter().any(|e| e == &method))
|
||||
.for_each(|alias| {
|
||||
let _ = alias
|
||||
.node
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.send_remote_signal(method, data);
|
||||
.filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
|
||||
.filter_map(|alias| alias.node.upgrade())
|
||||
.for_each(|node| {
|
||||
let _ = node.send_remote_signal(method, data);
|
||||
});
|
||||
let path = self.path.clone();
|
||||
let method = method.to_string();
|
||||
let data = data.to_vec();
|
||||
if let Some(client) = self.get_client() {
|
||||
if let Some(messenger) = client.messenger.as_ref() {
|
||||
messenger.send_remote_signal(path.as_str(), method.as_str(), data.as_slice());
|
||||
}
|
||||
if let Some(handle) = self.message_sender_handle.as_ref() {
|
||||
handle.signal(path.as_str(), method.as_str(), data.as_slice())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn execute_remote_method(&self, method: &str, data: Vec<u8>) -> Result<Vec<u8>> {
|
||||
if let Some(client) = self.get_client() {
|
||||
match client.messenger.as_ref() {
|
||||
None => Err(anyhow!("Messenger does not exist for this node's client")),
|
||||
Some(messenger) => {
|
||||
messenger
|
||||
.execute_remote_method(self.path.as_str(), method, &data)
|
||||
.await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("Client does not exist somehow?"))
|
||||
}
|
||||
// #[instrument(level = "debug", skip_all)]
|
||||
// pub fn execute_remote_method(
|
||||
// &self,
|
||||
// method: &str,
|
||||
// data: Vec<u8>,
|
||||
// ) -> Result<impl Future<Output = Result<Vec<u8>>>> {
|
||||
// let message_sender_handle = self
|
||||
// .message_sender_handle
|
||||
// .as_ref()
|
||||
// .ok_or(eyre!("Messenger does not exist for this node"))?;
|
||||
|
||||
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?;
|
||||
|
||||
// Ok(async { future.await.map_err(|e| eyre!(e)) })
|
||||
// }
|
||||
}
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Node")
|
||||
.field("uid", &self.uid)
|
||||
.field("path", &self.path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl Drop for Node {
|
||||
fn drop(&mut self) {
|
||||
// Debug breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::spatial::Spatial;
|
||||
use super::startup::DESKTOP_STARTUP_IDS;
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
@@ -14,61 +14,57 @@ static ROOT_REGISTRY: Registry<Root> = Registry::new();
|
||||
|
||||
pub struct Root {
|
||||
node: Arc<Node>,
|
||||
logic_step: AtomicBool,
|
||||
send_frame_event: AtomicBool,
|
||||
}
|
||||
impl Root {
|
||||
pub fn create(client: &Arc<Client>) -> Arc<Self> {
|
||||
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
|
||||
let node = Node::create(client, "", "", false);
|
||||
node.add_local_signal("applyDesktopStartupID", Root::apply_desktop_startup_id);
|
||||
node.add_local_signal("subscribeLogicStep", Root::subscribe_logic_step);
|
||||
node.add_local_signal("setBasePrefixes", Root::set_base_prefixes);
|
||||
let node = node.add_to_scenegraph();
|
||||
let _ = Spatial::add_to(&node, None, Mat4::IDENTITY);
|
||||
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
|
||||
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
let _ = Spatial::add_to(
|
||||
&node,
|
||||
None,
|
||||
client
|
||||
.startup_settings
|
||||
.as_ref()
|
||||
.map(|settings| settings.transform)
|
||||
.unwrap_or(Mat4::IDENTITY),
|
||||
false,
|
||||
);
|
||||
|
||||
ROOT_REGISTRY.add(Root {
|
||||
Ok(ROOT_REGISTRY.add(Root {
|
||||
node,
|
||||
logic_step: AtomicBool::from(false),
|
||||
})
|
||||
send_frame_event: AtomicBool::from(false),
|
||||
}))
|
||||
}
|
||||
|
||||
fn apply_desktop_startup_id(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let startup_settings = DESKTOP_STARTUP_IDS
|
||||
.lock()
|
||||
.remove(flexbuffers::Reader::get_root(data)?.get_str()?)
|
||||
.ok_or_else(|| anyhow!("Desktop startup ID not found in the list!"))?;
|
||||
node.spatial
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_local_transform(startup_settings.transform);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_logic_step(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
fn subscribe_frame_flex(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
calling_client
|
||||
.root
|
||||
.get()
|
||||
.unwrap()
|
||||
.logic_step
|
||||
.send_frame_event
|
||||
.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn logic_step(delta: f64) {
|
||||
#[instrument(level = "debug")]
|
||||
pub fn send_frame_events(delta: f64) {
|
||||
if let Ok(data) = serialize((delta, 0.0)) {
|
||||
for root in ROOT_REGISTRY.get_valid_contents() {
|
||||
if root.logic_step.load(Ordering::Relaxed) {
|
||||
let _ = root.node.send_remote_signal("logicStep", &data);
|
||||
if root.send_frame_event.load(Ordering::Relaxed) {
|
||||
let _ = root.node.send_remote_signal("frame", &data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_base_prefixes(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
fn set_base_prefixes_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
*calling_client.base_resource_prefixes.lock() = deserialize(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use glam::{Mat4, Quat};
|
||||
use mint::Vector3;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub struct Spatial {
|
||||
pub(super) node: Weak<Node>,
|
||||
parent: Mutex<Option<Arc<Spatial>>>,
|
||||
pub(super) transform: Mutex<Mat4>,
|
||||
}
|
||||
|
||||
impl Spatial {
|
||||
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
|
||||
Arc::new(Spatial {
|
||||
node,
|
||||
parent: Mutex::new(parent),
|
||||
transform: Mutex::new(transform),
|
||||
})
|
||||
}
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
parent: Option<Arc<Spatial>>,
|
||||
transform: Mat4,
|
||||
) -> Result<Arc<Spatial>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_none(),
|
||||
"Internal: Node already has a Spatial aspect!"
|
||||
);
|
||||
let spatial = Spatial {
|
||||
node: Arc::downgrade(node),
|
||||
parent: Mutex::new(parent),
|
||||
transform: Mutex::new(transform),
|
||||
};
|
||||
node.add_local_method("getTransform", Spatial::get_transform_flex);
|
||||
node.add_local_signal("setTransform", Spatial::set_transform_flex);
|
||||
node.add_local_signal("setSpatialParent", Spatial::set_spatial_parent_flex);
|
||||
node.add_local_signal(
|
||||
"setSpatialParentInPlace",
|
||||
Spatial::set_spatial_parent_in_place_flex,
|
||||
);
|
||||
let spatial_arc = Arc::new(spatial);
|
||||
let _ = node.spatial.set(spatial_arc.clone());
|
||||
Ok(spatial_arc)
|
||||
}
|
||||
|
||||
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
|
||||
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
|
||||
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
|
||||
world_to_space_matrix * space_to_world_matrix
|
||||
}
|
||||
|
||||
pub fn local_transform(&self) -> Mat4 {
|
||||
*self.transform.lock()
|
||||
}
|
||||
pub fn global_transform(&self) -> Mat4 {
|
||||
match self.parent.lock().clone() {
|
||||
Some(value) => value.global_transform() * *self.transform.lock(),
|
||||
None => *self.transform.lock(),
|
||||
}
|
||||
}
|
||||
pub fn set_local_transform(&self, transform: Mat4) {
|
||||
*self.transform.lock() = transform;
|
||||
}
|
||||
pub fn set_local_transform_components(
|
||||
&self,
|
||||
reference_space: Option<&Spatial>,
|
||||
transform: Transform,
|
||||
) {
|
||||
let reference_to_parent_transform = reference_space
|
||||
.map(|reference_space| {
|
||||
Spatial::space_to_space_matrix(Some(reference_space), self.parent.lock().as_deref())
|
||||
})
|
||||
.unwrap_or(Mat4::IDENTITY);
|
||||
let mut local_transform_in_reference_space =
|
||||
reference_to_parent_transform.inverse() * self.local_transform();
|
||||
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
|
||||
local_transform_in_reference_space.to_scale_rotation_translation();
|
||||
|
||||
if let Some(pos) = transform.position {
|
||||
reference_space_pos = pos.into()
|
||||
}
|
||||
if let Some(rot) = transform.rotation {
|
||||
reference_space_rot = rot.into()
|
||||
} else if reference_space_rot.is_nan() {
|
||||
reference_space_rot = Quat::IDENTITY;
|
||||
}
|
||||
if let Some(scl) = transform.scale {
|
||||
reference_space_scl = scl.into()
|
||||
}
|
||||
|
||||
local_transform_in_reference_space = Mat4::from_scale_rotation_translation(
|
||||
reference_space_scl,
|
||||
reference_space_rot,
|
||||
reference_space_pos,
|
||||
);
|
||||
self.set_local_transform(
|
||||
reference_to_parent_transform * local_transform_in_reference_space,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
|
||||
let mut current_ancestor = spatial;
|
||||
loop {
|
||||
if Arc::as_ptr(¤t_ancestor) == ptr::addr_of!(*self) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let current_ancestor_parent = current_ancestor.parent.lock().clone();
|
||||
if let Some(parent) = current_ancestor_parent {
|
||||
current_ancestor = parent;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_spatial_parent(&self, parent: Option<&Arc<Spatial>>) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(anyhow!("Setting spatial parent would cause a loop"));
|
||||
}
|
||||
|
||||
*self.parent.lock() = parent.cloned();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_spatial_parent_in_place(&self, parent: Option<&Arc<Spatial>>) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(anyhow!("Setting spatial parent would cause a loop"));
|
||||
}
|
||||
|
||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||
Some(self),
|
||||
parent.cloned().as_deref(),
|
||||
));
|
||||
*self.parent.lock() = parent.cloned();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_transform_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let this_spatial = node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("Node doesn't have a spatial?"))?;
|
||||
let relative_spatial = find_reference_space(&calling_client, deserialize(data)?)?;
|
||||
|
||||
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||
Some(this_spatial.as_ref()),
|
||||
Some(relative_spatial.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
serialize((
|
||||
mint::Vector3::from(position),
|
||||
mint::Quaternion::from(rotation),
|
||||
mint::Vector3::from(scale),
|
||||
))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
pub fn set_transform_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct TransformArgs<'a> {
|
||||
reference_space_path: Option<&'a str>,
|
||||
transform: Transform,
|
||||
}
|
||||
let transform_args: TransformArgs = deserialize(data)?;
|
||||
let reference_space_transform = transform_args
|
||||
.reference_space_path
|
||||
.map(|path| find_reference_space(&calling_client, path))
|
||||
.transpose()?;
|
||||
|
||||
node.spatial.get().unwrap().set_local_transform_components(
|
||||
reference_space_transform.as_deref(),
|
||||
transform_args.transform,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_spatial_parent_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
||||
node.spatial
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_spatial_parent(Some(&parent))
|
||||
}
|
||||
pub fn set_spatial_parent_in_place_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
||||
node.spatial
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_spatial_parent_in_place(Some(&parent))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_transform(
|
||||
transform: Transform,
|
||||
translation: bool,
|
||||
rotation: bool,
|
||||
scale: bool,
|
||||
) -> Result<Mat4> {
|
||||
let translation = translation
|
||||
.then_some(transform.position)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
||||
let rotation = rotation
|
||||
.then_some(transform.rotation)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Quat::IDENTITY.into());
|
||||
let scale = scale
|
||||
.then_some(transform.scale)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([1.0; 3]));
|
||||
|
||||
Ok(Mat4::from_scale_rotation_translation(
|
||||
scale.into(),
|
||||
rotation.into(),
|
||||
translation.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn find_spatial(
|
||||
calling_client: &Arc<Client>,
|
||||
node_name: &'static str,
|
||||
node_path: &str,
|
||||
) -> anyhow::Result<Arc<Spatial>> {
|
||||
Ok(calling_client
|
||||
.get_node(node_name, node_path)?
|
||||
.get_aspect(node_name, "spatial", |n| &n.spatial)?
|
||||
.clone())
|
||||
}
|
||||
pub fn find_spatial_parent(
|
||||
calling_client: &Arc<Client>,
|
||||
node_path: &str,
|
||||
) -> anyhow::Result<Arc<Spatial>> {
|
||||
find_spatial(calling_client, "Spatial parent", node_path)
|
||||
}
|
||||
pub fn find_reference_space(
|
||||
calling_client: &Arc<Client>,
|
||||
node_path: &str,
|
||||
) -> anyhow::Result<Arc<Spatial>> {
|
||||
find_spatial(calling_client, "Reference space", node_path)
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
let node = Node::create(client, "", "spatial", false);
|
||||
node.add_local_signal("createSpatial", create_spatial_flex);
|
||||
node.add_to_scenegraph();
|
||||
}
|
||||
|
||||
pub fn create_spatial_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateSpatialInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
}
|
||||
let info: CreateSpatialInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true)?;
|
||||
let node = node.add_to_scenegraph();
|
||||
Spatial::add_to(&node, Some(parent), transform)?;
|
||||
Ok(())
|
||||
}
|
||||
508
src/nodes/spatial/mod.rs
Normal file
508
src/nodes/spatial/mod.rs
Normal file
@@ -0,0 +1,508 @@
|
||||
pub mod zone;
|
||||
|
||||
use self::zone::{create_zone_flex, Zone};
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use glam::{vec3a, Mat4, Quat};
|
||||
use mint::Vector3;
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
use std::fmt::Debug;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, OnceLock, Weak};
|
||||
use stereokit::{bounds_grow_to_fit_box, Bounds};
|
||||
use tracing::instrument;
|
||||
|
||||
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
|
||||
|
||||
pub struct Spatial {
|
||||
uid: String,
|
||||
pub(super) node: Weak<Node>,
|
||||
self_ref: Weak<Spatial>,
|
||||
parent: Mutex<Option<Arc<Spatial>>>,
|
||||
old_parent: Mutex<Option<Arc<Spatial>>>,
|
||||
pub(super) transform: Mutex<Mat4>,
|
||||
zone: Mutex<Weak<Zone>>,
|
||||
children: Registry<Spatial>,
|
||||
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
|
||||
}
|
||||
|
||||
impl Spatial {
|
||||
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
|
||||
Arc::new_cyclic(|self_ref| Spatial {
|
||||
uid: nanoid!(),
|
||||
node,
|
||||
self_ref: self_ref.clone(),
|
||||
parent: Mutex::new(parent),
|
||||
old_parent: Mutex::new(None),
|
||||
transform: Mutex::new(transform),
|
||||
zone: Mutex::new(Weak::new()),
|
||||
children: Registry::new(),
|
||||
bounding_box_calc: OnceLock::default(),
|
||||
})
|
||||
}
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
parent: Option<Arc<Spatial>>,
|
||||
transform: Mat4,
|
||||
zoneable: bool,
|
||||
) -> Result<Arc<Spatial>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_none(),
|
||||
"Internal: Node already has a Spatial aspect!"
|
||||
);
|
||||
let spatial = Spatial::new(Arc::downgrade(node), parent, transform);
|
||||
node.add_local_method("get_bounding_box", Spatial::get_bounding_box_flex);
|
||||
node.add_local_method("get_transform", Spatial::get_transform_flex);
|
||||
node.add_local_signal("set_transform", Spatial::set_transform_flex);
|
||||
node.add_local_signal("set_spatial_parent", Spatial::set_spatial_parent_flex);
|
||||
node.add_local_signal(
|
||||
"set_spatial_parent_in_place",
|
||||
Spatial::set_spatial_parent_in_place_flex,
|
||||
);
|
||||
node.add_local_signal("set_zoneable", Spatial::set_zoneable_flex);
|
||||
node.add_local_method("field_distance", Spatial::field_distance_flex);
|
||||
node.add_local_method("field_normal", Spatial::field_normal_flex);
|
||||
node.add_local_method("field_closest_point", Spatial::field_closest_point_flex);
|
||||
if zoneable {
|
||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||
}
|
||||
let _ = node.spatial.set(spatial.clone());
|
||||
Ok(spatial)
|
||||
}
|
||||
|
||||
pub fn node(&self) -> Option<Arc<Node>> {
|
||||
self.node.upgrade()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
|
||||
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
|
||||
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
|
||||
world_to_space_matrix * space_to_world_matrix
|
||||
}
|
||||
|
||||
// the output bounds are probably way bigger than they need to be
|
||||
#[instrument(level = "debug")]
|
||||
pub fn get_bounding_box(&self) -> Bounds {
|
||||
let Some(node) = self.node() else {return Bounds::default()};
|
||||
let mut bounds = self
|
||||
.bounding_box_calc
|
||||
.get()
|
||||
.map(|b| (b)(&node))
|
||||
.unwrap_or_default();
|
||||
for child in self.children.get_valid_contents() {
|
||||
bounds = bounds_grow_to_fit_box(
|
||||
bounds,
|
||||
child.get_bounding_box(),
|
||||
Some(child.local_transform()),
|
||||
);
|
||||
}
|
||||
bounds
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn local_transform(&self) -> Mat4 {
|
||||
*self.transform.lock()
|
||||
}
|
||||
pub fn global_transform(&self) -> Mat4 {
|
||||
match self.get_parent() {
|
||||
Some(value) => value.global_transform() * *self.transform.lock(),
|
||||
None => *self.transform.lock(),
|
||||
}
|
||||
}
|
||||
#[instrument]
|
||||
pub fn set_local_transform(&self, transform: Mat4) {
|
||||
*self.transform.lock() = transform;
|
||||
}
|
||||
#[instrument(level = "debug", skip(self, reference_space))]
|
||||
pub fn set_local_transform_components(
|
||||
&self,
|
||||
reference_space: Option<&Spatial>,
|
||||
transform: Transform,
|
||||
) {
|
||||
if reference_space == Some(self) {
|
||||
self.set_local_transform(
|
||||
parse_transform(transform, true, true, true) * self.local_transform(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let reference_to_parent_transform = reference_space
|
||||
.map(|reference_space| {
|
||||
Spatial::space_to_space_matrix(Some(reference_space), self.get_parent().as_deref())
|
||||
})
|
||||
.unwrap_or(Mat4::IDENTITY);
|
||||
let mut local_transform_in_reference_space =
|
||||
reference_to_parent_transform.inverse() * self.local_transform();
|
||||
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
|
||||
local_transform_in_reference_space.to_scale_rotation_translation();
|
||||
|
||||
if let Some(pos) = transform.position {
|
||||
reference_space_pos = pos.into()
|
||||
}
|
||||
if let Some(rot) = transform.rotation {
|
||||
reference_space_rot = rot.into()
|
||||
} else if reference_space_rot.is_nan() {
|
||||
reference_space_rot = Quat::IDENTITY;
|
||||
}
|
||||
if let Some(scl) = transform.scale {
|
||||
reference_space_scl = scl.into()
|
||||
}
|
||||
|
||||
local_transform_in_reference_space = Mat4::from_scale_rotation_translation(
|
||||
reference_space_scl,
|
||||
reference_space_rot,
|
||||
reference_space_pos,
|
||||
);
|
||||
self.set_local_transform(
|
||||
reference_to_parent_transform * local_transform_in_reference_space,
|
||||
);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
|
||||
let mut current_ancestor = spatial;
|
||||
loop {
|
||||
if Arc::as_ptr(¤t_ancestor) == ptr::addr_of!(*self) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(parent) = current_ancestor.get_parent() {
|
||||
current_ancestor = parent;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
||||
self.parent.lock().clone()
|
||||
}
|
||||
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) {
|
||||
if let Some(parent) = self.get_parent() {
|
||||
parent.children.remove(self);
|
||||
}
|
||||
if let Some(new_parent) = &new_parent {
|
||||
new_parent
|
||||
.children
|
||||
.add_raw(&self.self_ref.upgrade().unwrap());
|
||||
}
|
||||
|
||||
*self.parent.lock() = new_parent;
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.as_ref()
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
}
|
||||
self.set_parent(parent);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.as_ref()
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
}
|
||||
|
||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||
Some(self),
|
||||
parent.as_deref(),
|
||||
));
|
||||
self.set_parent(parent);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_bounding_box_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let this_spatial = node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
|
||||
let relative_spatial_path: Option<&str> = deserialize(data)?;
|
||||
let bounds = if let Some(relative_spatial_path) = relative_spatial_path {
|
||||
let relative_spatial = find_reference_space(&calling_client, relative_spatial_path)?;
|
||||
let center =
|
||||
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||
.transform_point3([0.0; 3].into());
|
||||
let bounds: Bounds = Bounds {
|
||||
center,
|
||||
dimensions: [0.0; 3].into(),
|
||||
};
|
||||
bounds_grow_to_fit_box(
|
||||
bounds,
|
||||
this_spatial.get_bounding_box(),
|
||||
Some(Spatial::space_to_space_matrix(
|
||||
Some(&this_spatial),
|
||||
Some(&relative_spatial),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
this_spatial.get_bounding_box()
|
||||
};
|
||||
|
||||
serialize((
|
||||
mint::Vector3::from(bounds.center),
|
||||
mint::Vector3::from(bounds.dimensions),
|
||||
))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn get_transform_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let this_spatial = node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
|
||||
let relative_spatial = find_reference_space(&calling_client, deserialize(data)?)?;
|
||||
|
||||
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||
Some(this_spatial.as_ref()),
|
||||
Some(relative_spatial.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
serialize((
|
||||
mint::Vector3::from(position),
|
||||
mint::Quaternion::from(rotation),
|
||||
mint::Vector3::from(scale),
|
||||
))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
pub fn set_transform_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct TransformArgs<'a> {
|
||||
reference_space_path: Option<&'a str>,
|
||||
transform: Transform,
|
||||
}
|
||||
let transform_args: TransformArgs = deserialize(data)?;
|
||||
let reference_space_transform = transform_args
|
||||
.reference_space_path
|
||||
.map(|path| find_reference_space(&calling_client, path))
|
||||
.transpose()?;
|
||||
|
||||
node.spatial.get().unwrap().set_local_transform_components(
|
||||
reference_space_transform.as_deref(),
|
||||
transform_args.transform,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_spatial_parent_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
||||
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
|
||||
}
|
||||
pub fn set_spatial_parent_in_place_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
||||
node.spatial
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_spatial_parent_in_place(Some(parent))?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_zoneable_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let zoneable: bool = deserialize(data)?;
|
||||
let spatial = node.spatial.get().unwrap();
|
||||
if zoneable {
|
||||
ZONEABLE_REGISTRY.add_raw(spatial);
|
||||
} else {
|
||||
ZONEABLE_REGISTRY.remove(spatial);
|
||||
zone::release(spatial);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn field_distance_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
||||
let spatial = node.spatial.get().unwrap();
|
||||
|
||||
let output = fields
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
calling_client
|
||||
.get_node("Field", f?)
|
||||
.ok()?
|
||||
.get_aspect("Field", "field", |n| &n.field)
|
||||
.ok()
|
||||
.cloned()
|
||||
})
|
||||
.map(|f| f.map(|f| f.distance(spatial, point.into())))
|
||||
.collect::<Vec<Option<f32>>>();
|
||||
|
||||
Ok(serialize(output)?)
|
||||
}
|
||||
pub fn field_normal_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
||||
let spatial = node.spatial.get().unwrap();
|
||||
|
||||
let output = fields
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
calling_client
|
||||
.get_node("Field", f?)
|
||||
.ok()?
|
||||
.get_aspect("Field", "field", |n| &n.field)
|
||||
.ok()
|
||||
.cloned()
|
||||
})
|
||||
.map(|f| f.map(|f| Vector3::from(f.normal(spatial, point.into(), 0.001))))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(serialize(output)?)
|
||||
}
|
||||
pub fn field_closest_point_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
||||
let spatial = node.spatial.get().unwrap();
|
||||
|
||||
let output = fields
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
calling_client
|
||||
.get_node("Field", f?)
|
||||
.ok()?
|
||||
.get_aspect("Field", "field", |n| &n.field)
|
||||
.ok()
|
||||
.cloned()
|
||||
})
|
||||
.map(|f| f.map(|f| Vector3::from(f.closest_point(spatial, point.into(), 0.001))))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(serialize(output)?)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub(self) fn zone_distance(&self) -> f32 {
|
||||
self.zone
|
||||
.lock()
|
||||
.upgrade()
|
||||
.and_then(|zone| zone.field.upgrade())
|
||||
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
|
||||
.unwrap_or(f32::MAX)
|
||||
}
|
||||
}
|
||||
impl PartialEq for Spatial {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.uid == other.uid
|
||||
}
|
||||
}
|
||||
impl Debug for Spatial {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Spatial")
|
||||
.field("uid", &self.uid)
|
||||
.field("parent", &self.parent)
|
||||
.field("old_parent", &self.old_parent)
|
||||
.field("transform", &self.transform)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl Drop for Spatial {
|
||||
fn drop(&mut self) {
|
||||
ZONEABLE_REGISTRY.remove(self);
|
||||
zone::release(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||
let position = position
|
||||
.then_some(transform.position)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
||||
let rotation = rotation
|
||||
.then_some(transform.rotation)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Quat::IDENTITY.into());
|
||||
let scale = scale
|
||||
.then_some(transform.scale)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([1.0; 3]));
|
||||
|
||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||
}
|
||||
|
||||
pub fn find_spatial(
|
||||
calling_client: &Arc<Client>,
|
||||
node_name: &'static str,
|
||||
node_path: &str,
|
||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
||||
calling_client
|
||||
.get_node(node_name, node_path)?
|
||||
.get_aspect(node_name, "spatial", |n| &n.spatial)
|
||||
.cloned()
|
||||
}
|
||||
pub fn find_spatial_parent(
|
||||
calling_client: &Arc<Client>,
|
||||
node_path: &str,
|
||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
||||
find_spatial(calling_client, "Spatial parent", node_path)
|
||||
}
|
||||
pub fn find_reference_space(
|
||||
calling_client: &Arc<Client>,
|
||||
node_path: &str,
|
||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
||||
find_spatial(calling_client, "Reference space", node_path)
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "spatial", false);
|
||||
node.add_local_signal("create_spatial", create_spatial_flex);
|
||||
node.add_local_signal("create_zone", create_zone_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn create_spatial_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateSpatialInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
zoneable: bool,
|
||||
}
|
||||
let info: CreateSpatialInfo = deserialize(data)?;
|
||||
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, info.zoneable)?;
|
||||
Ok(())
|
||||
}
|
||||
169
src/nodes/spatial/zone.rs
Normal file
169
src/nodes/spatial/zone.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY};
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
nodes::{
|
||||
alias::{Alias, AliasInfo},
|
||||
fields::{find_field, Field},
|
||||
spatial::{find_spatial_parent, parse_transform},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::vec3a;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::{
|
||||
schemas::flex::{deserialize, serialize},
|
||||
values::Transform,
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
|
||||
let old_distance = spatial.zone_distance();
|
||||
let new_distance = zone
|
||||
.field
|
||||
.upgrade()
|
||||
.map(|field| field.distance(spatial, vec3a(0.0, 0.0, 0.0)))
|
||||
.unwrap_or(f32::MAX);
|
||||
if new_distance.abs() < old_distance.abs() {
|
||||
release(spatial);
|
||||
*spatial.old_parent.lock() = spatial.get_parent();
|
||||
*spatial.zone.lock() = Arc::downgrade(zone);
|
||||
zone.captured.add_raw(spatial);
|
||||
let node = zone.spatial.node.upgrade().unwrap();
|
||||
let _ = node.send_remote_signal("capture", &serialize(&spatial.uid).unwrap());
|
||||
}
|
||||
}
|
||||
pub fn release(spatial: &Spatial) {
|
||||
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take());
|
||||
let mut spatial_zone = spatial.zone.lock();
|
||||
if let Some(spatial_zone) = spatial_zone.upgrade() {
|
||||
let node = spatial_zone.spatial.node.upgrade().unwrap();
|
||||
spatial_zone.captured.remove(spatial);
|
||||
let _ = node.send_remote_signal("release", &serialize(&spatial.uid).unwrap());
|
||||
}
|
||||
*spatial_zone = Weak::new();
|
||||
}
|
||||
|
||||
pub struct Zone {
|
||||
spatial: Arc<Spatial>,
|
||||
pub field: Weak<Field>,
|
||||
zoneables: Mutex<FxHashMap<String, Arc<Node>>>,
|
||||
captured: Registry<Spatial>,
|
||||
}
|
||||
impl Zone {
|
||||
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: &Arc<Field>) -> Arc<Zone> {
|
||||
let zone = Arc::new(Zone {
|
||||
spatial,
|
||||
field: Arc::downgrade(field),
|
||||
zoneables: Mutex::new(FxHashMap::default()),
|
||||
captured: Registry::new(),
|
||||
});
|
||||
node.add_local_signal("capture", Zone::capture_flex);
|
||||
node.add_local_signal("release", Zone::release_flex);
|
||||
node.add_local_signal("update", Zone::update);
|
||||
let _ = node.zone.set(zone.clone());
|
||||
zone
|
||||
}
|
||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let zone = node.zone.get().unwrap();
|
||||
let capture_path: &str = deserialize(data)?;
|
||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
||||
capture(&spatial, zone);
|
||||
Ok(())
|
||||
}
|
||||
fn release_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let capture_path: &str = deserialize(data)?;
|
||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
||||
release(&spatial);
|
||||
Ok(())
|
||||
}
|
||||
fn update(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
||||
let zone = node.zone.get().unwrap();
|
||||
let Some(field) = zone.field.upgrade() else { return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed")) };
|
||||
let Some((zone_client, zone_node)) = zone
|
||||
.spatial
|
||||
.node
|
||||
.upgrade()
|
||||
.and_then(|n| n.get_client().zip(Some(n))) else { return Err(color_eyre::eyre::eyre!("No client on node?")) };
|
||||
let mut old_zoneables = zone.zoneables.lock();
|
||||
for (_uid, zoneable) in old_zoneables.iter() {
|
||||
zoneable.destroy();
|
||||
}
|
||||
let captured = zone.captured.get_valid_contents();
|
||||
let zoneables = ZONEABLE_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|zoneable| zoneable.node.upgrade().is_some())
|
||||
.filter(|zoneable| {
|
||||
if captured
|
||||
.iter()
|
||||
.any(|captured| Arc::ptr_eq(captured, zoneable))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
let spatial_zone_distance = zoneable.zone_distance();
|
||||
let self_zone_distance = field.distance(zoneable, vec3a(0.0, 0.0, 0.0));
|
||||
self_zone_distance < 0.0 && spatial_zone_distance > self_zone_distance
|
||||
})
|
||||
.filter_map(|zoneable| {
|
||||
let alias = Alias::create(
|
||||
&zone_client,
|
||||
zone_node.get_path(),
|
||||
&zoneable.uid,
|
||||
&zoneable.node.upgrade().unwrap(),
|
||||
AliasInfo {
|
||||
server_signals: vec![
|
||||
"set_transform",
|
||||
"set_spatial_parent",
|
||||
"set_spatial_parent_in_place",
|
||||
],
|
||||
server_methods: vec!["get_bounds", "get_transform"],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
Some((zoneable.uid.clone(), alias))
|
||||
})
|
||||
.collect::<FxHashMap<String, Arc<Node>>>();
|
||||
|
||||
for entered_uid in zoneables.keys().filter(|k| !old_zoneables.contains_key(*k)) {
|
||||
node.send_remote_signal("enter", &serialize(entered_uid)?)?;
|
||||
}
|
||||
for left_uid in old_zoneables.keys().filter(|k| !zoneables.contains_key(*k)) {
|
||||
node.send_remote_signal("leave", &serialize(left_uid)?)?;
|
||||
}
|
||||
|
||||
*old_zoneables = zoneables;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Zone {
|
||||
fn drop(&mut self) {
|
||||
for captured in self.captured.get_valid_contents() {
|
||||
release(&captured);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateZoneInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
}
|
||||
let info: CreateZoneInfo = deserialize(data)?;
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, false);
|
||||
let field = find_field(&calling_client, info.field_path)?;
|
||||
|
||||
let node =
|
||||
Node::create(&calling_client, "/spatial/zone", info.name, true).add_to_scenegraph()?;
|
||||
let space = Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Zone::add_to(&node, space, &field);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,60 +1,96 @@
|
||||
use crate::core::client::Client;
|
||||
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
|
||||
|
||||
use super::Node;
|
||||
use anyhow::{anyhow, Result};
|
||||
use super::{
|
||||
items::{ItemAcceptor, TypeInfo},
|
||||
spatial::find_spatial,
|
||||
Node,
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::sync::Arc;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref DESKTOP_STARTUP_IDS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
|
||||
pub static ref STARTUP_SETTINGS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct StartupSettings {
|
||||
pub transform: Mat4,
|
||||
pub acceptors: FxHashMap<&'static TypeInfo, Weak<ItemAcceptor>>,
|
||||
}
|
||||
impl StartupSettings {
|
||||
pub fn add_to(node: &Arc<Node>) {
|
||||
node.startup_settings
|
||||
.set(Mutex::new(StartupSettings::default()))
|
||||
.unwrap();
|
||||
let _ = node
|
||||
.startup_settings
|
||||
.set(Mutex::new(StartupSettings::default()));
|
||||
}
|
||||
|
||||
fn set_root_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let startup_id = flexbuffers::Reader::get_root(data)?.get_str()?;
|
||||
let spatial_node = calling_client
|
||||
.scenegraph
|
||||
.get_node(startup_id)
|
||||
.ok_or_else(|| anyhow!("Root spatial node does not exist"))?;
|
||||
let spatial = spatial_node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("Root spatial node is not a spatial"))?;
|
||||
let spatial = find_spatial(&calling_client, "Root spatial", deserialize(data)?)?;
|
||||
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_desktop_startup_id_flex(
|
||||
fn add_automatic_acceptor_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let acceptor_node = calling_client.get_node("Item acceptor", deserialize(data)?)?;
|
||||
let acceptor =
|
||||
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
|
||||
let mut startup_settings = node.startup_settings.get().unwrap().lock();
|
||||
startup_settings
|
||||
.acceptors
|
||||
.insert(acceptor.type_info, Arc::downgrade(acceptor));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_startup_token_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
_data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let id = nanoid::nanoid!();
|
||||
let data = flexbuffers::singleton(id.as_str());
|
||||
DESKTOP_STARTUP_IDS
|
||||
let data = serialize(&id)?;
|
||||
STARTUP_SETTINGS
|
||||
.lock()
|
||||
.insert(id, node.startup_settings.get().unwrap().lock().clone());
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
impl Debug for StartupSettings {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StartupSettings")
|
||||
.field("transform", &self.transform)
|
||||
.field(
|
||||
"acceptors",
|
||||
&self
|
||||
.acceptors
|
||||
.iter()
|
||||
.map(|(k, _)| k.type_name)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "startup", false);
|
||||
node.add_local_signal("createStartupSettings", create_startup_settings_flex);
|
||||
node.add_to_scenegraph();
|
||||
node.add_local_signal("create_startup_settings", create_startup_settings_flex);
|
||||
node.add_local_method(
|
||||
"get_connection_environment",
|
||||
get_connection_environment_flex,
|
||||
);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn create_startup_settings_flex(
|
||||
@@ -62,15 +98,49 @@ pub fn create_startup_settings_flex(
|
||||
calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let name = flexbuffers::Reader::get_root(data)?.get_str()?;
|
||||
let node = Node::create(&calling_client, "/startup/settings", name, true).add_to_scenegraph();
|
||||
let node = Node::create(
|
||||
&calling_client,
|
||||
"/startup/settings",
|
||||
deserialize(data)?,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph()?;
|
||||
StartupSettings::add_to(&node);
|
||||
|
||||
node.add_local_signal("setRoot", StartupSettings::set_root_flex);
|
||||
node.add_local_signal("set_root", StartupSettings::set_root_flex);
|
||||
node.add_local_signal(
|
||||
"add_automatic_acceptor",
|
||||
StartupSettings::add_automatic_acceptor_flex,
|
||||
);
|
||||
node.add_local_method(
|
||||
"generateDesktopStartupID",
|
||||
StartupSettings::generate_desktop_startup_id_flex,
|
||||
"generate_startup_token",
|
||||
StartupSettings::generate_startup_token_flex,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! var_env_insert {
|
||||
($env:ident, $name:ident) => {
|
||||
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
|
||||
};
|
||||
}
|
||||
pub fn get_connection_environment_flex(
|
||||
_node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
_data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
||||
var_env_insert!(env, STARDUST_INSTANCE);
|
||||
#[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());
|
||||
}
|
||||
|
||||
Ok(serialize(env)?)
|
||||
}
|
||||
|
||||
56
src/objects/input/eye_pointer.rs
Normal file
56
src/objects/input/eye_pointer.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
input::{pointer::Pointer, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use nanoid::nanoid;
|
||||
use serde::Serialize;
|
||||
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
|
||||
use std::sync::Arc;
|
||||
use stereokit::StereoKitMultiThread;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct KeyboardEvent {
|
||||
pub keyboard: String,
|
||||
pub keymap: Option<String>,
|
||||
pub keys_up: Option<Vec<u32>>,
|
||||
pub keys_down: Option<Vec<u32>>,
|
||||
}
|
||||
|
||||
pub struct EyePointer {
|
||||
spatial: Arc<Spatial>,
|
||||
pointer: Arc<InputMethod>,
|
||||
}
|
||||
impl EyePointer {
|
||||
pub fn new() -> Result<Self> {
|
||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
||||
let pointer =
|
||||
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
|
||||
|
||||
Ok(EyePointer { spatial, pointer })
|
||||
}
|
||||
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
|
||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
||||
let ray = sk.input_eyes();
|
||||
self.spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
ray.orientation,
|
||||
ray.position,
|
||||
));
|
||||
{
|
||||
// Set pointer input datamap
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
let mut map = fbb.start_map();
|
||||
map.push("eye", 2);
|
||||
map.end_map();
|
||||
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod eye_pointer;
|
||||
pub mod mouse_pointer;
|
||||
pub mod sk_controller;
|
||||
pub mod sk_hand;
|
||||
|
||||
@@ -1,59 +1,153 @@
|
||||
use crate::nodes::{
|
||||
input::{pointer::Pointer, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
};
|
||||
use glam::{vec3, Mat4};
|
||||
use stardust_xr::{schemas::flat::Datamap, values::Transform};
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit::{
|
||||
input::{ButtonState, Key, Ray},
|
||||
StereoKit,
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY},
|
||||
fields::Ray,
|
||||
input::{pointer::Pointer, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3, Mat4, Vec3};
|
||||
use nanoid::nanoid;
|
||||
use serde::Serialize;
|
||||
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
|
||||
use tracing::instrument;
|
||||
|
||||
const SK_KEYMAP: &str = include_str!("sk.kmp");
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct KeyboardEvent {
|
||||
pub keyboard: String,
|
||||
pub keymap: Option<String>,
|
||||
pub keys_up: Option<Vec<u32>>,
|
||||
pub keys_down: Option<Vec<u32>>,
|
||||
}
|
||||
|
||||
pub struct MousePointer {
|
||||
node: Arc<Node>,
|
||||
spatial: Arc<Spatial>,
|
||||
pointer: Arc<InputMethod>,
|
||||
keyboard_sender: Arc<PulseSender>,
|
||||
}
|
||||
impl MousePointer {
|
||||
pub fn new() -> Self {
|
||||
MousePointer {
|
||||
pointer: InputMethod::new(
|
||||
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
|
||||
InputType::Pointer(Pointer::default()),
|
||||
),
|
||||
}
|
||||
pub fn new() -> Result<Self> {
|
||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
||||
let pointer =
|
||||
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
|
||||
|
||||
let keyboard_mask = {
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
let mut map = fbb.start_map();
|
||||
map.push("keyboard", "xkbv1");
|
||||
map.end_map();
|
||||
Mask(fbb.take_buffer())
|
||||
};
|
||||
let keyboard_sender = PulseSender::add_to(&node, keyboard_mask).unwrap();
|
||||
|
||||
Ok(MousePointer {
|
||||
node,
|
||||
spatial,
|
||||
pointer,
|
||||
keyboard_sender,
|
||||
})
|
||||
}
|
||||
pub fn update(&self, sk: &StereoKit) {
|
||||
if let Some(ray) = Ray::from_mouse(sk.input_mouse()) {
|
||||
self.pointer.spatial.set_local_transform_components(
|
||||
None,
|
||||
Transform {
|
||||
position: Some(ray.pos),
|
||||
rotation: Some(
|
||||
glam::Quat::from_rotation_arc(vec3(0.0, 0.0, 1.0), ray.dir.into()).into(),
|
||||
),
|
||||
scale: None,
|
||||
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
|
||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
||||
let mouse = sk.input_mouse();
|
||||
|
||||
let ray = ray_from_mouse(mouse.pos).unwrap();
|
||||
self.spatial.set_local_transform(
|
||||
Mat4::look_to_rh(
|
||||
Vec3::from(ray.pos),
|
||||
Vec3::from(ray.dir),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
)
|
||||
.inverse(),
|
||||
);
|
||||
{
|
||||
// Set pointer input datamap
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
let mut map = fbb.start_map();
|
||||
map.push(
|
||||
"select",
|
||||
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
},
|
||||
);
|
||||
map.push(
|
||||
"grab",
|
||||
if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
},
|
||||
);
|
||||
let mut scroll_vec = map.start_vector("scroll");
|
||||
scroll_vec.push(0_f32);
|
||||
scroll_vec.push(mouse.scroll_change / 120.0);
|
||||
scroll_vec.end_vector();
|
||||
map.end_map();
|
||||
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
}
|
||||
self.send_keyboard_input(sk);
|
||||
}
|
||||
|
||||
fn send_keyboard_input(&self, sk: &impl StereoKitMultiThread) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
.map(|(rx, _)| rx);
|
||||
|
||||
if let Some(rx) = rx {
|
||||
let mut keys_up = vec![];
|
||||
let mut keys_down = vec![];
|
||||
let keys = (8_u32..254)
|
||||
.filter_map(|i| Some((i, Key::try_from(i).ok()?)))
|
||||
.map(|(i, k)| (i - 8, sk.input_key(k)));
|
||||
for (key, state) in keys {
|
||||
if state.contains(ButtonState::JUST_ACTIVE) {
|
||||
keys_down.push(key);
|
||||
} else if state.contains(ButtonState::JUST_INACTIVE) {
|
||||
keys_up.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
let key_event = KeyboardEvent {
|
||||
keyboard: "xkbv1".to_string(),
|
||||
keymap: Some(SK_KEYMAP.to_string()),
|
||||
keys_up: Some(keys_up),
|
||||
keys_down: Some(keys_down),
|
||||
};
|
||||
let mut serializer = flexbuffers::FlexbufferSerializer::new();
|
||||
let _ = key_event.serialize(&mut serializer);
|
||||
rx.send_data(&self.node.uid, serializer.take_buffer())
|
||||
.unwrap();
|
||||
}
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
let mut map = fbb.start_map();
|
||||
map.push(
|
||||
"select",
|
||||
if sk.input_key(Key::MouseLeft).contains(ButtonState::Active) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
},
|
||||
);
|
||||
map.push(
|
||||
"grab",
|
||||
if sk.input_key(Key::MouseRight).contains(ButtonState::Active) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
},
|
||||
);
|
||||
map.end_map();
|
||||
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
275
src/objects/input/sk.kmp
Normal file
275
src/objects/input/sk.kmp
Normal file
@@ -0,0 +1,275 @@
|
||||
xkb_keymap {
|
||||
|
||||
default xkb_keycodes "basic" {
|
||||
minimum = 8;
|
||||
maximum = 255;
|
||||
|
||||
<backspace> = 8;
|
||||
<tab> = 9;
|
||||
<return> = 13;
|
||||
<shift> = 16;
|
||||
<ctrl> = 17;
|
||||
<alt> = 18;
|
||||
<caps_lock> = 20;
|
||||
<esc> = 27;
|
||||
<space> = 32;
|
||||
<end> = 35;
|
||||
<home> = 36;
|
||||
<left> = 37;
|
||||
<right> = 39;
|
||||
<up> = 38;
|
||||
<down> = 40;
|
||||
<page_up> = 33;
|
||||
<page_down> = 34;
|
||||
<printscreen> = 42;
|
||||
<key_insert> = 45;
|
||||
<del> = 46;
|
||||
|
||||
<0> = 48;
|
||||
<1> = 49;
|
||||
<2> = 50;
|
||||
<3> = 51;
|
||||
<4> = 52;
|
||||
<5> = 53;
|
||||
<6> = 54;
|
||||
<7> = 55;
|
||||
<8> = 56;
|
||||
<9> = 57;
|
||||
|
||||
<a> = 65;
|
||||
<b> = 66;
|
||||
<c> = 67;
|
||||
<d> = 68;
|
||||
<e> = 69;
|
||||
<f> = 70;
|
||||
<g> = 71;
|
||||
<h> = 72;
|
||||
<i> = 73;
|
||||
<j> = 74;
|
||||
<k> = 75;
|
||||
<l> = 76;
|
||||
<m> = 77;
|
||||
<n> = 78;
|
||||
<o> = 79;
|
||||
<p> = 80;
|
||||
<q> = 81;
|
||||
<r> = 82;
|
||||
<s> = 83;
|
||||
<t> = 84;
|
||||
<u> = 85;
|
||||
<v> = 86;
|
||||
<w> = 87;
|
||||
<x> = 88;
|
||||
<y> = 89;
|
||||
<z> = 90;
|
||||
|
||||
<num0> = 96;
|
||||
<num1> = 97;
|
||||
<num2> = 98;
|
||||
<num3> = 99;
|
||||
<num4> = 100;
|
||||
<num5> = 101;
|
||||
<num6> = 102;
|
||||
<num7> = 103;
|
||||
<num8> = 104;
|
||||
<num9> = 105;
|
||||
|
||||
<f1> = 112;
|
||||
<f2> = 113;
|
||||
<f3> = 114;
|
||||
<f4> = 115;
|
||||
<f5> = 116;
|
||||
<f6> = 117;
|
||||
<f7> = 118;
|
||||
<f8> = 119;
|
||||
<f9> = 120;
|
||||
<f10> = 121;
|
||||
<f11> = 122;
|
||||
<f12> = 123;
|
||||
|
||||
<comma> = 188;
|
||||
<period> = 190;
|
||||
<slash_fwd> = 191;
|
||||
<slash_back> = 220;
|
||||
<semicolon> = 186;
|
||||
<apostrophe> = 222;
|
||||
<bracket_open> = 219;
|
||||
<bracket_close> = 221;
|
||||
<minus> = 189;
|
||||
<equals> = 187;
|
||||
<backtick> = 192;
|
||||
<lcmd> = 91;
|
||||
<rcmd> = 92;
|
||||
<multiply> = 106;
|
||||
<add> = 107;
|
||||
<subtract> = 109;
|
||||
<decimal> = 110;
|
||||
<divide> = 111;
|
||||
};
|
||||
|
||||
partial default xkb_types "basic" {
|
||||
virtual_modifiers Alt;
|
||||
|
||||
type "ONE_LEVEL" {
|
||||
modifiers= none;
|
||||
level_name[1]= "Any";
|
||||
};
|
||||
type "TWO_LEVEL" {
|
||||
modifiers= Shift;
|
||||
map[Shift]= 2;
|
||||
level_name[1]= "Base";
|
||||
level_name[2]= "Shift";
|
||||
};
|
||||
type "ALPHABETIC" {
|
||||
modifiers= Shift+Lock;
|
||||
map[Shift]= 2;
|
||||
map[Lock]= 2;
|
||||
level_name[1]= "Base";
|
||||
level_name[2]= "Caps";
|
||||
};
|
||||
type "SHIFT+ALT" {
|
||||
modifiers= Shift+Alt;
|
||||
map[Shift+Alt]= 2;
|
||||
level_name[1]= "Base";
|
||||
level_name[2]= "Shift+Alt";
|
||||
};
|
||||
type "PC_CONTROL_LEVEL2" {
|
||||
modifiers= Control;
|
||||
map[Control]= 2;
|
||||
level_name[1]= "Base";
|
||||
level_name[2]= "Control";
|
||||
};
|
||||
};
|
||||
|
||||
partial default xkb_compatibility "basic" {
|
||||
interpret.useModMapMods= AnyLevel;
|
||||
interpret.repeat= False;
|
||||
|
||||
interpret ISO_Level2_Latch+Exactly(Shift) {
|
||||
useModMapMods=level1;
|
||||
action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
|
||||
};
|
||||
interpret Caps_Lock+AnyOfOrNone(all) {
|
||||
action= LockMods(modifiers=Lock);
|
||||
};
|
||||
indicator "Caps Lock" {
|
||||
whichModState= locked;
|
||||
modifiers= Lock;
|
||||
};
|
||||
};
|
||||
|
||||
default xkb_symbols "basic" {
|
||||
name[Group1]="English (US)";
|
||||
key <backspace> { [ BackSpace, BackSpace ] };
|
||||
key <tab> { [ Tab, ISO_Left_Tab ] };
|
||||
key <return> { [ Return ] };
|
||||
key <shift> { [ Shift_L ] };
|
||||
key <shift> { [ Shift_R ] };
|
||||
key <ctrl> { [ Control_L ] };
|
||||
key <ctrl> { [ Control_R ] };
|
||||
key <alt> { [ Alt_L ] };
|
||||
key <alt> { [ Alt_R ] };
|
||||
key <caps_lock> { [ Caps_Lock ] };
|
||||
key <esc> { [ Escape ] };
|
||||
key <space> { [ space ] };
|
||||
key <end> { [ End ] };
|
||||
key <home> { [ Home ] };
|
||||
key <left> { [ Left ] };
|
||||
key <right> { [ Right ] };
|
||||
key <up> { [ Up ] };
|
||||
key <down> { [ Down ] };
|
||||
key <page_up> { [ Page_Up ] };
|
||||
key <page_down> { [ Page_Down ] };
|
||||
key <printscreen> { [ Print ] };
|
||||
key <key_insert> { [ Insert ] };
|
||||
key <del> { [ Delete ] };
|
||||
|
||||
key <1> { [ 1, exclam ] };
|
||||
key <2> { [ 2, at ] };
|
||||
key <3> { [ 3, numbersign ] };
|
||||
key <4> { [ 4, dollar ] };
|
||||
key <5> { [ 5, percent ] };
|
||||
key <6> { [ 6, asciicircum ] };
|
||||
key <7> { [ 7, ampersand ] };
|
||||
key <8> { [ 8, asterisk ] };
|
||||
key <9> { [ 9, parenleft ] };
|
||||
key <0> { [ 0, parenright ] };
|
||||
|
||||
key <a> { [ a, A ] };
|
||||
key <b> { [ b, B ] };
|
||||
key <c> { [ c, C ] };
|
||||
key <d> { [ d, D ] };
|
||||
key <e> { [ e, E ] };
|
||||
key <f> { [ f, F ] };
|
||||
key <g> { [ g, G ] };
|
||||
key <h> { [ h, H ] };
|
||||
key <i> { [ i, I ] };
|
||||
key <j> { [ j, J ] };
|
||||
key <k> { [ k, K ] };
|
||||
key <l> { [ l, L ] };
|
||||
key <m> { [ m, M ] };
|
||||
key <n> { [ n, N ] };
|
||||
key <o> { [ o, O ] };
|
||||
key <p> { [ p, P ] };
|
||||
key <q> { [ q, Q ] };
|
||||
key <r> { [ r, R ] };
|
||||
key <s> { [ s, S ] };
|
||||
key <t> { [ t, T ] };
|
||||
key <u> { [ u, U ] };
|
||||
key <v> { [ v, V ] };
|
||||
key <w> { [ w, W ] };
|
||||
key <x> { [ x, X ] };
|
||||
key <y> { [ y, Y ] };
|
||||
key <z> { [ z, Z ] };
|
||||
|
||||
key <num0> { [ KP_0 ] };
|
||||
key <num1> { [ KP_1 ] };
|
||||
key <num2> { [ KP_2 ] };
|
||||
key <num3> { [ KP_3 ] };
|
||||
key <num4> { [ KP_4 ] };
|
||||
key <num5> { [ KP_5 ] };
|
||||
key <num6> { [ KP_6 ] };
|
||||
key <num7> { [ KP_7 ] };
|
||||
key <num8> { [ KP_8 ] };
|
||||
key <num9> { [ KP_9 ] };
|
||||
|
||||
key <f1> { [ F1 ] };
|
||||
key <f2> { [ F2 ] };
|
||||
key <f3> { [ F3 ] };
|
||||
key <f4> { [ F4 ] };
|
||||
key <f5> { [ F5 ] };
|
||||
key <f6> { [ F6 ] };
|
||||
key <f7> { [ F7 ] };
|
||||
key <f8> { [ F8 ] };
|
||||
key <f9> { [ F9 ] };
|
||||
key <f10> { [ F10 ] };
|
||||
key <f11> { [ F11 ] };
|
||||
key <f12> { [ F12 ] };
|
||||
|
||||
key <comma> { [ comma, less ] };
|
||||
key <period> { [ period, greater ] };
|
||||
key <slash_fwd> { [ slash, question ] };
|
||||
key <slash_back> { [ backslash, bar ] };
|
||||
key <semicolon> { [ semicolon, colon ] };
|
||||
key <apostrophe> { [ apostrophe ] };
|
||||
key <bracket_open> { [ bracketleft, braceleft ] };
|
||||
key <bracket_close> { [ bracketright, braceright ] };
|
||||
key <minus> { [ minus, underscore ] };
|
||||
key <equals> { [ equal, plus ] };
|
||||
key <backtick> { [ grave, asciitilde ] };
|
||||
key <lcmd> { [ Super_L ] };
|
||||
key <rcmd> { [ Super_R ] };
|
||||
key <multiply> { [ KP_Multiply ] };
|
||||
key <add> { [ KP_Add ] };
|
||||
key <subtract> { [ KP_Subtract ] };
|
||||
key <decimal> { [ KP_Decimal ] };
|
||||
key <divide> { [ KP_Divide ] };
|
||||
|
||||
modifier_map Shift { <shift> };
|
||||
modifier_map Lock { <caps_lock> };
|
||||
modifier_map Control { <caps_lock> };
|
||||
modifier_map Mod1 { <alt> };
|
||||
modifier_map Mod4 { <lcmd>, <rcmd> };
|
||||
};
|
||||
|
||||
};
|
||||
@@ -1,40 +1,50 @@
|
||||
use crate::nodes::{
|
||||
input::{tip::Tip, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
input::{tip::Tip, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use stardust_xr::{schemas::flat::Datamap, values::Transform};
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit::{
|
||||
input::{ButtonState, Handed},
|
||||
StereoKit,
|
||||
use nanoid::nanoid;
|
||||
use stardust_xr::{
|
||||
schemas::{flat::Datamap, flex::flexbuffers},
|
||||
values::Transform,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use stereokit::{ButtonState, Handed, StereoKitMultiThread};
|
||||
use tracing::instrument;
|
||||
|
||||
pub struct SkController {
|
||||
tip: Arc<InputMethod>,
|
||||
_node: Arc<Node>,
|
||||
input: Arc<InputMethod>,
|
||||
handed: Handed,
|
||||
}
|
||||
impl SkController {
|
||||
pub fn new(handed: Handed) -> Self {
|
||||
SkController {
|
||||
tip: InputMethod::new(
|
||||
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
|
||||
InputType::Tip(Tip::default()),
|
||||
),
|
||||
pub fn new(handed: Handed) -> Result<Self> {
|
||||
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
|
||||
let tip = InputType::Tip(Tip::default());
|
||||
let input = InputMethod::add_to(&_node, tip, None)?;
|
||||
Ok(SkController {
|
||||
_node,
|
||||
input,
|
||||
handed,
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, sk: &StereoKit) {
|
||||
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
|
||||
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
|
||||
let controller = sk.input_controller(self.handed);
|
||||
*self.tip.enabled.lock() = controller.tracked.contains(ButtonState::Active);
|
||||
if *self.tip.enabled.lock() {
|
||||
self.tip.spatial.set_local_transform_components(
|
||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
|
||||
if *self.input.enabled.lock() {
|
||||
self.input.spatial.set_local_transform_components(
|
||||
None,
|
||||
Transform {
|
||||
position: Some(controller.pose.position),
|
||||
rotation: Some(controller.pose.orientation),
|
||||
scale: None,
|
||||
},
|
||||
Transform::from_position_rotation(
|
||||
controller.pose.position,
|
||||
controller.pose.orientation,
|
||||
),
|
||||
);
|
||||
}
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
@@ -42,6 +52,6 @@ impl SkController {
|
||||
map.push("select", controller.trigger);
|
||||
map.push("grab", controller.grip);
|
||||
map.end_map();
|
||||
*self.tip.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,60 @@
|
||||
use crate::nodes::{
|
||||
input::{hand::Hand, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
input::{hand::Hand, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use stardust_xr::schemas::flat::{Datamap, Hand as FlatHand, Joint};
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit::{
|
||||
input::{ButtonState, Handed, Joint as SkJoint},
|
||||
StereoKit,
|
||||
use nanoid::nanoid;
|
||||
use stardust_xr::schemas::{
|
||||
flat::{Datamap, Hand as FlatHand, Joint},
|
||||
flex::flexbuffers,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
|
||||
use tracing::instrument;
|
||||
|
||||
fn convert_joint(joint: SkJoint) -> Joint {
|
||||
fn convert_joint(joint: HandJoint) -> Joint {
|
||||
Joint {
|
||||
position: joint.position,
|
||||
rotation: joint.orientation,
|
||||
position: joint.position.into(),
|
||||
rotation: joint.orientation.into(),
|
||||
radius: joint.radius,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SkHand {
|
||||
hand: Arc<InputMethod>,
|
||||
_node: Arc<Node>,
|
||||
input: Arc<InputMethod>,
|
||||
handed: Handed,
|
||||
}
|
||||
impl SkHand {
|
||||
pub fn new(handed: Handed) -> Self {
|
||||
SkHand {
|
||||
hand: InputMethod::new(
|
||||
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
|
||||
InputType::Hand(Box::new(Hand {
|
||||
base: FlatHand {
|
||||
right: handed == Handed::Right,
|
||||
..Default::default()
|
||||
},
|
||||
})),
|
||||
),
|
||||
pub fn new(handed: Handed) -> Result<Self> {
|
||||
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
|
||||
let hand = InputType::Hand(Box::new(Hand {
|
||||
base: FlatHand {
|
||||
right: handed == Handed::Right,
|
||||
..Default::default()
|
||||
},
|
||||
}));
|
||||
let input = InputMethod::add_to(&_node, hand, None)?;
|
||||
Ok(SkHand {
|
||||
_node,
|
||||
input,
|
||||
handed,
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, sk: &StereoKit) {
|
||||
#[instrument(level = "debug", name = "Update Hand Input Method", skip_all)]
|
||||
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
|
||||
let sk_hand = sk.input_hand(self.handed);
|
||||
if let InputType::Hand(hand) = &mut *self.hand.specialization.lock() {
|
||||
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
|
||||
let controller = sk.input_controller(self.handed);
|
||||
*self.hand.enabled.lock() = controller.tracked.contains(ButtonState::Inactive)
|
||||
&& sk_hand.tracked_state.contains(ButtonState::Active);
|
||||
if *self.hand.enabled.lock() {
|
||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE)
|
||||
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE);
|
||||
if *self.input.enabled.lock() {
|
||||
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
||||
hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
|
||||
hand.base.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
|
||||
@@ -62,13 +73,13 @@ impl SkHand {
|
||||
finger.metacarpal = convert_joint(sk_finger[0]);
|
||||
}
|
||||
|
||||
hand.base.palm.position = sk_hand.palm.position;
|
||||
hand.base.palm.rotation = sk_hand.palm.orientation;
|
||||
hand.base.palm.position = sk_hand.palm.position.into();
|
||||
hand.base.palm.rotation = sk_hand.palm.orientation.into();
|
||||
hand.base.palm.radius =
|
||||
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
||||
|
||||
hand.base.wrist.position = sk_hand.wrist.position;
|
||||
hand.base.wrist.rotation = sk_hand.wrist.orientation;
|
||||
hand.base.wrist.position = sk_hand.wrist.position.into();
|
||||
hand.base.wrist.rotation = sk_hand.wrist.orientation.into();
|
||||
hand.base.wrist.radius =
|
||||
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
|
||||
|
||||
@@ -77,9 +88,9 @@ impl SkHand {
|
||||
}
|
||||
let mut fbb = flexbuffers::Builder::default();
|
||||
let mut map = fbb.start_map();
|
||||
map.push("grabStrength", sk_hand.grip_activation);
|
||||
map.push("pinchStrength", sk_hand.pinch_activation);
|
||||
map.push("grab_strength", sk_hand.grip_activation);
|
||||
map.push("pinch_strength", sk_hand.pinch_activation);
|
||||
map.end_map();
|
||||
*self.hand.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod input;
|
||||
pub mod play_space;
|
||||
|
||||
73
src/objects/play_space.rs
Normal file
73
src/objects/play_space.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use mint::Vector2;
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stereokit::StereoKitMultiThread;
|
||||
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
data::{Mask, PulseReceiver},
|
||||
fields::{r#box::BoxField, Field},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct PlaySpaceMap {
|
||||
play_space: (),
|
||||
size: Vector2<f32>,
|
||||
}
|
||||
impl Default for PlaySpaceMap {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
play_space: (),
|
||||
size: [0.0; 2].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlaySpace {
|
||||
_node: Arc<Node>,
|
||||
spatial: Arc<Spatial>,
|
||||
field: Arc<Field>,
|
||||
_pulse_rx: Arc<PulseReceiver>,
|
||||
}
|
||||
impl PlaySpace {
|
||||
pub fn new() -> Result<Self> {
|
||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false)?;
|
||||
let field = BoxField::add_to(&node, [0.0; 3].into())?;
|
||||
|
||||
let pulse_rx =
|
||||
PulseReceiver::add_to(&node, field.clone(), Mask::from_struct::<PlaySpaceMap>())?;
|
||||
|
||||
Ok(PlaySpace {
|
||||
_node: node,
|
||||
spatial,
|
||||
field,
|
||||
_pulse_rx: pulse_rx,
|
||||
})
|
||||
}
|
||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
||||
let pose = sk.world_get_bounds_pose();
|
||||
self.spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
pose.orientation,
|
||||
pose.position,
|
||||
));
|
||||
let Field::Box(box_field) = self.field.as_ref() else {return};
|
||||
box_field.set_size(
|
||||
[
|
||||
sk.world_get_bounds_size().x,
|
||||
0.0,
|
||||
sk.world_get_bounds_size().y,
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
use super::{state::WaylandState, surface::CoreSurface};
|
||||
use crate::wayland::surface::CoreSurface;
|
||||
|
||||
use super::state::{ClientState, WaylandState};
|
||||
use portable_atomic::{AtomicU32, Ordering};
|
||||
use smithay::{
|
||||
delegate_compositor,
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
wayland::compositor::{self, CompositorHandler, CompositorState},
|
||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
|
||||
wayland::compositor::{self, CompositorClientState, CompositorHandler, CompositorState},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
|
||||
impl CompositorHandler for WaylandState {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
@@ -11,16 +16,29 @@ impl CompositorHandler for WaylandState {
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
compositor::with_states(surface, |data| {
|
||||
data.data_map.insert_if_missing_threadsafe(|| {
|
||||
CoreSurface::new(
|
||||
&self.weak_ref.upgrade().unwrap(),
|
||||
&self.display,
|
||||
self.display_handle.clone(),
|
||||
surface,
|
||||
)
|
||||
})
|
||||
debug!(?surface, "Surface commit");
|
||||
let mut count = 0;
|
||||
let core_surface = compositor::with_states(surface, |data| {
|
||||
let count_new = data
|
||||
.data_map
|
||||
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
|
||||
if !count_new {
|
||||
count = data
|
||||
.data_map
|
||||
.get::<AtomicU32>()
|
||||
.unwrap()
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
||||
});
|
||||
if let Some(core_surface) = core_surface {
|
||||
core_surface.commit(count);
|
||||
}
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,96 @@
|
||||
use super::state::WaylandState;
|
||||
use smithay::{
|
||||
delegate_kde_decoration, delegate_xdg_decoration,
|
||||
reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
|
||||
wayland::shell::{
|
||||
self, kde::decoration::KdeDecorationHandler, xdg::decoration::XdgDecorationHandler,
|
||||
delegate_kde_decoration,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::{
|
||||
decoration::zv1::server::{
|
||||
zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1},
|
||||
zxdg_toplevel_decoration_v1::{self, Mode, ZxdgToplevelDecorationV1},
|
||||
},
|
||||
shell::server::xdg_toplevel::XdgToplevel,
|
||||
},
|
||||
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{
|
||||
Mode as KdeMode, OrgKdeKwinServerDecoration,
|
||||
},
|
||||
wayland_server::{
|
||||
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle,
|
||||
GlobalDispatch, New, Resource, WEnum, Weak,
|
||||
},
|
||||
},
|
||||
wayland::shell::{self, kde::decoration::KdeDecorationHandler},
|
||||
};
|
||||
|
||||
impl XdgDecorationHandler for WaylandState {
|
||||
fn new_decoration(&mut self, toplevel: smithay::wayland::shell::xdg::ToplevelSurface) {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.decoration_mode = Some(Mode::ServerSide);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
}
|
||||
|
||||
fn request_mode(
|
||||
&mut self,
|
||||
_toplevel: smithay::wayland::shell::xdg::ToplevelSurface,
|
||||
_mode: smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
|
||||
impl GlobalDispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
|
||||
fn bind(
|
||||
_state: &mut WaylandState,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<ZxdgDecorationManagerV1>,
|
||||
_global_data: &(),
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
data_init.init(resource, ());
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &ZxdgDecorationManagerV1,
|
||||
request: zxdg_decoration_manager_v1::Request,
|
||||
_data: &(),
|
||||
_dhandle: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
zxdg_decoration_manager_v1::Request::Destroy => (),
|
||||
zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => {
|
||||
data_init.init(id, toplevel.downgrade());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Dispatch<ZxdgToplevelDecorationV1, Weak<XdgToplevel>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
resource: &ZxdgToplevelDecorationV1,
|
||||
request: zxdg_toplevel_decoration_v1::Request,
|
||||
_data: &Weak<XdgToplevel>,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
zxdg_toplevel_decoration_v1::Request::SetMode { mode: _ } => {
|
||||
resource.configure(Mode::ServerSide);
|
||||
}
|
||||
zxdg_toplevel_decoration_v1::Request::UnsetMode => {
|
||||
resource.configure(Mode::ServerSide);
|
||||
}
|
||||
zxdg_toplevel_decoration_v1::Request::Destroy => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_mode(&mut self, _toplevel: smithay::wayland::shell::xdg::ToplevelSurface) {}
|
||||
}
|
||||
delegate_xdg_decoration!(WaylandState);
|
||||
|
||||
impl KdeDecorationHandler for WaylandState {
|
||||
fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState {
|
||||
&self.kde_decoration_state
|
||||
}
|
||||
|
||||
fn new_decoration(&mut self, _surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) {
|
||||
decoration.mode(KdeMode::Server);
|
||||
}
|
||||
|
||||
fn request_mode(
|
||||
&mut self,
|
||||
_surface: &WlSurface,
|
||||
decoration: &OrgKdeKwinServerDecoration,
|
||||
_mode: WEnum<KdeMode>,
|
||||
) {
|
||||
decoration.mode(KdeMode::Server);
|
||||
}
|
||||
}
|
||||
delegate_kde_decoration!(WaylandState);
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
pub mod compositor;
|
||||
mod compositor;
|
||||
mod data_device;
|
||||
pub mod decoration;
|
||||
mod decoration;
|
||||
pub mod panel_item;
|
||||
pub mod seat;
|
||||
pub mod shaders;
|
||||
pub mod state;
|
||||
pub mod surface;
|
||||
pub mod xdg_activation;
|
||||
pub mod xdg_shell;
|
||||
mod seat;
|
||||
mod shaders;
|
||||
mod state;
|
||||
mod surface;
|
||||
// mod xdg_activation;
|
||||
mod xdg_shell;
|
||||
|
||||
use self::{panel_item::PanelItem, state::WaylandState, surface::CORE_SURFACES};
|
||||
use crate::wayland::state::ClientState;
|
||||
use anyhow::{ensure, Result};
|
||||
use self::{state::WaylandState, surface::CORE_SURFACES};
|
||||
use crate::{core::task, wayland::state::ClientState};
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use global_counter::primitive::exact::CounterU32;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use slog::Logger;
|
||||
use smithay::{
|
||||
backend::{egl::EGLContext, renderer::gles2::Gles2Renderer},
|
||||
reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket, Resource},
|
||||
};
|
||||
use sk::StereoKitDraw;
|
||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
use smithay::backend::egl::EGLContext;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::backend::renderer::ImportDma;
|
||||
use smithay::reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket};
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
os::unix::{
|
||||
net::UnixListener,
|
||||
prelude::{FromRawFd, RawFd},
|
||||
},
|
||||
os::unix::{net::UnixListener, prelude::FromRawFd},
|
||||
sync::Arc,
|
||||
};
|
||||
use stereokit as sk;
|
||||
use stereokit::StereoKit;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tokio::{
|
||||
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
|
||||
};
|
||||
use tracing::{debug, debug_span, info, instrument};
|
||||
|
||||
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
|
||||
|
||||
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
|
||||
|
||||
struct EGLRawHandles {
|
||||
display: *const c_void,
|
||||
@@ -58,116 +62,118 @@ fn get_sk_egl() -> Result<EGLRawHandles> {
|
||||
static GLOBAL_DESTROY_QUEUE: OnceCell<mpsc::Sender<GlobalId>> = OnceCell::new();
|
||||
|
||||
pub struct Wayland {
|
||||
log: slog::Logger,
|
||||
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
pub socket_name: String,
|
||||
join_handle: JoinHandle<Result<()>>,
|
||||
renderer: Gles2Renderer,
|
||||
renderer: GlesRenderer,
|
||||
dmabuf_rx: UnboundedReceiver<Dmabuf>,
|
||||
state: Arc<Mutex<WaylandState>>,
|
||||
}
|
||||
impl Wayland {
|
||||
pub fn new(log: Logger) -> Result<Self> {
|
||||
pub fn new() -> Result<Self> {
|
||||
let egl_raw_handles = get_sk_egl()?;
|
||||
let renderer = unsafe {
|
||||
Gles2Renderer::new(
|
||||
EGLContext::from_raw(
|
||||
egl_raw_handles.display,
|
||||
egl_raw_handles.config,
|
||||
egl_raw_handles.context,
|
||||
log.clone(),
|
||||
)?,
|
||||
log.clone(),
|
||||
)?
|
||||
GlesRenderer::new(EGLContext::from_raw(
|
||||
egl_raw_handles.display,
|
||||
egl_raw_handles.config,
|
||||
egl_raw_handles.context,
|
||||
)?)?
|
||||
};
|
||||
|
||||
let display: Display<WaylandState> = Display::new()?;
|
||||
let display_handle = display.handle();
|
||||
|
||||
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
|
||||
let display = Arc::new(Mutex::new(display));
|
||||
let state = WaylandState::new(log.clone(), display.clone(), display_handle);
|
||||
let state = WaylandState::new(display.clone(), display_handle, &renderer, dmabuf_tx);
|
||||
|
||||
let (global_destroy_queue_in, global_destroy_queue) = mpsc::channel(8);
|
||||
GLOBAL_DESTROY_QUEUE.set(global_destroy_queue_in).unwrap();
|
||||
|
||||
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
|
||||
let socket_name = socket.socket_name().unwrap().to_str().unwrap().to_string();
|
||||
WAYLAND_DISPLAY
|
||||
.set(socket_name.clone())
|
||||
.expect("seriously message nova this time they screwed up big time");
|
||||
info!(socket_name, "Wayland active");
|
||||
|
||||
let join_handle =
|
||||
Wayland::start_loop(display.clone(), state.clone(), global_destroy_queue)?;
|
||||
Wayland::start_loop(display.clone(), socket, state.clone(), global_destroy_queue)?;
|
||||
|
||||
Ok(Wayland {
|
||||
log,
|
||||
display,
|
||||
socket_name,
|
||||
join_handle,
|
||||
renderer,
|
||||
dmabuf_rx,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
fn start_loop(
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
socket: ListeningSocket,
|
||||
state: Arc<Mutex<WaylandState>>,
|
||||
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
|
||||
) -> Result<JoinHandle<Result<()>>> {
|
||||
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
|
||||
if let Some(socket_name) = socket.socket_name() {
|
||||
println!("Wayland compositor {:?} active", socket_name);
|
||||
}
|
||||
|
||||
let listen_async =
|
||||
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
|
||||
|
||||
let dispatch_poll_fd: RawFd = display.lock().backend().poll_fd();
|
||||
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?;
|
||||
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
|
||||
|
||||
let dh1 = display.lock().handle();
|
||||
let mut dh2 = dh1.clone();
|
||||
|
||||
Ok(tokio::task::spawn(async move {
|
||||
Ok(task::new(|| "wayland loop", async move {
|
||||
let _socket = socket; // Keep the socket alive
|
||||
loop {
|
||||
tokio::select! {
|
||||
e = global_destroy_queue.recv() => { // New global to destroy
|
||||
debug!(?e, "destroy global");
|
||||
dh1.remove_global::<WaylandState>(e.unwrap());
|
||||
}
|
||||
acc = listen_async.accept() => { // New client connected
|
||||
let (stream, _) = acc?;
|
||||
let client = dh2.insert_client(stream.into_std()?, Arc::new(ClientState))?;
|
||||
let client = dh2.insert_client(stream.into_std()?, Arc::new(ClientState::default()))?;
|
||||
|
||||
state.lock().new_client(client.id(), &dh2);
|
||||
}
|
||||
e = dispatch_poll_listener.readable() => { // Dispatch
|
||||
let mut guard = e?;
|
||||
let mut display = display.lock();
|
||||
display.dispatch_clients(&mut *state.lock())?;
|
||||
display.flush_clients()?;
|
||||
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
|
||||
let mut display = display.lock();
|
||||
display.dispatch_clients(&mut *state.lock())?;
|
||||
display.flush_clients()?;
|
||||
Ok(())
|
||||
})?;
|
||||
guard.clear_ready();
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
})?)
|
||||
}
|
||||
|
||||
pub fn frame(&mut self, sk: &StereoKit) {
|
||||
let time_ms = (sk.time_getf() * 1000.) as u32;
|
||||
|
||||
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
|
||||
pub fn update(&mut self, sk: &impl StereoKitDraw) {
|
||||
while let Ok(dmabuf) = self.dmabuf_rx.try_recv() {
|
||||
let _ = self.renderer.import_dmabuf(&dmabuf, None);
|
||||
}
|
||||
for core_surface in CORE_SURFACES.get_valid_contents() {
|
||||
let client_id = core_surface.wl_surface().client_id().unwrap();
|
||||
let seat_data = self.state.lock().seats.get(&client_id).unwrap().clone();
|
||||
core_surface.process(
|
||||
sk,
|
||||
&mut self.renderer,
|
||||
time_ms,
|
||||
&self.log,
|
||||
|data| {
|
||||
PanelItem::on_mapped(&core_surface, data, seat_data);
|
||||
},
|
||||
|data| {
|
||||
PanelItem::if_mapped(&core_surface, data);
|
||||
},
|
||||
);
|
||||
core_surface.process(sk, &mut self.renderer);
|
||||
}
|
||||
|
||||
self.display.lock().flush_clients().unwrap();
|
||||
}
|
||||
|
||||
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
|
||||
let state = self.state.lock();
|
||||
|
||||
for core_surface in CORE_SURFACES.get_valid_contents() {
|
||||
core_surface.frame(sk, state.output.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_context_current(&self) {
|
||||
unsafe {
|
||||
self.renderer.egl_context().make_current().unwrap();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,57 +1,63 @@
|
||||
use super::{state::WaylandState, surface::CoreSurface, GLOBAL_DESTROY_QUEUE};
|
||||
use crate::nodes::items::Item;
|
||||
use anyhow::Result;
|
||||
use super::{
|
||||
panel_item::PanelItem, state::WaylandState, surface::CoreSurface, GLOBAL_DESTROY_QUEUE,
|
||||
SERIAL_COUNTER,
|
||||
};
|
||||
use crate::core::task;
|
||||
use color_eyre::eyre::Result;
|
||||
use mint::Vector2;
|
||||
use nanoid::nanoid;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rand::{seq::IteratorRandom, thread_rng};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smithay::{
|
||||
input::keyboard::{KeymapFile, ModifiersState},
|
||||
reexports::wayland_server::{
|
||||
backend::{ClientId, GlobalId},
|
||||
delegate_dispatch, delegate_global_dispatch,
|
||||
backend::{ClientId, GlobalId, ObjectId},
|
||||
protocol::{
|
||||
wl_keyboard::{self, KeyState, WlKeyboard},
|
||||
wl_pointer::{self, WlPointer},
|
||||
wl_pointer::{self, Axis, ButtonState, WlPointer},
|
||||
wl_seat::{self, Capability, WlSeat, EVT_NAME_SINCE},
|
||||
wl_surface::WlSurface,
|
||||
wl_touch::{self, WlTouch},
|
||||
},
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak as WlWeak,
|
||||
},
|
||||
wayland::compositor,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Deref, sync::Weak};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
use xkbcommon::xkb::{self, Keymap};
|
||||
|
||||
pub struct Cursor {
|
||||
pub core_surface: Weak<CoreSurface>,
|
||||
pub hotspot: Vector2<i32>,
|
||||
}
|
||||
|
||||
pub struct KeyboardInfo {
|
||||
pub keymap: KeymapFile,
|
||||
pub state: xkb::State,
|
||||
pub mods: ModifiersState,
|
||||
keymap: KeymapFile,
|
||||
state: xkb::State,
|
||||
mods: ModifiersState,
|
||||
keys: FxHashSet<u32>,
|
||||
}
|
||||
impl KeyboardInfo {
|
||||
pub fn new(keymap: &Keymap) -> Self {
|
||||
KeyboardInfo {
|
||||
state: xkb::State::new(keymap),
|
||||
keymap: KeymapFile::new(keymap, None),
|
||||
keymap: KeymapFile::new(keymap),
|
||||
mods: ModifiersState::default(),
|
||||
keys: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<()> {
|
||||
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<usize> {
|
||||
let wl_key_state = match state {
|
||||
0 => KeyState::Released,
|
||||
1 => KeyState::Pressed,
|
||||
_ => anyhow::bail!("Invalid key state!"),
|
||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
||||
};
|
||||
let xkb_key_state = match state {
|
||||
0 => xkb::KeyDirection::Up,
|
||||
1 => xkb::KeyDirection::Down,
|
||||
_ => anyhow::bail!("Invalid key state!"),
|
||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
||||
};
|
||||
let state_components = self.state.update_key(key + 8, xkb_key_state);
|
||||
if state_components != 0 {
|
||||
@@ -64,30 +70,182 @@ impl KeyboardInfo {
|
||||
0,
|
||||
);
|
||||
}
|
||||
keyboard.key(0, 0, key, wl_key_state);
|
||||
Ok(())
|
||||
keyboard.key(SERIAL_COUNTER.inc(), 0, key, wl_key_state);
|
||||
match wl_key_state {
|
||||
KeyState::Pressed => {
|
||||
self.keys.insert(key);
|
||||
}
|
||||
KeyState::Released => {
|
||||
self.keys.remove(&key);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
Ok(self.keys.len())
|
||||
}
|
||||
}
|
||||
unsafe impl Send for KeyboardInfo {}
|
||||
|
||||
pub struct SeatDelegate;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion(Vector2<f64>),
|
||||
Button {
|
||||
button: u32,
|
||||
state: u32,
|
||||
},
|
||||
Scroll {
|
||||
axis_continuous: Option<Vector2<f32>>,
|
||||
axis_discrete: Option<Vector2<f32>>,
|
||||
},
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum KeyboardEvent {
|
||||
Keymap,
|
||||
Key { key: u32, state: u32 },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SeatData(Arc<SeatDataInner>);
|
||||
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
struct SurfaceInfo {
|
||||
wl_surface: WlWeak<WlSurface>,
|
||||
panel_item: Weak<PanelItem>,
|
||||
pointer_queue: VecDeque<PointerEvent>,
|
||||
pointer_latest_event: Instant,
|
||||
keyboard_queue: VecDeque<KeyboardEvent>,
|
||||
keyboard_info: Option<KeyboardInfo>,
|
||||
}
|
||||
impl SurfaceInfo {
|
||||
fn new(wl_surface: &WlSurface, panel_item: Weak<PanelItem>) -> Self {
|
||||
SurfaceInfo {
|
||||
wl_surface: wl_surface.downgrade(),
|
||||
panel_item,
|
||||
pointer_queue: VecDeque::new(),
|
||||
pointer_latest_event: Instant::now(),
|
||||
keyboard_queue: VecDeque::new(),
|
||||
keyboard_info: None,
|
||||
}
|
||||
}
|
||||
fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool {
|
||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
||||
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
|
||||
let Some(focus_size) = core_surface.size() else { return false; };
|
||||
|
||||
if !self.pointer_queue.is_empty() {
|
||||
self.pointer_latest_event = Instant::now();
|
||||
}
|
||||
while let Some(event) = self.pointer_queue.pop_front() {
|
||||
match (locked, event) {
|
||||
(false, PointerEvent::Motion(pos)) => {
|
||||
pointer.enter(
|
||||
SERIAL_COUNTER.inc(),
|
||||
&focus,
|
||||
pos.x.clamp(0.0, focus_size.x as f64),
|
||||
pos.y.clamp(0.0, focus_size.y as f64),
|
||||
);
|
||||
locked = true;
|
||||
}
|
||||
(true, PointerEvent::Motion(pos)) => {
|
||||
pointer.motion(
|
||||
0,
|
||||
pos.x.clamp(0.0, focus_size.x as f64),
|
||||
pos.y.clamp(0.0, focus_size.y as f64),
|
||||
);
|
||||
pointer.frame();
|
||||
}
|
||||
(true, PointerEvent::Button { button, state }) => {
|
||||
pointer.button(
|
||||
0,
|
||||
0,
|
||||
button,
|
||||
match state {
|
||||
0 => ButtonState::Released,
|
||||
1 => ButtonState::Pressed,
|
||||
_ => continue,
|
||||
},
|
||||
);
|
||||
pointer.frame();
|
||||
}
|
||||
(
|
||||
true,
|
||||
PointerEvent::Scroll {
|
||||
axis_continuous,
|
||||
axis_discrete,
|
||||
},
|
||||
) => {
|
||||
if let Some(axis_continuous) = axis_continuous {
|
||||
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
|
||||
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64);
|
||||
}
|
||||
if let Some(axis_discrete) = axis_discrete {
|
||||
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
|
||||
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32);
|
||||
}
|
||||
if axis_discrete.is_none() && axis_continuous.is_none() {
|
||||
pointer.axis_stop(0, Axis::HorizontalScroll);
|
||||
pointer.axis_stop(0, Axis::VerticalScroll);
|
||||
}
|
||||
pointer.frame();
|
||||
}
|
||||
(locked, event) => {
|
||||
warn!(locked, ?event, "Invalid pointer event!");
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.pointer_latest_event.elapsed() > POINTER_EVENT_TIMEOUT {
|
||||
pointer.leave(SERIAL_COUNTER.inc(), &focus);
|
||||
locked = false;
|
||||
}
|
||||
|
||||
locked
|
||||
}
|
||||
fn handle_keyboard_events(&mut self, keyboard: &WlKeyboard, mut locked: bool) -> bool {
|
||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
||||
let Some(info) = self.keyboard_info.as_mut() else { return true; };
|
||||
|
||||
if !locked {
|
||||
keyboard.enter(0, &focus, vec![]);
|
||||
keyboard.repeat_info(0, 0);
|
||||
locked = info.keymap.send(keyboard).is_ok();
|
||||
}
|
||||
while let Some(event) = self.keyboard_queue.pop_front() {
|
||||
debug!(locked, ?event, "Process keyboard event");
|
||||
match (locked, event) {
|
||||
(true, KeyboardEvent::Keymap) => {
|
||||
let _ = info.keymap.send(keyboard);
|
||||
}
|
||||
(true, KeyboardEvent::Key { key, state }) => {
|
||||
if let Ok(key_count) = info.process(key, state, keyboard) {
|
||||
if key_count == 0 {
|
||||
keyboard.leave(SERIAL_COUNTER.inc(), &focus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(locked, event) => {
|
||||
warn!(locked, ?event, "Invalid keyboard event!");
|
||||
}
|
||||
}
|
||||
}
|
||||
locked
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SeatData {
|
||||
client: ClientId,
|
||||
global_id: OnceCell<GlobalId>,
|
||||
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
|
||||
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
|
||||
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
|
||||
touch: OnceCell<WlTouch>,
|
||||
}
|
||||
impl SeatData {
|
||||
pub fn new(dh: &DisplayHandle, client: ClientId) -> Self {
|
||||
let seat_data = SeatData(Arc::new(SeatDataInner {
|
||||
pub fn new(dh: &DisplayHandle, client: ClientId) -> Arc<Self> {
|
||||
let seat_data = Arc::new(SeatData {
|
||||
client,
|
||||
global_id: OnceCell::new(),
|
||||
panel_item: OnceCell::new(),
|
||||
cursor: Mutex::new(None),
|
||||
cursor_changed: Mutex::new(false),
|
||||
surfaces: Mutex::new(FxHashMap::default()),
|
||||
pointer: OnceCell::new(),
|
||||
pointer_active: Mutex::new(false),
|
||||
keyboard: OnceCell::new(),
|
||||
keyboard_info: Mutex::new(None),
|
||||
touch: OnceCell::new(),
|
||||
}));
|
||||
});
|
||||
|
||||
seat_data
|
||||
.global_id
|
||||
@@ -96,53 +254,132 @@ impl SeatData {
|
||||
|
||||
seat_data
|
||||
}
|
||||
}
|
||||
impl Deref for SeatData {
|
||||
type Target = SeatDataInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
pub fn set_keymap(&self, keymap: &Keymap, surfaces: Vec<WlSurface>) {
|
||||
let mut panels = self.surfaces.lock();
|
||||
let Some((_, focus)) = self.keyboard.get() else {return};
|
||||
for surface in surfaces {
|
||||
let Some(surface_info) = panels.get_mut(&surface.id()) else {continue};
|
||||
surface_info
|
||||
.keyboard_info
|
||||
.replace(KeyboardInfo::new(keymap));
|
||||
|
||||
pub struct SeatDataInner {
|
||||
client: ClientId,
|
||||
pub global_id: OnceCell<GlobalId>,
|
||||
pub panel_item: OnceCell<Weak<Item>>,
|
||||
pub cursor: Mutex<Option<Arc<Mutex<Cursor>>>>,
|
||||
pub cursor_changed: Mutex<bool>,
|
||||
pointer: OnceCell<WlPointer>,
|
||||
pub pointer_active: Mutex<bool>,
|
||||
keyboard: OnceCell<WlKeyboard>,
|
||||
pub keyboard_info: Mutex<Option<KeyboardInfo>>,
|
||||
touch: OnceCell<WlTouch>,
|
||||
}
|
||||
impl SeatDataInner {
|
||||
pub fn pointer(&self) -> Option<&WlPointer> {
|
||||
self.pointer.get()
|
||||
if *focus.lock() == surface.id() {
|
||||
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn keyboard(&self) -> Option<&WlKeyboard> {
|
||||
self.keyboard.get()
|
||||
|
||||
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
||||
surface_info.pointer_queue.push_back(event);
|
||||
drop(surfaces);
|
||||
self.handle_pointer_events();
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn touch(&self) -> Option<&WlTouch> {
|
||||
self.touch.get()
|
||||
pub fn keyboard_event(&self, surface: &WlSurface, event: KeyboardEvent) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
||||
surface_info.keyboard_queue.push_back(event);
|
||||
drop(surfaces);
|
||||
self.handle_keyboard_events();
|
||||
}
|
||||
|
||||
fn handle_pointer_events(&self) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some((pointer, pointer_focus)) = self.pointer.get() else {return};
|
||||
let mut pointer_focus = pointer_focus.lock();
|
||||
|
||||
loop {
|
||||
let locked = !pointer_focus.is_null();
|
||||
// Pick a pointer to focus on if there is none
|
||||
if pointer_focus.is_null() {
|
||||
*pointer_focus = surfaces
|
||||
.iter()
|
||||
.filter(|(_k, v)| !v.pointer_queue.is_empty())
|
||||
.map(|(k, _v)| k)
|
||||
.choose(&mut thread_rng())
|
||||
.cloned()
|
||||
.unwrap_or(ObjectId::null());
|
||||
}
|
||||
if pointer_focus.is_null() {
|
||||
// If there's still none, guess we're done with pointer events for the time being
|
||||
break;
|
||||
}
|
||||
let Some(surface_info) = surfaces.get_mut(&pointer_focus) else {break};
|
||||
if surface_info.handle_pointer_events(pointer, locked) {
|
||||
// We haven't gotten to a point where we can switch the focus
|
||||
break;
|
||||
} else {
|
||||
*pointer_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_keyboard_events(&self) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some((keyboard, keyboard_focus)) = self.keyboard.get() else {return};
|
||||
let mut keyboard_focus = keyboard_focus.lock();
|
||||
loop {
|
||||
let locked = !keyboard_focus.is_null();
|
||||
// Pick a keyboard to focus on if there is none
|
||||
if keyboard_focus.is_null() {
|
||||
*keyboard_focus = surfaces
|
||||
.iter()
|
||||
.filter(|(_k, v)| v.keyboard_info.is_some())
|
||||
.filter(|(_k, v)| !v.keyboard_queue.is_empty())
|
||||
.map(|(k, _v)| k)
|
||||
.choose(&mut thread_rng())
|
||||
.cloned()
|
||||
.unwrap_or(ObjectId::null());
|
||||
}
|
||||
// If there's still none, guess we're done with keyboard events for the time being
|
||||
let Some(surface_info) = surfaces.get_mut(&keyboard_focus) else {break};
|
||||
if surface_info.handle_keyboard_events(keyboard, locked) {
|
||||
// We haven't gotten to a point where we can switch the focus
|
||||
break;
|
||||
} else {
|
||||
*keyboard_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_surface(&self, surface: &WlSurface, panel_item: Weak<PanelItem>) {
|
||||
self.surfaces
|
||||
.lock()
|
||||
.insert(surface.id(), SurfaceInfo::new(surface, panel_item));
|
||||
}
|
||||
pub fn drop_surface(&self, surface: &WlSurface) {
|
||||
self.surfaces.lock().remove(&surface.id());
|
||||
if let Some((_, pointer_focus)) = self.pointer.get() {
|
||||
let mut pointer_focus = pointer_focus.lock();
|
||||
if *pointer_focus == surface.id() {
|
||||
*pointer_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
if let Some((_, keyboard_focus)) = self.keyboard.get() {
|
||||
let mut keyboard_focus = keyboard_focus.lock();
|
||||
if *keyboard_focus == surface.id() {
|
||||
*keyboard_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for SeatDataInner {
|
||||
impl Drop for SeatData {
|
||||
fn drop(&mut self) {
|
||||
let id = self.global_id.take().unwrap();
|
||||
tokio::spawn(async move { GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await });
|
||||
let _ = task::new(|| "global destroy queue garbage collection", async move {
|
||||
GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalDispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
|
||||
impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn bind(
|
||||
_state: &mut WaylandState,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<WlSeat>,
|
||||
data: &SeatData,
|
||||
data: &Arc<SeatData>,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
let resource = data_init.init(resource, data.clone());
|
||||
@@ -154,48 +391,50 @@ impl GlobalDispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
|
||||
resource.capabilities(Capability::Pointer | Capability::Keyboard);
|
||||
}
|
||||
|
||||
fn can_view(client: Client, data: &SeatData) -> bool {
|
||||
client.id() == data.0.client
|
||||
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
|
||||
client.id() == data.client
|
||||
}
|
||||
}
|
||||
delegate_global_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
|
||||
|
||||
impl Dispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
|
||||
impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlSeat,
|
||||
request: <WlSeat as Resource>::Request,
|
||||
data: &SeatData,
|
||||
request: wl_seat::Request,
|
||||
data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
wl_seat::Request::GetPointer { id } => {
|
||||
let _ = data.0.pointer.set(data_init.init(id, data.clone()));
|
||||
let pointer = data_init.init(id, data.clone());
|
||||
let _ = data.pointer.set((pointer, Mutex::new(ObjectId::null())));
|
||||
}
|
||||
wl_seat::Request::GetKeyboard { id } => {
|
||||
let keyboard = data_init.init(id, data.clone());
|
||||
keyboard.repeat_info(0, 0);
|
||||
let _ = data.0.keyboard.set(keyboard);
|
||||
let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null())));
|
||||
}
|
||||
wl_seat::Request::GetTouch { id } => {
|
||||
let _ = data.0.touch.set(data_init.init(id, data.clone()));
|
||||
let _ = data.touch.set(data_init.init(id, data.clone()));
|
||||
}
|
||||
wl_seat::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
|
||||
|
||||
impl Dispatch<WlPointer, SeatData, WaylandState> for SeatDelegate {
|
||||
pub struct Cursor {
|
||||
pub hotspot: Vector2<i32>,
|
||||
}
|
||||
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlPointer,
|
||||
request: <WlPointer as Resource>::Request,
|
||||
seat_data: &SeatData,
|
||||
request: wl_pointer::Request,
|
||||
seat_data: &Arc<SeatData>,
|
||||
dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
@@ -206,54 +445,43 @@ impl Dispatch<WlPointer, SeatData, WaylandState> for SeatDelegate {
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => {
|
||||
if !*seat_data.pointer_active.lock() {
|
||||
return;
|
||||
}
|
||||
*seat_data.0.cursor_changed.lock() = true;
|
||||
if let Some(surface) = surface.as_ref() {
|
||||
CoreSurface::add_to(&state.display, dh.clone(), surface, || (), |_| ());
|
||||
compositor::with_states(surface, |data| {
|
||||
data.data_map.insert_if_missing_threadsafe(|| {
|
||||
CoreSurface::new(
|
||||
&state.weak_ref.upgrade().unwrap(),
|
||||
&state.display,
|
||||
dh.clone(),
|
||||
surface,
|
||||
)
|
||||
});
|
||||
if !data.data_map.insert_if_missing_threadsafe(|| {
|
||||
Arc::new(Mutex::new(Cursor {
|
||||
core_surface: Arc::downgrade(
|
||||
data.data_map.get::<Arc<CoreSurface>>().unwrap(),
|
||||
),
|
||||
hotspot: Vector2::from([hotspot_x, hotspot_y]),
|
||||
}))
|
||||
}) {
|
||||
let mut cursor =
|
||||
data.data_map.get::<Arc<Mutex<Cursor>>>().unwrap().lock();
|
||||
cursor.hotspot = Vector2::from([hotspot_x, hotspot_y]);
|
||||
});
|
||||
let mut cursor = data.data_map.get::<Arc<Mutex<Cursor>>>().unwrap().lock();
|
||||
cursor.hotspot = Vector2::from([hotspot_x, hotspot_y]);
|
||||
|
||||
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
||||
core_surface.set_material_offset(1);
|
||||
}
|
||||
})
|
||||
}
|
||||
*seat_data.cursor.lock() = surface.and_then(|surf| {
|
||||
compositor::with_states(&surf, |data| {
|
||||
data.data_map.get::<Arc<Mutex<Cursor>>>().cloned()
|
||||
})
|
||||
});
|
||||
|
||||
let Some((_, focus)) = seat_data.pointer.get() else {return};
|
||||
let focus = focus.lock();
|
||||
let surfaces = seat_data.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get(&focus) else {return};
|
||||
let Some(panel_item) = surface_info.panel_item.upgrade() else {return};
|
||||
panel_item.set_cursor(surface.as_ref(), hotspot_x, hotspot_y);
|
||||
}
|
||||
wl_pointer::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_dispatch!(WaylandState: [WlPointer: SeatData] => SeatDelegate);
|
||||
|
||||
impl Dispatch<WlKeyboard, SeatData, WaylandState> for SeatDelegate {
|
||||
impl Dispatch<WlKeyboard, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlKeyboard,
|
||||
request: <WlKeyboard as Resource>::Request,
|
||||
_data: &SeatData,
|
||||
_data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
@@ -263,15 +491,14 @@ impl Dispatch<WlKeyboard, SeatData, WaylandState> for SeatDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_dispatch!(WaylandState: [WlKeyboard: SeatData] => SeatDelegate);
|
||||
|
||||
impl Dispatch<WlTouch, SeatData, WaylandState> for SeatDelegate {
|
||||
impl Dispatch<WlTouch, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlTouch,
|
||||
request: <WlTouch as Resource>::Request,
|
||||
_data: &SeatData,
|
||||
_data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
@@ -281,4 +508,3 @@ impl Dispatch<WlTouch, SeatData, WaylandState> for SeatDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_dispatch!(WaylandState: [WlTouch: SeatData] => SeatDelegate);
|
||||
|
||||
16
src/wayland/shaders/gamma.frag
Normal file
16
src/wayland/shaders/gamma.frag
Normal file
@@ -0,0 +1,16 @@
|
||||
#version 320 es
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
layout(binding = 0) uniform highp sampler2D diffuse;
|
||||
|
||||
layout(location = 0) in highp vec2 fs_uv;
|
||||
layout(location = 0) out highp vec4 _entryPointOutput;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 _101 = texture(diffuse, fs_uv);
|
||||
highp vec3 _104 = pow(_101.xyz, vec3(2.2000000476837158203125));
|
||||
_entryPointOutput = vec4(_104.x, _104.y, _104.z, _101.w);
|
||||
}
|
||||
|
||||
@@ -5,3 +5,9 @@
|
||||
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
|
||||
|
||||
// Simula text shader (fragment)
|
||||
// pub const SIMULA_FRAG_STR: &str = include_str!("simula.frag");
|
||||
|
||||
// Simula text shader (vertex)
|
||||
// pub const SIMULA_VERT_STR: &str = include_str!("simula.vert");
|
||||
|
||||
@@ -28,7 +28,7 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
|
||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
||||
|
||||
o.uv = (input.uv) + uv_offset * uv_scale;
|
||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
||||
return o;
|
||||
}
|
||||
float4 ps(psIn input) : SV_TARGET {
|
||||
|
||||
Binary file not shown.
@@ -4,19 +4,19 @@
|
||||
|
||||
//--name = stardust/text_shader
|
||||
//--diffuse = white
|
||||
//--fcFactor = 1.0
|
||||
//--ripple = 4.0
|
||||
//--uv_offset = 0.0, 0.0
|
||||
//--uv_scale = 1.0, 1.0
|
||||
//--fcFactor = 1.0
|
||||
//--ripple = 4.0
|
||||
//--alpha_min = 0.0
|
||||
//--alpha_max = 1.0
|
||||
Texture2D diffuse : register(t0);
|
||||
SamplerState diffuse_s : register(s0);
|
||||
float4 diffuse_i;
|
||||
float fcFactor;
|
||||
float ripple;
|
||||
float2 uv_scale;
|
||||
float2 uv_offset;
|
||||
float fcFactor;
|
||||
float ripple;
|
||||
float alpha_min;
|
||||
float alpha_max;
|
||||
|
||||
@@ -39,7 +39,7 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
|
||||
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
|
||||
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
|
||||
|
||||
o.uv = (input.uv) + uv_offset * uv_scale;
|
||||
o.uv = (input.uv + uv_offset) * uv_scale;
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
75
src/wayland/shaders/simula.frag
Normal file
75
src/wayland/shaders/simula.frag
Normal file
@@ -0,0 +1,75 @@
|
||||
#version 320 es
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
layout(binding = 0, std140) uniform _Global
|
||||
{
|
||||
highp vec4 diffuse_i;
|
||||
highp vec2 uv_scale;
|
||||
highp vec2 uv_offset;
|
||||
highp float fcFactor;
|
||||
highp float ripple;
|
||||
highp float alpha_min;
|
||||
highp float alpha_max;
|
||||
} uniforms;
|
||||
|
||||
layout(binding = 0) uniform highp samplerExternalOES diffuse;
|
||||
|
||||
layout(location = 0) in highp vec2 fs_uv;
|
||||
layout(location = 0) out highp vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec2 dx_uv = dFdx(fs_uv);
|
||||
highp vec2 dy_uv = dFdy(fs_uv);
|
||||
highp vec2 width = fs_uv * uniforms.diffuse_i.xy;
|
||||
ivec2 _475 = ivec2(width);
|
||||
highp vec2 _477 = clamp(floor(abs(vec2(dx_uv.x, dy_uv.y)) * uniforms.diffuse_i.xy), vec2(1.0), vec2(2.0));
|
||||
ivec2 _480 = ivec2(_477);
|
||||
ivec2 _481 = _475 - _480;
|
||||
ivec2 _485 = _475 + _480;
|
||||
int _487 = _481.y;
|
||||
highp vec4 _671;
|
||||
highp float _672;
|
||||
_672 = 0.0;
|
||||
_671 = vec4(0.0);
|
||||
highp vec4 _679;
|
||||
highp float _681;
|
||||
for (int _670 = _487; _670 <= _485.y; _672 = _681, _671 = _679, _670++)
|
||||
{
|
||||
int _496 = _481.x;
|
||||
_681 = _672;
|
||||
_679 = _671;
|
||||
highp vec4 _553;
|
||||
highp float _556;
|
||||
for (int _673 = _496; _673 <= _485.x; _681 = _556, _679 = _553, _673++)
|
||||
{
|
||||
highp float _509 = float(_673);
|
||||
highp float _514 = (uniforms.fcFactor * (width.x - _509)) / _477.x;
|
||||
highp float _520 = float(_670);
|
||||
highp float _525 = (uniforms.fcFactor * (width.y - _520)) / _477.y;
|
||||
highp float _533 = sqrt((_514 * _514) + (_525 * _525));
|
||||
highp float _675;
|
||||
do
|
||||
{
|
||||
if (_533 > 1.0)
|
||||
{
|
||||
_675 = 0.0;
|
||||
break;
|
||||
}
|
||||
highp float _592 = pow(uniforms.ripple * sqrt(1.0 - (_533 * _533)), 2.0);
|
||||
_675 = 1.0 + (_592 * (0.25 + (_592 * (0.015625 + (_592 * (0.00043402801384218037128448486328125 + (_592 * (6.7816799855791032314300537109375e-06 + (_592 * (6.7816799287356843706220388412476e-08 + (_592 * (4.709500012189948847662890329957e-10 + (_592 * (2.4028099388645474121517509047408e-12 + (_592 * (9.3859703944590075486154034933861e-15 + (_592 * (2.8968999943407451927966655969016e-17 + (7.242260299760125752555485045131e-20 * _592)))))))))))))))))));
|
||||
break;
|
||||
} while(false);
|
||||
_553 = _679 + (texture2D(diffuse, (vec2(_509, _520) + vec2(0.5)) / uniforms.diffuse_i.xy) * _675);
|
||||
_556 = _681 + _675;
|
||||
}
|
||||
}
|
||||
highp vec4 _568 = _671 / vec4(_672);
|
||||
highp vec3 _417 = pow(_568.xyz, vec3(2.2000000476837158203125));
|
||||
highp vec4 _669 = vec4(_417.x, _417.y, _417.z, _568.w);
|
||||
_669.w = uniforms.alpha_min + (_568.w * (uniforms.alpha_max - uniforms.alpha_min));
|
||||
fragColor = _669;
|
||||
}
|
||||
|
||||
61
src/wayland/shaders/simula.vert
Normal file
61
src/wayland/shaders/simula.vert
Normal file
@@ -0,0 +1,61 @@
|
||||
#version 320 es
|
||||
// #ifdef GL_AMD_vertex_shader_layer
|
||||
// #extension GL_AMD_vertex_shader_layer : enable
|
||||
// #elif defined(GL_NV_viewport_array2)
|
||||
// #extension GL_NV_viewport_array2 : enable
|
||||
// #else
|
||||
// #define gl_Layer int _dummy_gl_layer_var
|
||||
// #endif
|
||||
|
||||
struct Inst
|
||||
{
|
||||
mat4 world;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
layout(binding = 1, std140) uniform StereoKitBuffer
|
||||
{
|
||||
layout(row_major) mat4 sk_view[2];
|
||||
layout(row_major) mat4 sk_proj[2];
|
||||
layout(row_major) mat4 sk_proj_inv[2];
|
||||
layout(row_major) mat4 sk_viewproj[2];
|
||||
vec4 sk_lighting_sh[9];
|
||||
vec4 sk_camera_pos[2];
|
||||
vec4 sk_camera_dir[2];
|
||||
vec4 sk_fingertip[2];
|
||||
vec4 sk_cubemap_i;
|
||||
float sk_time;
|
||||
uint sk_view_count;
|
||||
} _38;
|
||||
|
||||
layout(binding = 2, std140) uniform TransformBuffer
|
||||
{
|
||||
layout(row_major) Inst sk_inst[819];
|
||||
} _56;
|
||||
|
||||
layout(binding = 0, std140) uniform _Global
|
||||
{
|
||||
vec4 diffuse_i;
|
||||
vec2 uv_scale;
|
||||
vec2 uv_offset;
|
||||
float fcFactor;
|
||||
float ripple;
|
||||
float alpha_min;
|
||||
float alpha_max;
|
||||
} _91;
|
||||
|
||||
layout(location = 0) in vec4 input_pos;
|
||||
layout(location = 1) in vec3 input_norm;
|
||||
layout(location = 2) in vec2 input_uv;
|
||||
layout(location = 0) out vec2 fs_uv;
|
||||
|
||||
mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; }
|
||||
|
||||
void main()
|
||||
{
|
||||
uint _155 = uint(gl_InstanceID) % _38.sk_view_count;
|
||||
gl_Position = spvWorkaroundRowMajor(_38.sk_viewproj[_155]) * vec4((spvWorkaroundRowMajor(_56.sk_inst[uint(gl_InstanceID) / _38.sk_view_count].world) * vec4(input_pos.xyz, 1.0)).xyz, 1.0);
|
||||
fs_uv = (input_uv + _91.uv_offset) * _91.uv_scale;
|
||||
// gl_Layer = int(_155);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,53 @@
|
||||
use crate::wayland::seat::SeatData;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use slog::Logger;
|
||||
use smithay::{
|
||||
delegate_output, delegate_shm,
|
||||
output::{Output, Scale, Subpixel},
|
||||
backend::{
|
||||
allocator::dmabuf::Dmabuf,
|
||||
egl::EGLDevice,
|
||||
renderer::{gles::GlesRenderer, ImportDma},
|
||||
},
|
||||
delegate_dmabuf, delegate_output, delegate_shm,
|
||||
output::{Mode, Output, Scale, Subpixel},
|
||||
reexports::{
|
||||
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode,
|
||||
wayland_protocols::xdg::{
|
||||
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
|
||||
shell::server::xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_data_device_manager::WlDataDeviceManager,
|
||||
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
|
||||
Display, DisplayHandle,
|
||||
},
|
||||
},
|
||||
utils::Size,
|
||||
utils::{Size, Transform},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::CompositorState,
|
||||
output::OutputManagerState,
|
||||
shell::{
|
||||
kde::decoration::KdeDecorationState,
|
||||
xdg::{decoration::XdgDecorationState, XdgShellState},
|
||||
compositor::{CompositorClientState, CompositorState},
|
||||
dmabuf::{
|
||||
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
|
||||
ImportError,
|
||||
},
|
||||
shell::kde::decoration::KdeDecorationState,
|
||||
shm::{ShmHandler, ShmState},
|
||||
xdg_activation::XdgActivationState,
|
||||
},
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub struct ClientState;
|
||||
#[derive(Default)]
|
||||
pub struct ClientState {
|
||||
pub compositor_state: CompositorClientState,
|
||||
}
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, client_id: ClientId) {
|
||||
println!("Wayland client {:?} connected", client_id);
|
||||
info!("Wayland client {:?} connected", client_id);
|
||||
}
|
||||
|
||||
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
|
||||
println!(
|
||||
info!(
|
||||
"Wayland client {:?} disconnected because {:#?}",
|
||||
client_id, reason
|
||||
);
|
||||
@@ -48,30 +60,65 @@ pub struct WaylandState {
|
||||
pub display_handle: DisplayHandle,
|
||||
|
||||
pub compositor_state: CompositorState,
|
||||
pub xdg_activation_state: XdgActivationState,
|
||||
pub xdg_decoration_state: XdgDecorationState,
|
||||
// pub xdg_activation_state: XdgActivationState,
|
||||
pub kde_decoration_state: KdeDecorationState,
|
||||
pub xdg_shell_state: XdgShellState,
|
||||
pub shm_state: ShmState,
|
||||
pub output_manager_state: OutputManagerState,
|
||||
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
||||
pub output: Output,
|
||||
pub seats: FxHashMap<ClientId, SeatData>,
|
||||
pub seats: FxHashMap<ClientId, Arc<SeatData>>,
|
||||
}
|
||||
|
||||
impl WaylandState {
|
||||
pub fn new(
|
||||
log: Logger,
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
display_handle: DisplayHandle,
|
||||
renderer: &GlesRenderer,
|
||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let compositor_state = CompositorState::new::<Self, _>(&display_handle, log.clone());
|
||||
let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle, log.clone());
|
||||
let xdg_shell_state = XdgShellState::new::<Self, _>(&display_handle, log.clone());
|
||||
let xdg_decoration_state = XdgDecorationState::new::<Self, _>(&display_handle, log.clone());
|
||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
|
||||
let kde_decoration_state =
|
||||
KdeDecorationState::new::<Self, _>(&display_handle, Mode::Server, log.clone());
|
||||
let shm_state = ShmState::new::<Self, _>(&display_handle, vec![], log.clone());
|
||||
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
|
||||
KdeDecorationState::new::<Self>(&display_handle, DecorationMode::Server);
|
||||
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
|
||||
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
|
||||
.and_then(|device| device.try_get_render_node());
|
||||
|
||||
let dmabuf_default_feedback = match render_node {
|
||||
Ok(Some(node)) => {
|
||||
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
|
||||
let dmabuf_default_feedback =
|
||||
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
||||
.build()
|
||||
.unwrap();
|
||||
Some(dmabuf_default_feedback)
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("failed to query render node, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(?err, "failed to egl device for display, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
};
|
||||
// if we failed to build dmabuf feedback we fall back to dmabuf v3
|
||||
// Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display)
|
||||
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<WaylandState>(
|
||||
&display_handle,
|
||||
&default_feedback,
|
||||
);
|
||||
(dmabuf_state, dmabuf_global, Some(default_feedback))
|
||||
} else {
|
||||
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global =
|
||||
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats);
|
||||
(dmabuf_state, dmabuf_global, None)
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
"1x".to_owned(),
|
||||
smithay::output::PhysicalProperties {
|
||||
@@ -80,13 +127,24 @@ impl WaylandState {
|
||||
make: "Virtual XR Display".to_owned(),
|
||||
model: "Your Headset Name Here".to_owned(),
|
||||
},
|
||||
log.clone(),
|
||||
);
|
||||
let _global = output.create_global::<Self>(&display_handle);
|
||||
output.change_current_state(None, None, Some(Scale::Integer(2)), None);
|
||||
let _output_global = output.create_global::<Self>(&display_handle);
|
||||
let mode = Mode {
|
||||
size: (4096, 4096).into(),
|
||||
refresh: 60000,
|
||||
};
|
||||
output.change_current_state(
|
||||
Some(mode),
|
||||
Some(Transform::Normal),
|
||||
Some(Scale::Integer(2)),
|
||||
None,
|
||||
);
|
||||
output.set_preferred(mode);
|
||||
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
|
||||
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
|
||||
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
|
||||
|
||||
println!("Init Wayland compositor");
|
||||
info!("Init Wayland compositor");
|
||||
|
||||
Arc::new_cyclic(|weak| {
|
||||
Mutex::new(WaylandState {
|
||||
@@ -95,12 +153,11 @@ impl WaylandState {
|
||||
display_handle,
|
||||
|
||||
compositor_state,
|
||||
xdg_activation_state,
|
||||
xdg_decoration_state,
|
||||
// xdg_activation_state,
|
||||
kde_decoration_state,
|
||||
xdg_shell_state,
|
||||
shm_state,
|
||||
output_manager_state,
|
||||
dmabuf_state,
|
||||
dmabuf_tx,
|
||||
output,
|
||||
seats: FxHashMap::default(),
|
||||
})
|
||||
@@ -114,20 +171,30 @@ impl WaylandState {
|
||||
}
|
||||
impl Drop for WaylandState {
|
||||
fn drop(&mut self) {
|
||||
println!("Cleanly shut down the Wayland compositor");
|
||||
info!("Cleanly shut down the Wayland compositor");
|
||||
}
|
||||
}
|
||||
impl BufferHandler for WaylandState {
|
||||
fn buffer_destroyed(
|
||||
&mut self,
|
||||
_buffer: &smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer,
|
||||
) {
|
||||
}
|
||||
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
|
||||
}
|
||||
impl ShmHandler for WaylandState {
|
||||
fn shm_state(&self) -> &smithay::wayland::shm::ShmState {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
impl DmabufHandler for WaylandState {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.dmabuf_state.0
|
||||
}
|
||||
|
||||
fn dmabuf_imported(
|
||||
&mut self,
|
||||
_global: &DmabufGlobal,
|
||||
dmabuf: Dmabuf,
|
||||
) -> Result<(), dmabuf::ImportError> {
|
||||
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed)
|
||||
}
|
||||
}
|
||||
delegate_dmabuf!(WaylandState);
|
||||
delegate_shm!(WaylandState);
|
||||
delegate_output!(WaylandState);
|
||||
|
||||
@@ -1,137 +1,125 @@
|
||||
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
|
||||
use crate::{
|
||||
core::{destroy_queue, registry::Registry},
|
||||
nodes::drawable::model::Model,
|
||||
core::{delta::Delta, destroy_queue, registry::Registry},
|
||||
nodes::drawable::model::ModelPart,
|
||||
};
|
||||
use mint::Vector2;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use send_wrapper::SendWrapper;
|
||||
use slog::Logger;
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
gles2::{Gles2Renderer, Gles2Texture},
|
||||
gles::{GlesRenderer, GlesTexture},
|
||||
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
|
||||
Texture,
|
||||
Renderer, Texture,
|
||||
},
|
||||
desktop::utils::send_frames_surface_tree,
|
||||
output::Output,
|
||||
reexports::wayland_server::{
|
||||
backend::ObjectId, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
|
||||
self, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
|
||||
},
|
||||
wayland::compositor::{self, SurfaceData},
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use stereokit::{
|
||||
material::{Material, Transparency},
|
||||
shader::Shader,
|
||||
texture::{Texture as SKTexture, TextureAddress, TextureFormat, TextureSample, TextureType},
|
||||
StereoKit,
|
||||
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
|
||||
Transparency,
|
||||
};
|
||||
|
||||
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
|
||||
|
||||
pub struct CoreSurfaceData {
|
||||
wl_tex: Option<SendWrapper<Gles2Texture>>,
|
||||
sk_tex: Option<SendWrapper<SKTexture>>,
|
||||
sk_mat: Option<Arc<SendWrapper<Material>>>,
|
||||
wl_tex: Option<SendWrapper<GlesTexture>>,
|
||||
pub size: Vector2<u32>,
|
||||
}
|
||||
impl CoreSurfaceData {
|
||||
fn new(sk: &StereoKit) -> Self {
|
||||
let sk_tex = SendWrapper::new(
|
||||
SKTexture::create(sk, TextureType::ImageNoMips, TextureFormat::RGBA32).unwrap(),
|
||||
);
|
||||
let sk_mat = {
|
||||
let shader = Shader::from_mem(sk, PANEL_SHADER_BYTES).unwrap();
|
||||
let mat = Material::create(sk, &shader).unwrap();
|
||||
mat.set_parameter("diffuse", &*sk_tex);
|
||||
mat.set_transparency(Transparency::Blend);
|
||||
Arc::new(SendWrapper::new(mat))
|
||||
};
|
||||
CoreSurfaceData {
|
||||
wl_tex: None,
|
||||
sk_tex: Some(sk_tex),
|
||||
sk_mat: Some(sk_mat),
|
||||
size: Vector2::from([0, 0]),
|
||||
}
|
||||
}
|
||||
fn update_tex(&mut self, data: &RendererSurfaceStateUserData, renderer: &Gles2Renderer) {
|
||||
if let Some(surface_size) = data.borrow().surface_size() {
|
||||
self.size = Vector2::from([surface_size.w as u32, surface_size.h as u32]);
|
||||
}
|
||||
self.wl_tex = data
|
||||
.borrow()
|
||||
.texture(renderer)
|
||||
.cloned()
|
||||
.map(SendWrapper::new);
|
||||
if let Some(smithay_tex) = self.wl_tex.as_ref() {
|
||||
let sk_tex = self.sk_tex.as_ref().unwrap();
|
||||
unsafe {
|
||||
sk_tex.set_native(
|
||||
smithay_tex.tex_id() as usize,
|
||||
smithay::backend::renderer::gles2::ffi::RGBA8.into(),
|
||||
TextureType::Image,
|
||||
smithay_tex.width(),
|
||||
smithay_tex.height(),
|
||||
false,
|
||||
);
|
||||
sk_tex.set_sample(TextureSample::Point);
|
||||
sk_tex.set_address_mode(TextureAddress::Clamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for CoreSurfaceData {
|
||||
fn drop(&mut self) {
|
||||
destroy_queue::add(self.wl_tex.take());
|
||||
destroy_queue::add(self.sk_tex.take());
|
||||
destroy_queue::add(self.sk_mat.take());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CoreSurface {
|
||||
display: Weak<Mutex<Display<WaylandState>>>,
|
||||
pub state: Weak<Mutex<WaylandState>>,
|
||||
pub dh: DisplayHandle,
|
||||
pub surface_id: ObjectId,
|
||||
pub mapped_data: Mutex<Option<CoreSurfaceData>>,
|
||||
pub pending_material_applications: Mutex<Vec<(Arc<Model>, u32)>>,
|
||||
pub weak_surface: wayland_server::Weak<WlSurface>,
|
||||
mapped_data: Mutex<Option<CoreSurfaceData>>,
|
||||
sk_tex: OnceCell<SendWrapper<Tex>>,
|
||||
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
|
||||
material_offset: Mutex<Delta<u32>>,
|
||||
on_mapped: Box<dyn Fn() + Send + Sync>,
|
||||
on_commit: Box<dyn Fn(u32) + Send + Sync>,
|
||||
pub pending_material_applications: Registry<ModelPart>,
|
||||
}
|
||||
|
||||
impl CoreSurface {
|
||||
pub fn new(
|
||||
state: &Arc<Mutex<WaylandState>>,
|
||||
pub fn add_to(
|
||||
display: &Arc<Mutex<Display<WaylandState>>>,
|
||||
dh: DisplayHandle,
|
||||
surface: &WlSurface,
|
||||
) -> Arc<Self> {
|
||||
CORE_SURFACES.add(CoreSurface {
|
||||
display: Arc::downgrade(display),
|
||||
state: Arc::downgrade(state),
|
||||
dh,
|
||||
surface_id: surface.id(),
|
||||
mapped_data: Mutex::new(None),
|
||||
pending_material_applications: Mutex::new(Vec::new()),
|
||||
on_mapped: impl Fn() + Send + Sync + 'static,
|
||||
on_commit: impl Fn(u32) + Send + Sync + 'static,
|
||||
) {
|
||||
compositor::with_states(surface, |data| {
|
||||
data.data_map.insert_if_missing_threadsafe(|| {
|
||||
CORE_SURFACES.add(CoreSurface {
|
||||
display: Arc::downgrade(display),
|
||||
dh,
|
||||
weak_surface: surface.downgrade(),
|
||||
mapped_data: Mutex::new(None),
|
||||
sk_tex: OnceCell::new(),
|
||||
sk_mat: OnceCell::new(),
|
||||
material_offset: Mutex::new(Delta::new(0)),
|
||||
on_mapped: Box::new(on_mapped) as Box<dyn Fn() + Send + Sync>,
|
||||
on_commit: Box::new(on_commit) as Box<dyn Fn(u32) + Send + Sync>,
|
||||
pending_material_applications: Registry::new(),
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn commit(&self, count: u32) {
|
||||
(self.on_commit)(count);
|
||||
}
|
||||
|
||||
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
|
||||
compositor::with_states(surf, |data| {
|
||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process<F: FnOnce(&SurfaceData), M: FnOnce(&SurfaceData)>(
|
||||
&self,
|
||||
sk: &StereoKit,
|
||||
renderer: &mut Gles2Renderer,
|
||||
time_ms: u32,
|
||||
log: &Logger,
|
||||
on_mapped: F,
|
||||
if_mapped: M,
|
||||
) {
|
||||
// Let Smithay handle all the buffer maintenance
|
||||
on_commit_buffer_handler(&self.wl_surface());
|
||||
// Import all surface buffers into textures
|
||||
import_surface_tree(renderer, &self.wl_surface(), log).unwrap();
|
||||
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
|
||||
let Some(wl_surface) = self.wl_surface() else { return };
|
||||
|
||||
let mapped = compositor::with_states(&self.wl_surface(), |data| {
|
||||
let sk_tex = self.sk_tex.get_or_init(|| {
|
||||
SendWrapper::new(sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32))
|
||||
});
|
||||
self.sk_mat.get_or_init(|| {
|
||||
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES).unwrap();
|
||||
// let _ = renderer.with_context(|c| unsafe {
|
||||
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
|
||||
// });
|
||||
|
||||
let mat = sk.material_create(&shader);
|
||||
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
|
||||
sk.material_set_transparency(&mat, Transparency::Blend);
|
||||
Arc::new(SendWrapper::new(mat))
|
||||
});
|
||||
|
||||
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
|
||||
on_commit_buffer_handler::<WaylandState>(&wl_surface);
|
||||
// Import all surface buffers into textures
|
||||
if import_surface_tree(renderer, &wl_surface).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mapped = compositor::with_states(&wl_surface, |data| {
|
||||
data.data_map
|
||||
.get::<RendererSurfaceStateUserData>()
|
||||
.map(|surface_states| surface_states.borrow().wl_buffer().is_some())
|
||||
.map(|surface_states| surface_states.borrow().buffer().is_some())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
@@ -141,67 +129,92 @@ impl CoreSurface {
|
||||
|
||||
let mut mapped_data = self.mapped_data.lock();
|
||||
let just_mapped = mapped_data.is_none();
|
||||
if just_mapped {
|
||||
*mapped_data = Some(CoreSurfaceData::new(sk));
|
||||
}
|
||||
drop(mapped_data);
|
||||
self.with_states(|data| {
|
||||
self.with_data(|mapped_data| {
|
||||
mapped_data.update_tex(
|
||||
data.data_map.get::<RendererSurfaceStateUserData>().unwrap(),
|
||||
renderer,
|
||||
let renderer_surface_state = data
|
||||
.data_map
|
||||
.get::<RendererSurfaceStateUserData>()
|
||||
.unwrap()
|
||||
.borrow();
|
||||
let smithay_tex = renderer_surface_state
|
||||
.texture::<GlesRenderer>(renderer.id())
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let sk_tex = self.sk_tex.get().unwrap();
|
||||
let sk_mat = self.sk_mat.get().unwrap();
|
||||
unsafe {
|
||||
sk.tex_set_surface(
|
||||
sk_tex.as_ref(),
|
||||
smithay_tex.tex_id() as usize as *mut c_void,
|
||||
TextureType::IMAGE_NO_MIPS,
|
||||
smithay::backend::renderer::gles::ffi::RGBA8.into(),
|
||||
smithay_tex.width() as i32,
|
||||
smithay_tex.height() as i32,
|
||||
1,
|
||||
false,
|
||||
);
|
||||
});
|
||||
self.apply_surface_materials();
|
||||
|
||||
if just_mapped {
|
||||
on_mapped(data);
|
||||
sk.tex_set_sample(sk_tex.as_ref(), TextureSample::Point);
|
||||
sk.tex_set_address(sk_tex.as_ref(), TextureAddress::Clamp);
|
||||
}
|
||||
if let Some(material_offset) = self.material_offset.lock().delta() {
|
||||
sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32);
|
||||
}
|
||||
if_mapped(data);
|
||||
});
|
||||
|
||||
send_frames_surface_tree(&self.wl_surface(), time_ms);
|
||||
let surface_size = renderer_surface_state.surface_size().unwrap();
|
||||
let new_mapped_data = CoreSurfaceData {
|
||||
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
|
||||
wl_tex: Some(SendWrapper::new(smithay_tex)),
|
||||
};
|
||||
*mapped_data = Some(new_mapped_data);
|
||||
});
|
||||
drop(mapped_data);
|
||||
if just_mapped {
|
||||
(self.on_mapped)();
|
||||
}
|
||||
self.apply_surface_materials();
|
||||
}
|
||||
|
||||
pub fn apply_material(&self, model: Arc<Model>, material_idx: u32) {
|
||||
self.pending_material_applications
|
||||
.lock()
|
||||
.push((model, material_idx));
|
||||
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
|
||||
let Some(wl_surface) = self.wl_surface() else { return };
|
||||
|
||||
send_frames_surface_tree(
|
||||
&wl_surface,
|
||||
&output,
|
||||
Duration::from_secs_f64(sk.time_get()),
|
||||
None,
|
||||
|_, _| Some(output.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_material_offset(&self, material_offset: u32) {
|
||||
*self.material_offset.lock().value_mut() = material_offset;
|
||||
}
|
||||
|
||||
pub fn apply_material(&self, model_node: &Arc<ModelPart>) {
|
||||
self.pending_material_applications.add_raw(model_node)
|
||||
}
|
||||
|
||||
fn apply_surface_materials(&self) {
|
||||
self.with_data(|mapped_data| {
|
||||
let mut pending_material_applications = self.pending_material_applications.lock();
|
||||
for (model, material_idx) in &*pending_material_applications {
|
||||
model
|
||||
.pending_material_replacements
|
||||
.lock()
|
||||
.insert(*material_idx, mapped_data.sk_mat.clone().unwrap());
|
||||
}
|
||||
pending_material_applications.clear();
|
||||
});
|
||||
for model_node in self.pending_material_applications.get_valid_contents() {
|
||||
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
|
||||
}
|
||||
self.pending_material_applications.clear();
|
||||
}
|
||||
|
||||
pub fn wayland_state(&self) -> Arc<Mutex<WaylandState>> {
|
||||
self.state.upgrade().unwrap()
|
||||
pub fn wl_surface(&self) -> Option<WlSurface> {
|
||||
self.weak_surface.upgrade().ok()
|
||||
}
|
||||
|
||||
pub fn wl_surface(&self) -> WlSurface {
|
||||
WlSurface::from_id(&self.dh, self.surface_id.clone()).unwrap()
|
||||
}
|
||||
|
||||
pub fn with_states<F, T>(&self, f: F) -> T
|
||||
pub fn with_states<F, T>(&self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&SurfaceData) -> T,
|
||||
{
|
||||
compositor::with_states(&self.wl_surface(), f)
|
||||
self.wl_surface()
|
||||
.map(|wl_surface| compositor::with_states(&wl_surface, f))
|
||||
}
|
||||
|
||||
pub fn with_data<F, T>(&self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut CoreSurfaceData) -> T,
|
||||
{
|
||||
self.mapped_data.lock().as_mut().map(f)
|
||||
pub fn size(&self) -> Option<Vector2<u32>> {
|
||||
self.mapped_data.lock().as_ref().map(|d| d.size)
|
||||
}
|
||||
|
||||
pub fn flush_clients(&self) {
|
||||
@@ -216,5 +229,8 @@ impl CoreSurface {
|
||||
impl Drop for CoreSurface {
|
||||
fn drop(&mut self) {
|
||||
CORE_SURFACES.remove(self);
|
||||
|
||||
destroy_queue::add(self.sk_tex.take());
|
||||
destroy_queue::add(self.sk_mat.take());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,697 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::nodes::Node;
|
||||
|
||||
use super::{panel_item::PanelItem, state::WaylandState, surface::CoreSurface};
|
||||
use smithay::{
|
||||
delegate_xdg_shell,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::{
|
||||
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
|
||||
shell::server::xdg_toplevel::State,
|
||||
},
|
||||
wayland_server::protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
|
||||
use super::{
|
||||
panel_item::{PanelItem, RecommendedState, SurfaceID},
|
||||
state::WaylandState,
|
||||
surface::CoreSurface,
|
||||
SERIAL_COUNTER,
|
||||
};
|
||||
use mint::Vector2;
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::Mutex;
|
||||
use serde::{ser::SerializeSeq, Serialize, Serializer};
|
||||
use smithay::reexports::{
|
||||
wayland_protocols::xdg::shell::server::{
|
||||
xdg_popup::{self, XdgPopup},
|
||||
xdg_positioner::{self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner},
|
||||
xdg_surface::{self, XdgSurface},
|
||||
xdg_toplevel::{self, XdgToplevel, EVT_WM_CAPABILITIES_SINCE},
|
||||
xdg_wm_base::{self, XdgWmBase},
|
||||
},
|
||||
utils::Serial,
|
||||
wayland::{
|
||||
compositor,
|
||||
shell::xdg::{
|
||||
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler,
|
||||
XdgShellState,
|
||||
},
|
||||
wayland_server::{
|
||||
backend::{ClientId, ObjectId},
|
||||
protocol::wl_surface::WlSurface,
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
|
||||
Weak as WlWeak,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
impl XdgShellHandler for WaylandState {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
impl GlobalDispatch<XdgWmBase, (), WaylandState> for WaylandState {
|
||||
fn bind(
|
||||
_state: &mut WaylandState,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<XdgWmBase>,
|
||||
_global_data: &(),
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
data_init.init(resource, ());
|
||||
}
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
self.output
|
||||
.enter(&self.display_handle, surface.wl_surface());
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(State::Maximized);
|
||||
state.states.set(State::Activated);
|
||||
state.decoration_mode = Some(Mode::ServerSide);
|
||||
});
|
||||
surface.send_configure();
|
||||
impl Dispatch<XdgWmBase, (), WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &XdgWmBase,
|
||||
request: xdg_wm_base::Request,
|
||||
_data: &(),
|
||||
_dhandle: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
xdg_wm_base::Request::CreatePositioner { id } => {
|
||||
let positioner = data_init.init(id, Mutex::new(PositionerData::default()));
|
||||
debug!(?positioner, "Create XDG positioner");
|
||||
}
|
||||
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
|
||||
let xdg_surface = data_init.init(id, Mutex::new(XdgSurfaceData::new(&surface)));
|
||||
debug!(?xdg_surface, "Create XDG surface");
|
||||
}
|
||||
xdg_wm_base::Request::Pong { serial } => {
|
||||
debug!(serial, "Client pong");
|
||||
}
|
||||
xdg_wm_base::Request::Destroy => {
|
||||
debug!("Destroy XDG WM base");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
match configure {
|
||||
Configure::Toplevel(config) => {
|
||||
if let Some(size) = config.state.size {
|
||||
compositor::with_states(&surface, |data| {
|
||||
if let Some(panel_node) = data.data_map.get::<Arc<Node>>() {
|
||||
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
||||
let panel_item = PanelItem::from_node(panel_node);
|
||||
let has_data = core_surface
|
||||
.with_data(|data| {
|
||||
data.size.x = size.w as u32;
|
||||
data.size.y = size.h as u32;
|
||||
})
|
||||
.is_some();
|
||||
if has_data {
|
||||
panel_item.resize();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Copy)]
|
||||
pub struct PositionerData {
|
||||
size: Vector2<u32>,
|
||||
anchor_rect_pos: Vector2<i32>,
|
||||
anchor_rect_size: Vector2<u32>,
|
||||
anchor: u32,
|
||||
gravity: u32,
|
||||
constraint_adjustment: u32,
|
||||
offset: Vector2<i32>,
|
||||
reactive: bool,
|
||||
}
|
||||
impl Default for PositionerData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: Vector2::from([0; 2]),
|
||||
anchor_rect_pos: Vector2::from([0; 2]),
|
||||
anchor_rect_size: Vector2::from([0; 2]),
|
||||
anchor: Anchor::None as u32,
|
||||
gravity: Gravity::None as u32,
|
||||
constraint_adjustment: ConstraintAdjustment::None.bits(),
|
||||
offset: Vector2::from([0; 2]),
|
||||
reactive: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgPositioner, Mutex<PositionerData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
positioner: &XdgPositioner,
|
||||
request: xdg_positioner::Request,
|
||||
data: &Mutex<PositionerData>,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
xdg_positioner::Request::SetSize { width, height } => {
|
||||
debug!(?positioner, width, height, "Set positioner size");
|
||||
data.lock().size = Vector2::from([width as u32, height as u32]);
|
||||
}
|
||||
xdg_positioner::Request::SetAnchorRect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if width < 1 || height < 1 {
|
||||
positioner.post_error(
|
||||
xdg_positioner::Error::InvalidInput,
|
||||
"Invalid size for positioner's anchor rectangle.",
|
||||
);
|
||||
warn!(
|
||||
?positioner,
|
||||
width, height, "Invalid size for positioner's anchor rectangle"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
debug!(
|
||||
?positioner,
|
||||
x, y, width, height, "Set positioner anchor rectangle"
|
||||
);
|
||||
let mut data = data.lock();
|
||||
data.anchor_rect_pos = [x, y].into();
|
||||
data.anchor_rect_size = [width as u32, height as u32].into();
|
||||
}
|
||||
xdg_positioner::Request::SetAnchor { anchor } => {
|
||||
if let WEnum::Value(anchor) = anchor {
|
||||
debug!(?positioner, ?anchor, "Set positioner anchor");
|
||||
data.lock().anchor = anchor as u32;
|
||||
}
|
||||
}
|
||||
Configure::Popup(_) => (),
|
||||
xdg_positioner::Request::SetGravity { gravity } => {
|
||||
if let WEnum::Value(gravity) = gravity {
|
||||
debug!(?positioner, ?gravity, "Set positioner gravity");
|
||||
data.lock().gravity = gravity as u32;
|
||||
}
|
||||
}
|
||||
xdg_positioner::Request::SetConstraintAdjustment {
|
||||
constraint_adjustment,
|
||||
} => {
|
||||
debug!(
|
||||
?positioner,
|
||||
constraint_adjustment, "Set positioner constraint adjustment"
|
||||
);
|
||||
data.lock().constraint_adjustment = constraint_adjustment;
|
||||
}
|
||||
xdg_positioner::Request::SetOffset { x, y } => {
|
||||
debug!(?positioner, x, y, "Set positioner offset");
|
||||
data.lock().offset = [x, y].into();
|
||||
}
|
||||
xdg_positioner::Request::SetReactive => {
|
||||
debug!(?positioner, "Set positioner reactive");
|
||||
data.lock().reactive = true;
|
||||
}
|
||||
xdg_positioner::Request::SetParentSize {
|
||||
parent_width,
|
||||
parent_height,
|
||||
} => {
|
||||
debug!(
|
||||
?positioner,
|
||||
parent_width, parent_height, "Set positioner parent size"
|
||||
);
|
||||
}
|
||||
xdg_positioner::Request::SetParentConfigure { serial } => {
|
||||
debug!(?positioner, serial, "Set positioner parent size");
|
||||
}
|
||||
xdg_positioner::Request::Destroy => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Copy)]
|
||||
pub struct Geometry {
|
||||
pub origin: Vector2<i32>,
|
||||
pub size: Vector2<u32>,
|
||||
}
|
||||
impl Default for Geometry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
origin: Vector2::from([0; 2]),
|
||||
size: Vector2::from([0; 2]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct XdgSurfaceData {
|
||||
wl_surface: WlWeak<WlSurface>,
|
||||
surface_id: SurfaceID,
|
||||
panel_item: Weak<PanelItem>,
|
||||
geometry: Option<Geometry>,
|
||||
}
|
||||
impl XdgSurfaceData {
|
||||
pub fn new(wl_surface: &WlSurface) -> Self {
|
||||
XdgSurfaceData {
|
||||
wl_surface: wl_surface.downgrade(),
|
||||
surface_id: SurfaceID::Toplevel,
|
||||
panel_item: Weak::new(),
|
||||
geometry: None,
|
||||
}
|
||||
}
|
||||
pub fn get(xdg_surface: &XdgSurface) -> &Mutex<Self> {
|
||||
xdg_surface.data::<Mutex<Self>>().unwrap()
|
||||
}
|
||||
pub fn wl_surface(&self) -> WlSurface {
|
||||
self.wl_surface.upgrade().unwrap()
|
||||
}
|
||||
pub fn panel_item(&self) -> Option<Arc<PanelItem>> {
|
||||
self.panel_item.upgrade()
|
||||
}
|
||||
}
|
||||
// impl Clone for XdgSurfaceData {
|
||||
// fn clone(&self) -> Self {
|
||||
// Self {
|
||||
// wl_surface: self.wl_surface.clone(),
|
||||
// geometry: self.geometry.clone(),
|
||||
// surface_type: Mutex::new(self.surface_type.lock().clone()),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl Dispatch<XdgSurface, Mutex<XdgSurfaceData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
state: &mut WaylandState,
|
||||
client: &Client,
|
||||
xdg_surface: &XdgSurface,
|
||||
request: xdg_surface::Request,
|
||||
xdg_surface_data: &Mutex<XdgSurfaceData>,
|
||||
_dhandle: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
xdg_surface::Request::GetToplevel { id } => {
|
||||
let toplevel_state = Mutex::new(ToplevelData::new(xdg_surface));
|
||||
let toplevel = data_init.init(id, toplevel_state);
|
||||
debug!(?toplevel, ?xdg_surface, "Create XDG toplevel");
|
||||
|
||||
if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE {
|
||||
toplevel.wm_capabilities(vec![2, 3, 4]);
|
||||
}
|
||||
toplevel.configure(
|
||||
0,
|
||||
0,
|
||||
if toplevel.version() >= 2 {
|
||||
vec![5, 6, 7, 8]
|
||||
.into_iter()
|
||||
.flat_map(u32::to_ne_bytes)
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
);
|
||||
xdg_surface.configure(SERIAL_COUNTER.inc());
|
||||
|
||||
let client_credentials = client.get_credentials(&state.display_handle).ok();
|
||||
let seat_data = state.seats.get(&client.id()).unwrap().clone();
|
||||
CoreSurface::add_to(
|
||||
&state.display,
|
||||
state.display_handle.clone(),
|
||||
&xdg_surface_data.lock().wl_surface(),
|
||||
{
|
||||
let toplevel = toplevel.downgrade();
|
||||
move || {
|
||||
let toplevel = toplevel.upgrade().unwrap();
|
||||
let toplevel_data = ToplevelData::get(&toplevel);
|
||||
let xdg_surface = toplevel_data.lock().xdg_surface();
|
||||
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface);
|
||||
let wl_surface = xdg_surface_data.lock().wl_surface();
|
||||
|
||||
xdg_surface_data.lock().surface_id = SurfaceID::Toplevel;
|
||||
let (node, panel_item) = PanelItem::create(
|
||||
toplevel.clone(),
|
||||
wl_surface.clone(),
|
||||
client_credentials,
|
||||
seat_data.clone(),
|
||||
);
|
||||
toplevel_data.lock().panel_item_node.replace(node);
|
||||
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
|
||||
}
|
||||
},
|
||||
{
|
||||
let toplevel = toplevel.downgrade();
|
||||
move |_| {
|
||||
let toplevel = toplevel.upgrade().unwrap();
|
||||
let toplevel_data = ToplevelData::get(&toplevel);
|
||||
let Some(panel_item) = toplevel_data.lock().panel_item() else {
|
||||
// if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates
|
||||
toplevel.configure(
|
||||
0,
|
||||
0,
|
||||
if toplevel.version() >= 2 {
|
||||
vec![5, 6, 7, 8].into_iter().flat_map(u32::to_ne_bytes).collect()
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
);
|
||||
let xdg_surface = toplevel_data.lock().xdg_surface();
|
||||
xdg_surface.configure(SERIAL_COUNTER.inc());
|
||||
return
|
||||
};
|
||||
panel_item.commit_toplevel();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
xdg_surface::Request::GetPopup {
|
||||
id,
|
||||
parent,
|
||||
positioner,
|
||||
} => {
|
||||
let parent_clone = parent.clone().unwrap();
|
||||
let parent_data = parent_clone.data::<Mutex<XdgSurfaceData>>().unwrap().lock();
|
||||
// let positioner_data = positioner
|
||||
// .data::<Mutex<PositionerData>>()
|
||||
// .unwrap()
|
||||
// .lock()
|
||||
// .clone();
|
||||
// let parent = match &*parent_data {
|
||||
// XdgSurfaceType::Toplevel(_) => SurfaceID::Toplevel,
|
||||
// XdgSurfaceType::Popup(p) => {
|
||||
// SurfaceID::Popup(p.upgrade().unwrap().uid.clone())
|
||||
// }
|
||||
// XdgSurfaceType::Unknown => return,
|
||||
// };
|
||||
let uid = nanoid!();
|
||||
let popup_data = Mutex::new(PopupData::new(
|
||||
uid.clone(),
|
||||
xdg_surface,
|
||||
parent_data.surface_id.clone(),
|
||||
positioner,
|
||||
));
|
||||
let xdg_popup = data_init.init(id, popup_data);
|
||||
xdg_surface_data.lock().surface_id = SurfaceID::Popup(uid);
|
||||
let panel_item = parent_data.panel_item().unwrap();
|
||||
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
|
||||
|
||||
panel_item.seat_data.new_surface(
|
||||
&xdg_surface_data.lock().wl_surface(),
|
||||
Arc::downgrade(&panel_item),
|
||||
);
|
||||
debug!(?xdg_popup, ?xdg_surface, "Create XDG popup");
|
||||
|
||||
let xdg_surface = xdg_surface.downgrade();
|
||||
let xdg_popup = xdg_popup.downgrade();
|
||||
CoreSurface::add_to(
|
||||
&state.display,
|
||||
state.display_handle.clone(),
|
||||
&xdg_surface_data.lock().wl_surface.upgrade().unwrap(),
|
||||
move || {
|
||||
let xdg_popup = xdg_popup.upgrade().unwrap();
|
||||
let popup_data = PopupData::get(&xdg_popup);
|
||||
let popup_data = popup_data.lock();
|
||||
// panel_item.commit_popup(popup_data);
|
||||
panel_item.new_popup(&xdg_popup, &*popup_data);
|
||||
},
|
||||
move |commit_count| {
|
||||
if commit_count == 0 {
|
||||
xdg_surface
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.configure(SERIAL_COUNTER.inc())
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
xdg_surface::Request::SetWindowGeometry {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
debug!(
|
||||
?xdg_surface,
|
||||
x, y, width, height, "Set XDG surface geometry"
|
||||
);
|
||||
let geometry = Geometry {
|
||||
origin: [x, y].into(),
|
||||
size: [width as u32, height as u32].into(),
|
||||
};
|
||||
xdg_surface_data.lock().geometry.replace(geometry);
|
||||
}
|
||||
xdg_surface::Request::AckConfigure { serial } => {
|
||||
debug!(?xdg_surface, serial, "Acknowledge XDG surface configure");
|
||||
}
|
||||
xdg_surface::Request::Destroy => {
|
||||
debug!(?xdg_surface, "Destroy XDG surface");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serde_error<S: Serializer>(msg: &str) -> Result<S::Ok, S::Error> {
|
||||
Err(serde::ser::Error::custom(msg))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToplevelData {
|
||||
panel_item_node: Option<Arc<Node>>,
|
||||
xdg_surface: WlWeak<XdgSurface>,
|
||||
parent: Option<WlWeak<XdgToplevel>>,
|
||||
title: Option<String>,
|
||||
app_id: Option<String>,
|
||||
max_size: Option<Vector2<u32>>,
|
||||
min_size: Option<Vector2<u32>>,
|
||||
states: Vec<u32>,
|
||||
}
|
||||
impl ToplevelData {
|
||||
fn new(xdg_surface: &XdgSurface) -> Self {
|
||||
ToplevelData {
|
||||
panel_item_node: None,
|
||||
xdg_surface: xdg_surface.downgrade(),
|
||||
parent: None,
|
||||
title: None,
|
||||
app_id: None,
|
||||
max_size: None,
|
||||
min_size: None,
|
||||
states: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {}
|
||||
fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}
|
||||
pub fn get(toplevel: &XdgToplevel) -> &Mutex<ToplevelData> {
|
||||
toplevel.data::<Mutex<ToplevelData>>().unwrap()
|
||||
}
|
||||
|
||||
pub fn xdg_surface(&self) -> XdgSurface {
|
||||
self.xdg_surface.upgrade().unwrap()
|
||||
}
|
||||
fn panel_item(&self) -> Option<Arc<PanelItem>> {
|
||||
let xdg_surface = self.xdg_surface();
|
||||
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
|
||||
xdg_surface_data.panel_item()
|
||||
}
|
||||
}
|
||||
impl Serialize for ToplevelData {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let xdg_surface = self.xdg_surface();
|
||||
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
|
||||
let geometry = xdg_surface_data.geometry.clone();
|
||||
let wl_surface = xdg_surface_data.wl_surface();
|
||||
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return serde_error::<S>("Core surface not found")};
|
||||
let Some(size) = core_surface.size() else {return serializer.serialize_none()};
|
||||
let geometry = geometry.unwrap_or_else(|| Geometry {
|
||||
origin: [0; 2].into(),
|
||||
size,
|
||||
});
|
||||
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
// Parent UID
|
||||
seq.serialize_element(&self.parent.as_ref().and_then(|p| {
|
||||
Some(
|
||||
ToplevelData::get(&p.upgrade().ok()?)
|
||||
.lock()
|
||||
.panel_item()?
|
||||
.uid
|
||||
.clone(),
|
||||
)
|
||||
}))?;
|
||||
seq.serialize_element(&self.title)?;
|
||||
seq.serialize_element(&self.app_id)?;
|
||||
seq.serialize_element(&size)?;
|
||||
seq.serialize_element(&self.min_size)?;
|
||||
seq.serialize_element(&self.max_size)?;
|
||||
seq.serialize_element(&geometry)?;
|
||||
seq.serialize_element(&self.states)?;
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
impl Dispatch<XdgToplevel, Mutex<ToplevelData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
xdg_toplevel: &XdgToplevel,
|
||||
request: xdg_toplevel::Request,
|
||||
data: &Mutex<ToplevelData>,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
xdg_toplevel::Request::SetParent { parent } => {
|
||||
debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent");
|
||||
data.lock().parent = parent.map(|toplevel| toplevel.downgrade());
|
||||
}
|
||||
xdg_toplevel::Request::SetTitle { title } => {
|
||||
debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title");
|
||||
data.lock().title = (!title.is_empty()).then_some(title);
|
||||
}
|
||||
xdg_toplevel::Request::SetAppId { app_id } => {
|
||||
debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID");
|
||||
data.lock().app_id = (!app_id.is_empty()).then_some(app_id);
|
||||
}
|
||||
xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => {
|
||||
debug!(
|
||||
?xdg_toplevel,
|
||||
?seat,
|
||||
serial,
|
||||
x,
|
||||
y,
|
||||
"Show XDG Toplevel window menu"
|
||||
);
|
||||
}
|
||||
xdg_toplevel::Request::Move { seat, serial } => {
|
||||
debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request");
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Move);
|
||||
}
|
||||
xdg_toplevel::Request::Resize {
|
||||
seat,
|
||||
serial,
|
||||
edges,
|
||||
} => {
|
||||
let WEnum::Value(edges) = edges else { return };
|
||||
debug!(
|
||||
?xdg_toplevel,
|
||||
?seat,
|
||||
serial,
|
||||
?edges,
|
||||
"XDG Toplevel resize request"
|
||||
);
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Resize(edges as u32));
|
||||
}
|
||||
xdg_toplevel::Request::SetMaxSize { width, height } => {
|
||||
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size");
|
||||
data.lock().max_size = (width > 1 || height > 1)
|
||||
.then_some(Vector2::from([width as u32, height as u32]));
|
||||
}
|
||||
xdg_toplevel::Request::SetMinSize { width, height } => {
|
||||
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size");
|
||||
data.lock().min_size = (width > 1 || height > 1)
|
||||
.then_some(Vector2::from([width as u32, height as u32]));
|
||||
}
|
||||
xdg_toplevel::Request::SetMaximized => {
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Maximize(true));
|
||||
}
|
||||
xdg_toplevel::Request::UnsetMaximized => {
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Maximize(false));
|
||||
}
|
||||
xdg_toplevel::Request::SetFullscreen { output: _ } => {
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
|
||||
}
|
||||
xdg_toplevel::Request::UnsetFullscreen => {
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
|
||||
}
|
||||
xdg_toplevel::Request::SetMinimized => {
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.recommend_toplevel_state(RecommendedState::Minimize);
|
||||
}
|
||||
xdg_toplevel::Request::Destroy => {
|
||||
debug!(?xdg_toplevel, "Destroy XDG Toplevel");
|
||||
let Some(panel_item) = data.lock().panel_item() else { return };
|
||||
panel_item.on_drop();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PopupData {
|
||||
pub uid: String,
|
||||
grabbed: bool,
|
||||
parent_id: SurfaceID,
|
||||
positioner: XdgPositioner,
|
||||
xdg_surface: WlWeak<XdgSurface>,
|
||||
}
|
||||
impl PopupData {
|
||||
fn new(
|
||||
uid: impl ToString,
|
||||
xdg_surface: &XdgSurface,
|
||||
parent_id: SurfaceID,
|
||||
positioner: XdgPositioner,
|
||||
) -> Self {
|
||||
PopupData {
|
||||
uid: uid.to_string(),
|
||||
grabbed: false,
|
||||
parent_id,
|
||||
positioner,
|
||||
xdg_surface: xdg_surface.downgrade(),
|
||||
}
|
||||
}
|
||||
pub fn get(popup: &XdgPopup) -> &Mutex<Self> {
|
||||
popup.data::<Mutex<Self>>().unwrap()
|
||||
}
|
||||
pub fn xdg_surface(&self) -> XdgSurface {
|
||||
self.xdg_surface.upgrade().unwrap()
|
||||
}
|
||||
|
||||
fn panel_item(&self) -> Option<Arc<PanelItem>> {
|
||||
XdgSurfaceData::get(&self.xdg_surface()).lock().panel_item()
|
||||
}
|
||||
// fn get_parent(&self) -> Option<XdgSurface> {
|
||||
// self.parent.as_ref()?.upgrade().ok()
|
||||
// }
|
||||
pub fn wl_surface(&self) -> WlSurface {
|
||||
XdgSurfaceData::get(&self.xdg_surface()).lock().wl_surface()
|
||||
}
|
||||
|
||||
pub fn positioner_data(&self) -> Option<PositionerData> {
|
||||
Some(
|
||||
self.positioner
|
||||
.data::<Mutex<PositionerData>>()?
|
||||
.lock()
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PopupData {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let Some(positioner_data) = self.positioner_data() else {return serde_error::<S>("Positioner not found")};
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
seq.serialize_element(&self.uid)?;
|
||||
seq.serialize_element(&self.parent_id)?;
|
||||
seq.serialize_element(&positioner_data)?;
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
impl Debug for PopupData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("XdgPopupData")
|
||||
.field("uid", &self.uid)
|
||||
.field("positioner", &self.positioner)
|
||||
.field("xdg_surface", &self.xdg_surface)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl Dispatch<XdgPopup, Mutex<PopupData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
xdg_popup: &XdgPopup,
|
||||
request: xdg_popup::Request,
|
||||
data: &Mutex<PopupData>,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
xdg_popup::Request::Grab { seat, serial } => {
|
||||
let mut data = data.lock();
|
||||
data.grabbed = true;
|
||||
debug!(?xdg_popup, ?seat, serial, "XDG popup grab");
|
||||
let Some(panel_item) = data.panel_item() else {return};
|
||||
panel_item.grab_keyboard(Some(SurfaceID::Popup(data.uid.clone())));
|
||||
}
|
||||
xdg_popup::Request::Reposition { positioner, token } => {
|
||||
let mut data = data.lock();
|
||||
debug!(?xdg_popup, ?positioner, token, "XDG popup reposition");
|
||||
data.positioner = positioner;
|
||||
let Some(panel_item) = data.panel_item() else {return};
|
||||
panel_item.reposition_popup(&*data);
|
||||
// xdg_popup.popup_done(); // temporary hack to avoid apps locking up before popups are implemented
|
||||
}
|
||||
xdg_popup::Request::Destroy => {
|
||||
let data = data.lock();
|
||||
debug!(?xdg_popup, "Destroy XDG popup");
|
||||
if data.grabbed {
|
||||
let Some(panel_item) = data.panel_item() else {return};
|
||||
panel_item.grab_keyboard(None);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
_state: &mut WaylandState,
|
||||
_client: ClientId,
|
||||
_resource: ObjectId,
|
||||
data: &Mutex<PopupData>,
|
||||
) {
|
||||
let data = data.lock();
|
||||
let Some(panel_item) = data.panel_item() else {return};
|
||||
panel_item.drop_popup(&data.uid);
|
||||
}
|
||||
}
|
||||
delegate_xdg_shell!(WaylandState);
|
||||
|
||||
5
stardust-xr-server.desktop
Normal file
5
stardust-xr-server.desktop
Normal file
@@ -0,0 +1,5 @@
|
||||
[Desktop Entry]
|
||||
Name=Stardust XR
|
||||
Comment=FOSS XR system UI (display server) project
|
||||
Exec=stardust-xr-server
|
||||
Type=Application
|
||||
Reference in New Issue
Block a user