Compare commits
514 Commits
openxr
...
Nervyalloy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c7a5efea | ||
|
|
b5dcffd7c0 | ||
|
|
9d0e1ce021 | ||
|
|
dd38b590c1 | ||
|
|
24b7195297 | ||
|
|
7d8993b640 | ||
|
|
4c70ded2b0 | ||
|
|
7f7a8b5264 | ||
|
|
43246900db | ||
|
|
b7a123f9c9 | ||
|
|
900316968a | ||
|
|
db30f8e61b | ||
|
|
0a005b9864 | ||
|
|
f4ed8bc37d | ||
|
|
49ee4d3b67 | ||
|
|
c2f1f737a0 | ||
|
|
c9a57773d1 | ||
|
|
68a7c06b9e | ||
|
|
b196cbfa3a | ||
|
|
7067d048d6 | ||
|
|
ef09b69378 | ||
|
|
c5dea3b7c9 | ||
|
|
5ea147f9fe | ||
|
|
3d6fceb0dd | ||
|
|
b1900de652 | ||
|
|
76ff476112 | ||
|
|
57f9516a81 | ||
|
|
fe6ed81255 | ||
|
|
173b033963 | ||
|
|
fe9ae8225c | ||
|
|
a149098044 | ||
|
|
2a5bddbb5a | ||
|
|
a7d5992b6b | ||
|
|
94b9b9ddcf | ||
|
|
cfb193251f | ||
|
|
14e899db0e | ||
|
|
42fc3c3f44 | ||
|
|
9bfbade9a2 | ||
|
|
3f4002881c | ||
|
|
8a8121f1a8 | ||
|
|
8fc017a6fc | ||
|
|
7016904adb | ||
|
|
93692f365e | ||
|
|
b765b68d41 | ||
|
|
5d82e42820 | ||
|
|
15fe997237 | ||
|
|
44a3480022 | ||
|
|
f0c50ba237 | ||
|
|
30a05a3218 | ||
|
|
779706d792 | ||
|
|
d65163553e | ||
|
|
33ccc66411 | ||
|
|
fb1627dccc | ||
|
|
9f49ba729d | ||
|
|
a44f36641e | ||
|
|
34fd7e6e49 | ||
|
|
a5f087d29f | ||
|
|
3b996c46e2 | ||
|
|
2e50491144 | ||
|
|
c3e4b2ed2a | ||
|
|
c0141da88b | ||
|
|
8f18d83694 | ||
|
|
6822e4bdb7 | ||
|
|
3c66109c45 | ||
|
|
d27ec84496 | ||
|
|
239e0c0318 | ||
|
|
ab913a8d84 | ||
|
|
a5653853f8 | ||
|
|
58a17fedba | ||
|
|
be709efbdd | ||
|
|
4f01bd5eec | ||
|
|
242eed37fe | ||
|
|
fe22d3954a | ||
|
|
96b4e22e10 | ||
|
|
a51db703fd | ||
|
|
7e755a44b8 | ||
|
|
80c9386f79 | ||
|
|
4730f0732b | ||
|
|
79935befb7 | ||
|
|
b2e452326b | ||
|
|
3e31905b5b | ||
|
|
c830becbff | ||
|
|
e8e485b472 | ||
|
|
bf68b87813 | ||
|
|
9546a36200 | ||
|
|
c12300e756 | ||
|
|
4683710f09 | ||
|
|
1ca054c2b3 | ||
|
|
5f7e9d4eb6 | ||
|
|
c9fe1be10b | ||
|
|
f58c748f80 | ||
|
|
499aa2be28 | ||
|
|
7ba710e8b7 | ||
|
|
62802367eb | ||
|
|
853f779930 | ||
|
|
21d10a15ee | ||
|
|
6146a5b63a | ||
|
|
01485e2020 | ||
|
|
6e77c3667d | ||
|
|
003dd9fcc1 | ||
|
|
42738b739f | ||
|
|
fe83a69bb9 | ||
|
|
e0a7d4f44a | ||
|
|
14ebe85493 | ||
|
|
08135b03a2 | ||
|
|
4827561f88 | ||
|
|
72f7fd8e9d | ||
|
|
827c630a70 | ||
|
|
98a6ad6f32 | ||
|
|
c09afba366 | ||
|
|
48f15e848d | ||
|
|
78912c8f1b | ||
|
|
13815d4d20 | ||
|
|
36d91fd999 | ||
|
|
b7191c3183 | ||
|
|
796ee1a34e | ||
|
|
de10a7101e | ||
|
|
41d22da691 | ||
|
|
73ed1210ed | ||
|
|
a8e16a8411 | ||
|
|
4d8d38e4e5 | ||
|
|
01f4a8fbcd | ||
|
|
f639a1662a | ||
|
|
6350444559 | ||
|
|
ecc7d7b912 | ||
|
|
ae40158dec | ||
|
|
1b28290cbb | ||
|
|
a3bcff035a | ||
|
|
71ca32a560 | ||
|
|
d28b477c8a | ||
|
|
643697ea33 | ||
|
|
f5259f2977 | ||
|
|
7bedd2c27f | ||
|
|
707b81871c | ||
|
|
1fddcd9033 | ||
|
|
7ff470e1db | ||
|
|
a7df6d8a08 | ||
|
|
a9ff97e39c | ||
|
|
126e9f4ca6 | ||
|
|
e379dae4ad | ||
|
|
b3fa529f77 | ||
|
|
1e8d3a3d4c | ||
|
|
2988ae3c28 | ||
|
|
9c04b5710e | ||
|
|
1324c26c74 | ||
|
|
0b9f79158c | ||
|
|
e5d0906d73 | ||
|
|
f08f3e4e4b | ||
|
|
d17841ec7a | ||
|
|
ebaf113d94 | ||
|
|
5be25da46f | ||
|
|
f5e8156400 | ||
|
|
02bf210c51 | ||
|
|
eba317ace9 | ||
|
|
84e42499e7 | ||
|
|
9b6b450d18 | ||
|
|
53979ce167 | ||
|
|
e2c9e06cd3 | ||
|
|
65a9957be1 | ||
|
|
fbe941749a | ||
|
|
36fd3216c7 | ||
|
|
f73c8f968d | ||
|
|
83e3a913c5 | ||
|
|
de56aa53d6 | ||
|
|
9f0043f406 | ||
|
|
99eb0ea547 | ||
|
|
04535895e8 | ||
|
|
69311125ba | ||
|
|
45b832455b | ||
|
|
b590e82b05 | ||
|
|
f770357010 | ||
|
|
3cbc10d91e | ||
|
|
9425d30cb3 | ||
|
|
8d2aac12d6 | ||
|
|
5f9d9d4714 | ||
|
|
eda50b7d51 | ||
|
|
01c5ad3b04 | ||
|
|
d7fba79be9 | ||
|
|
89fcc55cca | ||
|
|
a831fcb99f | ||
|
|
8e37d27130 | ||
|
|
149131f322 | ||
|
|
4a90b936b1 | ||
|
|
ab6998407b | ||
|
|
be2f5b8e37 | ||
|
|
226554fadc | ||
|
|
47cbc2b8fc | ||
|
|
90f60c9178 | ||
|
|
49253567cb | ||
|
|
350007cbaf | ||
|
|
7318d5317c | ||
|
|
15f771ff93 | ||
|
|
302a64785d | ||
|
|
34aab266a3 | ||
|
|
fba4e10611 | ||
|
|
807928a8d3 | ||
|
|
f74c891907 | ||
|
|
2dc0fdadfd | ||
|
|
8e317a8e08 | ||
|
|
c41179a437 | ||
|
|
43910cce78 | ||
|
|
643986e03d | ||
|
|
a2b061ed43 | ||
|
|
c458624157 | ||
|
|
1d2b149395 | ||
|
|
c84848ea9c | ||
|
|
b34e5f7a19 | ||
|
|
af3d49c9ef | ||
|
|
d5d63b2f89 | ||
|
|
d4b7c3f61a | ||
|
|
36dacb3322 | ||
|
|
eec38dd60f | ||
|
|
1e0ea6ae92 | ||
|
|
31f45760f0 | ||
|
|
f76863a79b | ||
|
|
43b3499ed7 | ||
|
|
dfaaa2a3a9 | ||
|
|
b8d17ac7ca | ||
|
|
ed28914f86 | ||
|
|
f8fab6bf5a | ||
|
|
1b37d77304 | ||
|
|
6eb36516b0 | ||
|
|
f0200be990 | ||
|
|
cfd3d0016b | ||
|
|
fbf0f4f672 | ||
|
|
387fa96a60 | ||
|
|
d549018024 | ||
|
|
5cd92f2c03 | ||
|
|
2450411e21 | ||
|
|
b53bfde23b | ||
|
|
15eb73af41 | ||
|
|
3c82ae309c | ||
|
|
4adcfca2fe | ||
|
|
f893491bed | ||
|
|
9d1181aaca | ||
|
|
b0f9bf24cf | ||
|
|
3527ce2507 | ||
|
|
f045bfb93d | ||
|
|
3d4ab27a14 | ||
|
|
54ff87c146 | ||
|
|
2bc988fe3d | ||
|
|
051893858b | ||
|
|
1ac211c23f | ||
|
|
4874f010dd | ||
|
|
da894143f9 | ||
|
|
109affec81 | ||
|
|
665e6b034f | ||
|
|
fc45b4e400 | ||
|
|
af75d2a451 | ||
|
|
3136a8f2b7 | ||
|
|
e97368f3e2 | ||
|
|
4da7ed1ccf | ||
|
|
bf248e192f | ||
|
|
167c3d1cbf | ||
|
|
b39d8f37f4 | ||
|
|
1198797db8 | ||
|
|
823a71a286 | ||
|
|
f78da4b198 | ||
|
|
558fb1aa4e | ||
|
|
abe32a8dbd | ||
|
|
d9e040bf8b | ||
|
|
0b8da1d280 | ||
|
|
455f3a0e9c | ||
|
|
411b30294b | ||
|
|
1d54b75a53 | ||
|
|
4c56c31bfc | ||
|
|
b21e031668 | ||
|
|
7c6b2f5949 | ||
|
|
1f66d7dee4 | ||
|
|
53b035ef92 | ||
|
|
237b084a65 | ||
|
|
707e56cb6f | ||
|
|
6f4da69e36 | ||
|
|
5634729445 | ||
|
|
b9603dc0a1 | ||
|
|
0323fbfb86 | ||
|
|
433568da63 | ||
|
|
4e199dd43f | ||
|
|
a4430b9a95 | ||
|
|
136383326e | ||
|
|
5a86f11beb | ||
|
|
6ab2bb2d52 | ||
|
|
ce8877b67e | ||
|
|
74a2f7a249 | ||
|
|
11ecb0aebe | ||
|
|
281f5e91ff | ||
|
|
5dc7cfbe83 | ||
|
|
3432c63a6e | ||
|
|
02ac96b0dc | ||
|
|
0736f99631 | ||
|
|
4bbe3ad8d0 | ||
|
|
1cf9d0f8c5 | ||
|
|
51d0cab832 | ||
|
|
062c63af2b | ||
|
|
3ce3fadb8d | ||
|
|
f0e39195b7 | ||
|
|
0d639760e9 | ||
|
|
6109a6bde6 | ||
|
|
000b633767 | ||
|
|
e3b1276d77 | ||
|
|
1cb8e1b7a4 | ||
|
|
e879b724ec | ||
|
|
08010efa46 | ||
|
|
e682931e3e | ||
|
|
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 |
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
build_and_package:
|
||||
@@ -11,13 +11,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
|
||||
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install -y --no-install-recommends cmake ninja-build
|
||||
run: sudo apt install -y --no-install-recommends cmake ninja-build libfuse2
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -26,21 +26,3 @@ jobs:
|
||||
|
||||
- name: Build server
|
||||
run: cargo build --release
|
||||
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
|
||||
chmod +x /usr/local/bin/appimagetool; \
|
||||
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
|
||||
- name: Install cargo-appimage
|
||||
run: cargo install cargo-appimage
|
||||
|
||||
- name: Generate AppImage
|
||||
run: cargo appimage
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: appimage
|
||||
path: '*.AppImage'
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@
|
||||
|
||||
/libs/
|
||||
*.AppImage
|
||||
*.blend1
|
||||
*.blend1
|
||||
anchors.txt
|
||||
|
||||
29
.lapce/run.toml
Normal file
29
.lapce/run.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
# The run config is used for both run mode and debug mode
|
||||
|
||||
[[configs]]
|
||||
# the name of this task
|
||||
name = "task"
|
||||
|
||||
# the type of the debugger. If not set, it can't be debugged but can still be run
|
||||
# type = "lldb"
|
||||
|
||||
# the program to run
|
||||
program = "./target/debug/stardust-xr-server"
|
||||
|
||||
# the program arguments, e.g. args = ["arg1", "arg2"], optional
|
||||
# args = []
|
||||
|
||||
# current working directory, optional
|
||||
cwd = "${workspace}"
|
||||
|
||||
# enviroment variables, optional
|
||||
# [configs.env]
|
||||
# VAR1 = "VAL1"
|
||||
# VAR2 = "VAL2"
|
||||
|
||||
# task to run before the run/debug session is started, optional
|
||||
# [configs.prelaunch]
|
||||
# program = "cargo"
|
||||
# args = [
|
||||
# "build",
|
||||
# ]
|
||||
2954
Cargo.lock
generated
2954
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
134
Cargo.toml
134
Cargo.toml
@@ -1,94 +1,114 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
name = "stardust-xr-server"
|
||||
version = "0.42.1"
|
||||
version = "0.45.0"
|
||||
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"
|
||||
|
||||
[workspace]
|
||||
members = ["codegen"]
|
||||
|
||||
[workspace.dependencies.stardust-xr]
|
||||
git = "https://github.com/StardustXR/core.git"
|
||||
branch = "dev"
|
||||
|
||||
[[bin]]
|
||||
name = "stardust-xr-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["wayland", "xwayland"]
|
||||
openxr_runtime = []
|
||||
wayland = ["dep:smithay", "dep:xkbcommon"]
|
||||
xwayland = ["smithay/xwayland"]
|
||||
default = ["wayland"]
|
||||
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
|
||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||
profile_app = ["dep:tracing-chrome"]
|
||||
profile_app = ["dep:tracing-tracy"]
|
||||
local_deps = ["stereokit-rust/force-local-deps"]
|
||||
|
||||
[package.metadata.appimage]
|
||||
auto_link = true
|
||||
auto_link_exclude_list = [
|
||||
"libc*",
|
||||
"libdl*",
|
||||
"libpthread*",
|
||||
"ld-linux*",
|
||||
"libGL*",
|
||||
"libEGL*",
|
||||
"libc*",
|
||||
"libdl*",
|
||||
"libpthread*",
|
||||
"ld-linux*",
|
||||
"libGL*",
|
||||
"libEGL*",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
strip = false
|
||||
debug-assertions = true
|
||||
overflow-checks = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = "line-tables-only"
|
||||
strip = true
|
||||
lto = true
|
||||
debug-assertions = true
|
||||
overflow-checks = false
|
||||
lto = "thin"
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
# small utility thingys
|
||||
nanoid = "0.4.0"
|
||||
once_cell = "1.17.1"
|
||||
parking_lot = "0.12.1"
|
||||
portable-atomic = { version = "1.2.0", features = ["float", "std"] }
|
||||
rustc-hash = "1.1.0"
|
||||
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal"] }
|
||||
send_wrapper = "0.6.0"
|
||||
prisma = "0.1.1"
|
||||
xkbcommon = { version = "0.5.0", default-features = false, optional = true }
|
||||
stardust-xr = "0.13.0"
|
||||
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"
|
||||
lazy_static = "1.5.0"
|
||||
rand = "0.8.5"
|
||||
atty = "0.2.14"
|
||||
rustc-hash = "2.0.0"
|
||||
send_wrapper = "0.6.0"
|
||||
slotmap = "1.0.7"
|
||||
global_counter = "=0.2.2"
|
||||
parking_lot = "0.12.3"
|
||||
|
||||
[dependencies.stereokit]
|
||||
default-features = false
|
||||
features = ["linux-egl"]
|
||||
version = "0.16.9"
|
||||
# rust errors/logging
|
||||
color-eyre = { version = "0.6.3", default-features = false }
|
||||
clap = { version = "4.5.13", features = ["derive"] }
|
||||
console-subscriber = { version = "0.4.0", optional = true }
|
||||
thiserror = "2.0.9"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing-tracy = { version = "0.11.1", optional = true }
|
||||
|
||||
# (de)serialization
|
||||
serde = { version = "1.0.205", features = ["derive"] }
|
||||
serde_repr = "0.1.19"
|
||||
toml = "0.8.19"
|
||||
|
||||
# mathy stuffs
|
||||
glam = { version = "0.29.0", features = ["mint", "serde"] }
|
||||
mint = "0.5.9"
|
||||
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
|
||||
|
||||
# linux stuffs
|
||||
input-event-codes = "6.2.0"
|
||||
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
||||
directories = "5.0.1"
|
||||
xkbcommon-rs = "0.1.0"
|
||||
|
||||
# wayland
|
||||
wayland-backend = { version = "0.3.7", optional = true, default-features = false }
|
||||
wayland-scanner = { version = "0.31.4", optional = true }
|
||||
dashmap = "6.1.0"
|
||||
|
||||
[dependencies.smithay]
|
||||
# git = "https://github.com/technobaboo/smithay.git" # 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"
|
||||
git = "https://github.com/smithay/smithay.git"
|
||||
default-features = false
|
||||
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
|
||||
[dependencies.stereokit-rust]
|
||||
git = "https://github.com/mvvvv/StereoKit-rust.git"
|
||||
rev = "73ffaae6f42aa369e599a6ea0391f77840d682d8"
|
||||
features = ["no-event-loop"]
|
||||
default-features = false
|
||||
|
||||
# [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"
|
||||
[dependencies.stardust-xr]
|
||||
workspace = true
|
||||
|
||||
[dependencies.stardust-xr-server-codegen]
|
||||
path = "codegen"
|
||||
|
||||
154
README.md
154
README.md
@@ -1,85 +1,113 @@
|
||||
# Stardust XR Reference Server
|
||||
# Stardust XR Server
|
||||
|
||||
This project is a usable Linux display server that reinvents human-computer interaction for all kinds of XR, from putting 2D/XR apps into various 3D shells for varying uses to SDF-based interaction.
|
||||
Stardust XR is a display server for VR and AR headsets on Linux-based systems. [Stardust provides a 3D environment](https://www.youtube.com/watch?v=v2WblwbaLaA), where anything from 2D windows (including your existing apps!), to 3D apps built from objects, can exist together in physical space.
|
||||
|
||||
## Prerequisites
|
||||
1. Cargo
|
||||
2. CMake
|
||||
3. EGL+GLES 3.2
|
||||
4. GLX+Xlib
|
||||
5. fontconfig
|
||||
6. dlopen
|
||||
7. OpenXR Loader (required even if run in flatscreen mode)
|
||||

|
||||
|
||||
Command line installation of core & dynamic dependencies are provided below:
|
||||
<details>
|
||||
<summary>Ubuntu/Debian</summary>
|
||||
<pre><code class="language-bash">
|
||||
sudo apt update && sudo apt install \
|
||||
build-essential \
|
||||
cargo \
|
||||
cmake \
|
||||
libxkbcommon-dev libudev1 libinput10 libcap2 libmtdev1 libevdev2 libwacom9 libgudev-1.0-0 \
|
||||
libglib2.0-dev libffi8 libpcre2-dev libxkbcommon-x11-dev libxcb-dev libxcb-xkb-dev libxau-dev \
|
||||
libstdc++-dev libx11-dev libxfixes-dev libegl-dev libgbm-dev libfontconfig1-dev libgl-dev \
|
||||
libdrm-dev libexpat1-dev libfreetype6-dev libxml2-dev zlib1g-dev libbz2-dev libpng-dev \
|
||||
libharfbuzz-dev libbrotli-dev liblzma-dev libraphite2-dev
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fedora</summary>
|
||||
<pre><code class="language-bash">
|
||||
sudo apt update && sudo apt install \
|
||||
cargo \
|
||||
cmake \
|
||||
libxkbcommon-devel systemd-devel libinput-devel libcap-devel mtdev-devel libevdev-devel glib2-devel \
|
||||
libffi-devel pcre2-devel libxkbcommon-x11-devel libxcb-devel libXau-devel libstdc++-devel libx11-devel libxfixes-devel \
|
||||
mesa-libEGL-devel mesa-libgbm-devel fontconfig-devel libdrm-devel expat-devel freetype-devel libxml2-devel zlib-devel \
|
||||
bzip2-devel libpng-devel harfbuzz-devel brotli-devel xz-devel graphite2-devel
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux</summary>
|
||||
<pre><code class="language-bash">
|
||||
sudo pacman -Syu --needed \
|
||||
cargo \
|
||||
cmake \
|
||||
libxkbcommon systemd libinput libcap mtdev libevdev libwacom glib2 libffi pcre2 libxkbcommon-x11 \
|
||||
libxcb libxau libx11 libxfixes mesa fontconfig libdrm expat freetype2 libxml2 zlib bzip2 \
|
||||
libpng harfbuzz brotli xz graphite
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
## Installation
|
||||
|
||||
More detailed instructions and walkthroughs are provided at https://www.stardustxr.org
|
||||
|
||||
The [Terra Repository](https://terra.fyralabs.com/) is required, and comes pre-installed with [Ultramarine Linux](https://ultramarine-linux.org/). Other Fedora Editions and derivatives can directly install terra-release:
|
||||
|
||||
## Build
|
||||
```bash
|
||||
cargo build
|
||||
sudo dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release
|
||||
```
|
||||
|
||||
For a full installation of the Stardust XR server *and* a selected group of clients, run:
|
||||
|
||||
```bash
|
||||
sudo dnf group install stardust-xr
|
||||
```
|
||||
|
||||
## Manual Build
|
||||
We've provided a manual installation script [here](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh) that clones and builds the Stardust XR server along with a number of other clients from their respective repositories, and provides a startup script for automatically launching some clients.
|
||||
|
||||
After cloning the repository
|
||||
```bash
|
||||
cargo build --release # this is needed to skip validation layers
|
||||
```
|
||||
The latest stable server is automatically built to an appimage at https://github.com/StardustXR/server/releases for easy testing.
|
||||
|
||||
## Usage
|
||||
> [!NOTE]
|
||||
> For help with setting up an XR headset on linux, visit https://stardustxr.org/docs/get-started/setup-openxr
|
||||
|
||||
First, try running `cargo run` in a terminal window. If a headset is plugged in and OpenXR is working no window will show up. However, the headset should show the same things as the window that opens:
|
||||
|
||||

|
||||
The **Stardust XR Server** is a server that runs clients, so without any running, you will see a black screen. If you only have the server installed, we recommend also cloning and building the following clients to start: [Flatland](https://github.com/StardustXR/flatland), which allows normal 2D apps to run in Stardust, [Protostar](https://github.com/StardustXR/protostar), which contains Hexagon Launcher, an app launcher menu, and [Black Hole](https://github.com/StardustXR/black-hole) to quickly tuck away your objects and apps (kind of like desktop peek on Windows).
|
||||
|
||||
Stardust won't do anything interesting without clients! Try some from https://github.com/StardustXR.
|
||||
First, try running `cargo run -- -f` in a terminal window to check out flatscreen mode, (or `stardust-xr-server -f` / `stardust-xr-server_dev -f` if you installed via dnf or the manual installation script, respectively, as they provide symlinks.)
|
||||
|
||||
### Default Sky
|
||||
If there aren't already any clients running, you'll need to manually launch them by either navigating to their repositories and running `cargo run`, or running them via their names if you installed via dnf or the manual installation script, such as `flatland`, `hexagon_launcher`, etc.
|
||||
|
||||
You can set a default skytex/skylight by putting your favorite HDRI equirectangular sky in `~/.config/stardust/skytex.hdr`. Certain clients can override this.
|
||||
> [!IMPORTANT]
|
||||
> [Flatland](https://github.com/StardustXR/flatland) must be running for 2D apps to launch.
|
||||
|
||||
Flatscreen mode when the default skybox is [Zhengyang Gate](https://polyhaven.com/a/zhengyang_gate):
|
||||

|
||||
### Startup Script
|
||||
A startup script can be created at `~/.config/stardust/startup` that will launch specified settings and clients/applications, an example of which is shown [here](https://github.com/cyberneticmelon/usefulscripts/blob/main/startup). If you used the [installation script](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh), one will have already been made for you. This allows wide flexibility of what clients to launch upon startup (and, for example, *where*, using the [Gravity](https://github.com/StardustXR/gravity) client to specify X Y and Z co-ordinates).
|
||||
|
||||
### Windowed Mode
|
||||
### Flatscreen Navigation
|
||||
A video guide showcasing flatscreen controls is available [here](https://www.youtube.com/watch?v=JCYecSlKlDI)
|
||||
|
||||
If the stardust server can't connect to an OpenXR runtime or you force it into flatscreen mode with `-f`, the server will show in a window.
|
||||

|
||||
To move around, hold down `Shift + W A S D`, with `Q` for moving down and `E` for moving up.
|
||||

|
||||
|
||||
You can navigate around by right click + dragging to look around, Shift+W/A/S/D/Q/E to move. If you have a virtual hand, left click pinches, right click points, both make a fist.
|
||||
To look around, hold down `Shift + Right` Click while moving the mouse.
|
||||

|
||||
|
||||
### Flags
|
||||
#### Flatscreen (-f)
|
||||
To drag applications out of the app launcher, hold down `Shift + ~`
|
||||

|
||||
|
||||
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:
|
||||

|
||||
### XR Navigation
|
||||
A video guide showcasing XR controls is available [here](https://www.youtube.com/watch?v=RbxFq6JjliA)
|
||||
|
||||
#### Overlay (-o \<PRIORITY>)
|
||||
**Quest 3 Hand tracking**:
|
||||
Pinch to drag and drop, grasp with full hand for grabbing, point and click with pointer finger to click or pinch from a distance
|
||||
|
||||
The server will, if in XR mode, be overlaid using the OpenXR overlay extension with the given priority.
|
||||

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

|
||||
|
||||
##### Everything
|
||||
|
||||
`nix flake check` will build every test underneath of the `checks` attribute in the `flake.nix`
|
||||

|
||||
|
||||
16
codegen/Cargo.toml
Normal file
16
codegen/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "stardust-xr-server-codegen"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.6.0"
|
||||
quote = "1.0.33"
|
||||
mint = "0.5.9"
|
||||
proc-macro2 = "1.0.71"
|
||||
split-iter = "0.1.0"
|
||||
|
||||
stardust-xr = { workspace = true }
|
||||
724
codegen/src/lib.rs
Normal file
724
codegen/src/lib.rs
Normal file
@@ -0,0 +1,724 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use split_iter::Splittable;
|
||||
use stardust_xr::schemas::protocol::*;
|
||||
|
||||
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
|
||||
quote!(#a #b)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn codegen_root_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(ROOT_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_node_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(NODE_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_spatial_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(SPATIAL_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(FIELD_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(AUDIO_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_drawable_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(DRAWABLE_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(INPUT_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_item_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(ITEM_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_item_camera_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(ITEM_CAMERA_PROTOCOL)
|
||||
}
|
||||
#[proc_macro]
|
||||
pub fn codegen_item_panel_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
codegen_protocol(ITEM_PANEL_PROTOCOL)
|
||||
}
|
||||
|
||||
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
|
||||
let protocol = Protocol::parse(protocol).unwrap();
|
||||
let interface = protocol
|
||||
.interface
|
||||
.map(|p| {
|
||||
let node_id = p.node_id;
|
||||
let node_id = quote! {
|
||||
const INTERFACE_NODE_ID: u64 = #node_id;
|
||||
};
|
||||
let aspect = generate_aspect(&Aspect {
|
||||
name: "interface".to_string(),
|
||||
id: 0,
|
||||
description: protocol.description.clone(),
|
||||
inherits: vec![],
|
||||
members: p.members,
|
||||
});
|
||||
quote! {
|
||||
#node_id
|
||||
#aspect
|
||||
pub struct Interface;
|
||||
impl crate::nodes::AspectIdentifier for Interface {
|
||||
impl_aspect_for_interface_aspect_id!{}
|
||||
}
|
||||
impl crate::nodes::Aspect for Interface {
|
||||
impl_aspect_for_interface_aspect!{}
|
||||
}
|
||||
pub fn create_interface(client: &std::sync::Arc<crate::core::client::Client>) -> crate::core::error::Result<()>{
|
||||
let node = crate::nodes::Node::from_id(client,INTERFACE_NODE_ID,false);
|
||||
node.add_aspect(Interface);
|
||||
node.add_to_scenegraph()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let custom_enums = protocol
|
||||
.custom_enums
|
||||
.iter()
|
||||
.map(generate_custom_enum)
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let custom_unions = protocol
|
||||
.custom_unions
|
||||
.iter()
|
||||
.map(generate_custom_union)
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let custom_structs = protocol
|
||||
.custom_structs
|
||||
.iter()
|
||||
.map(generate_custom_struct)
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let aspects = protocol
|
||||
.aspects
|
||||
.iter()
|
||||
.map(generate_aspect)
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into()
|
||||
}
|
||||
|
||||
fn generate_custom_enum(custom_enum: &CustomEnum) -> TokenStream {
|
||||
let name = Ident::new(&custom_enum.name.to_case(Case::Pascal), Span::call_site());
|
||||
let description = &custom_enum.description;
|
||||
|
||||
let argument_decls = custom_enum
|
||||
.variants
|
||||
.iter()
|
||||
.map(|a| Ident::new(&a.to_case(Case::Pascal), Span::call_site()).to_token_stream())
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
#[derive(Debug, Clone, Copy, serde_repr::Deserialize_repr, serde_repr::Serialize_repr)]
|
||||
#[repr(u32)]
|
||||
pub enum #name {#argument_decls}
|
||||
}
|
||||
}
|
||||
fn generate_custom_union(custom_union: &CustomUnion) -> TokenStream {
|
||||
let name = Ident::new(&custom_union.name.to_case(Case::Pascal), Span::call_site());
|
||||
let description = &custom_union.description;
|
||||
|
||||
let option_decls = custom_union
|
||||
.options
|
||||
.iter()
|
||||
.map(generate_union_option)
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum #name {#option_decls}
|
||||
}
|
||||
}
|
||||
fn generate_union_option(union_option: &UnionOption) -> TokenStream {
|
||||
let name = union_option
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|n| n.to_case(Case::Pascal))
|
||||
.unwrap_or_else(|| argument_type_option_name(&union_option._type));
|
||||
let description = union_option
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|d| quote!(#[doc = #d]))
|
||||
.unwrap_or_default();
|
||||
let identifier = Ident::new(&name, Span::call_site());
|
||||
let _type = generate_argument_type(&union_option._type, false, true);
|
||||
quote! (#description #identifier(#_type))
|
||||
}
|
||||
fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
|
||||
let name = Ident::new(&custom_struct.name.to_case(Case::Pascal), Span::call_site());
|
||||
let description = &custom_struct.description;
|
||||
|
||||
let argument_decls = custom_struct
|
||||
.fields
|
||||
.iter()
|
||||
.map(|a| generate_argument_decl(a, true))
|
||||
.map(|d| quote!(pub #d))
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct #name {#argument_decls}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_aspect(aspect: &Aspect) -> TokenStream {
|
||||
let description = &aspect.description;
|
||||
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server);
|
||||
|
||||
let client_mod_name = Ident::new(
|
||||
&format!("{}_client", &aspect.name.to_case(Case::Snake)),
|
||||
Span::call_site(),
|
||||
);
|
||||
let client_side_members = client_members
|
||||
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m))
|
||||
.reduce(fold_tokens)
|
||||
.map(|t| {
|
||||
// TODO: properly import all dependencies
|
||||
quote! {
|
||||
#[allow(clippy::all)]
|
||||
pub mod #client_mod_name {
|
||||
use super::*;
|
||||
#t
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let opcodes = aspect
|
||||
.members
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let aspect_name = aspect.name.to_case(Case::ScreamingSnake);
|
||||
let member_name = m.name.to_case(Case::ScreamingSnake);
|
||||
let name_type = if m.side == Side::Client {
|
||||
"CLIENT"
|
||||
} else {
|
||||
"SERVER"
|
||||
};
|
||||
let name = Ident::new(
|
||||
&format!("{aspect_name}_{member_name}_{name_type}_OPCODE"),
|
||||
Span::call_site(),
|
||||
);
|
||||
let opcode = m.opcode;
|
||||
|
||||
quote!(pub(crate) const #name: u64 = #opcode;)
|
||||
})
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let alias_info = generate_alias_info(aspect);
|
||||
|
||||
let server_side_members = server_members
|
||||
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let aspect_trait_name = Ident::new(
|
||||
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
||||
Span::call_site(),
|
||||
);
|
||||
let run_signals = aspect
|
||||
.members
|
||||
.iter()
|
||||
.filter(|m| m.side == Side::Server)
|
||||
.filter(|m| m._type == MemberType::Signal)
|
||||
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Signal, m))
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let run_methods = aspect
|
||||
.members
|
||||
.iter()
|
||||
.filter(|m| m.side == Side::Server)
|
||||
.filter(|m| m._type == MemberType::Method)
|
||||
.map(|m| generate_run_member(&aspect_trait_name, MemberType::Method, m))
|
||||
.reduce(fold_tokens)
|
||||
.unwrap_or_default();
|
||||
let server_side_members = quote! {
|
||||
#[allow(clippy::all)]
|
||||
#[doc = #description]
|
||||
pub trait #aspect_trait_name {
|
||||
#server_side_members
|
||||
}
|
||||
};
|
||||
let aspect_id_macro_name = Ident::new(
|
||||
&format!(
|
||||
"impl_aspect_for_{}_aspect_id",
|
||||
aspect.name.to_case(Case::Snake)
|
||||
),
|
||||
Span::call_site(),
|
||||
);
|
||||
let aspect_macro_name = Ident::new(
|
||||
&format!(
|
||||
"impl_aspect_for_{}_aspect",
|
||||
aspect.name.to_case(Case::Snake)
|
||||
),
|
||||
Span::call_site(),
|
||||
);
|
||||
let aspect_id = aspect.id;
|
||||
let aspect_macro = quote! {
|
||||
macro_rules! #aspect_id_macro_name {
|
||||
() => {
|
||||
const ID: u64 = #aspect_id;
|
||||
}
|
||||
}
|
||||
macro_rules! #aspect_macro_name {
|
||||
() => {
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||
self
|
||||
}
|
||||
#[allow(clippy::all)]
|
||||
fn run_signal(
|
||||
&self,
|
||||
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||
_node: std::sync::Arc<crate::nodes::Node>,
|
||||
_signal: u64,
|
||||
_message: crate::nodes::Message
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||
match _signal {
|
||||
#run_signals
|
||||
_ => Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound)
|
||||
}
|
||||
}
|
||||
#[allow(clippy::all)]
|
||||
fn run_method(
|
||||
&self,
|
||||
_calling_client: std::sync::Arc<crate::core::client::Client>,
|
||||
_node: std::sync::Arc<crate::nodes::Node>,
|
||||
_method: u64,
|
||||
_message: crate::nodes::Message,
|
||||
_method_response: crate::nodes::MethodResponseSender,
|
||||
) {
|
||||
match _method {
|
||||
#run_methods
|
||||
_ => {
|
||||
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
quote!(#opcodes #alias_info #client_side_members #server_side_members #aspect_macro)
|
||||
}
|
||||
|
||||
fn generate_alias_opcodes(aspect: &Aspect, side: Side, _type: MemberType) -> TokenStream {
|
||||
aspect
|
||||
.members
|
||||
.iter()
|
||||
.filter(|m| m.side == side)
|
||||
.filter(|m| m._type == _type)
|
||||
.map(|m| m.opcode)
|
||||
.map(|o| quote!(#o))
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
fn generate_alias_info(aspect: &Aspect) -> TokenStream {
|
||||
let aspect_alias_info_name = Ident::new(
|
||||
&format!(
|
||||
"{}_ASPECT_ALIAS_INFO",
|
||||
aspect.name.to_case(Case::ScreamingSnake)
|
||||
),
|
||||
Span::call_site(),
|
||||
);
|
||||
let local_signals = generate_alias_opcodes(aspect, Side::Server, MemberType::Signal);
|
||||
let local_methods = generate_alias_opcodes(aspect, Side::Server, MemberType::Method);
|
||||
let remote_signals = generate_alias_opcodes(aspect, Side::Client, MemberType::Signal);
|
||||
|
||||
let inherits = aspect
|
||||
.inherits
|
||||
.iter()
|
||||
.map(|a| {
|
||||
Ident::new(
|
||||
&format!("{}_ASPECT_ALIAS_INFO", a.to_case(Case::ScreamingSnake)),
|
||||
Span::call_site(),
|
||||
)
|
||||
})
|
||||
.map(|a| quote!(#a.clone()))
|
||||
.fold(quote!(), |a, b| quote!(#a + #b));
|
||||
|
||||
quote! {
|
||||
lazy_static::lazy_static! {
|
||||
#[allow(clippy::all)]
|
||||
pub static ref #aspect_alias_info_name: crate::nodes::alias::AliasInfo = crate::nodes::alias::AliasInfo {
|
||||
server_signals: vec![#local_signals],
|
||||
server_methods: vec![#local_methods],
|
||||
client_signals: vec![#remote_signals],
|
||||
}
|
||||
#inherits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenStream {
|
||||
let opcode = member.opcode;
|
||||
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
|
||||
let description = &member.description;
|
||||
|
||||
let side = member.side;
|
||||
let _type = member._type;
|
||||
|
||||
let first_args = match member.side {
|
||||
Side::Server => {
|
||||
quote!(_node: std::sync::Arc<crate::nodes::Node>, _calling_client: std::sync::Arc<crate::core::client::Client>)
|
||||
}
|
||||
Side::Client => quote!(_node: &crate::nodes::Node),
|
||||
};
|
||||
let argument_decls = member
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|a| generate_argument_decl(a, member.side == Side::Server))
|
||||
.fold(first_args, |a, b| quote!(#a, #b));
|
||||
|
||||
let argument_uses = member
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|a| generate_argument_serialize(&a.name, &a._type, a.optional))
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
let return_type = member
|
||||
.return_type
|
||||
.as_ref()
|
||||
.map(|r| generate_argument_type(r, false, true))
|
||||
.unwrap_or_else(|| quote!(()));
|
||||
let name_str = name.to_string();
|
||||
|
||||
match (side, _type) {
|
||||
(Side::Client, MemberType::Signal) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
|
||||
|
||||
let result = stardust_xr::schemas::flex::serialize((#argument_uses)).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
|
||||
if let Err(err) = result.as_ref() {
|
||||
::tracing::warn!("failed to send remote signal: {}::{}, error: {}",#aspect_name,#name_str,err);
|
||||
} else {
|
||||
::tracing::trace!("sent remote signal: {}::{}",#aspect_name,#name_str);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
(Side::Client, MemberType::Method) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
||||
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await;
|
||||
if let Err(err) = result.as_ref() {
|
||||
::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err);
|
||||
} else {
|
||||
::tracing::trace!("called remote method: {}::{}",#aspect_name,#name_str);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
(Side::Server, MemberType::Signal) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
fn #name(#argument_decls) -> crate::core::error::Result<()>;
|
||||
}
|
||||
}
|
||||
(Side::Server, MemberType::Method) => {
|
||||
quote! {
|
||||
#[doc = #description]
|
||||
fn #name(#argument_decls) -> impl std::future::Future<Output = crate::core::error::Result<#return_type>> + Send + Sync + 'static;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
|
||||
let opcode = member.opcode;
|
||||
let member_name_ident = Ident::new(&member.name, Span::call_site());
|
||||
|
||||
let argument_names = member
|
||||
.arguments
|
||||
.iter()
|
||||
.map(generate_argument_name)
|
||||
.reduce(|a, b| quote!(#a, #b));
|
||||
let argument_types = member
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let _type = convert_deserializeable_argument_type(&a._type);
|
||||
generate_argument_type(&_type, a.optional, true)
|
||||
})
|
||||
.reduce(|a, b| quote!(#a, #b));
|
||||
// dbg!(&argument_types);
|
||||
let deserialize = argument_names
|
||||
.clone()
|
||||
.zip(argument_types)
|
||||
.map(|(argument_names, argument_types)| {
|
||||
quote!{
|
||||
#[allow(unused_parens)]
|
||||
let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let serialize = generate_argument_serialize(
|
||||
"result",
|
||||
&member.return_type.clone().unwrap_or(ArgumentType::Empty),
|
||||
false,
|
||||
);
|
||||
let argument_uses = member
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
|
||||
.reduce(|a, b| quote!(#a, #b))
|
||||
.unwrap_or_default();
|
||||
let member_name = member_name_ident.to_string();
|
||||
let aspect_name_str = aspect_name.to_string();
|
||||
match _type {
|
||||
MemberType::Signal => quote! {
|
||||
#opcode => { let result = (move || {
|
||||
#deserialize
|
||||
<Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
||||
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() });
|
||||
if let Err(err) = result.as_ref() {
|
||||
::tracing::warn!("failed to receive local signal: {}::{}, error: {}",#aspect_name_str,#member_name,err);
|
||||
} else {
|
||||
::tracing::trace!("received local signal: {}::{}",#aspect_name_str,#member_name);
|
||||
}
|
||||
result
|
||||
},
|
||||
},
|
||||
MemberType::Method => quote! {
|
||||
#opcode => _method_response.wrap_async(async move {
|
||||
#deserialize
|
||||
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
|
||||
if let Err(err) = result.as_ref() {
|
||||
::tracing::warn!("failed to call local method: {}::{}, error: {}",#aspect_name_str,#member_name,err);
|
||||
} else {
|
||||
::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name);
|
||||
};
|
||||
let result = result?;
|
||||
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
fn generate_argument_name(argument: &Argument) -> TokenStream {
|
||||
Ident::new(&argument.name.to_case(Case::Snake), Span::call_site()).to_token_stream()
|
||||
}
|
||||
|
||||
fn convert_deserializeable_argument_type(argument_type: &ArgumentType) -> ArgumentType {
|
||||
match argument_type {
|
||||
ArgumentType::Node { .. } => ArgumentType::NodeID,
|
||||
ArgumentType::Vec(v) => {
|
||||
ArgumentType::Vec(Box::new(convert_deserializeable_argument_type(v.as_ref())))
|
||||
}
|
||||
ArgumentType::Map(v) => {
|
||||
ArgumentType::Map(Box::new(convert_deserializeable_argument_type(v.as_ref())))
|
||||
}
|
||||
f => f.clone(),
|
||||
}
|
||||
}
|
||||
fn generate_argument_deserialize(
|
||||
argument_name: &str,
|
||||
argument_type: &ArgumentType,
|
||||
optional: bool,
|
||||
) -> TokenStream {
|
||||
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
|
||||
if let ArgumentType::Node { .. } = argument_type {
|
||||
return match optional {
|
||||
true => quote!(#name.map(|n| _calling_client.get_node(#argument_name, n)?)),
|
||||
false => quote!(_calling_client.get_node(#argument_name, #name)?),
|
||||
};
|
||||
}
|
||||
if optional {
|
||||
let mapping = generate_argument_deserialize("o", argument_type, false);
|
||||
return quote!(#name.map(|o| Ok::<_, crate::core::error::ServerError>(#mapping)).transpose()?);
|
||||
}
|
||||
|
||||
match argument_type {
|
||||
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
|
||||
ArgumentType::Vec(v) => {
|
||||
let mapping = generate_argument_deserialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||
}
|
||||
ArgumentType::Map(v) => {
|
||||
let mapping = generate_argument_deserialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
}
|
||||
_ => quote!(#name),
|
||||
}
|
||||
}
|
||||
fn generate_argument_serialize(
|
||||
argument_name: &str,
|
||||
argument_type: &ArgumentType,
|
||||
optional: bool,
|
||||
) -> TokenStream {
|
||||
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
|
||||
match argument_type {
|
||||
ArgumentType::Node {
|
||||
_type,
|
||||
return_id_parameter_name: _,
|
||||
} => match optional {
|
||||
true => quote!(#name.map(|n| n.get_id())),
|
||||
false => quote!(#name.get_id()),
|
||||
},
|
||||
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
|
||||
ArgumentType::Vec(v) => {
|
||||
let mapping = generate_argument_serialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<crate::core::error::Result<Vec<_>>>()?)
|
||||
}
|
||||
ArgumentType::Map(v) => {
|
||||
let mapping = generate_argument_serialize("a", v, false);
|
||||
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<crate::core::error::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||
}
|
||||
_ => quote!(#name),
|
||||
}
|
||||
}
|
||||
fn generate_argument_decl(argument: &Argument, owned_values: bool) -> TokenStream {
|
||||
let name = Ident::new(&argument.name.to_case(Case::Snake), Span::call_site());
|
||||
let mut _type = generate_argument_type(&argument._type, argument.optional, owned_values);
|
||||
quote!(#name: #_type)
|
||||
}
|
||||
fn argument_type_option_name(argument_type: &ArgumentType) -> String {
|
||||
match argument_type {
|
||||
ArgumentType::Empty => "Empty".to_string(),
|
||||
ArgumentType::Bool => "Bool".to_string(),
|
||||
ArgumentType::Int => "Int".to_string(),
|
||||
ArgumentType::UInt => "UInt".to_string(),
|
||||
ArgumentType::Float => "Float".to_string(),
|
||||
ArgumentType::Vec2(_) => "Vec2".to_string(),
|
||||
ArgumentType::Vec3(_) => "Vec3".to_string(),
|
||||
ArgumentType::Quat => "Quat".to_string(),
|
||||
ArgumentType::Mat4 => "Mat4".to_string(),
|
||||
ArgumentType::Color => "Color".to_string(),
|
||||
ArgumentType::String => "String".to_string(),
|
||||
ArgumentType::Bytes => "Bytes".to_string(),
|
||||
ArgumentType::Vec(v) => format!("{}Vector", argument_type_option_name(v)),
|
||||
ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(m)),
|
||||
ArgumentType::NodeID => "Node ID".to_string(),
|
||||
ArgumentType::Datamap => "Datamap".to_string(),
|
||||
ArgumentType::ResourceID => "ResourceID".to_string(),
|
||||
ArgumentType::Enum(e) => e.clone(),
|
||||
ArgumentType::Union(u) => u.clone(),
|
||||
ArgumentType::Struct(s) => s.clone(),
|
||||
ArgumentType::Node { _type, .. } => _type.clone(),
|
||||
ArgumentType::Fd => "File Descriptor".to_string(),
|
||||
}
|
||||
}
|
||||
fn generate_argument_type(
|
||||
argument_type: &ArgumentType,
|
||||
optional: bool,
|
||||
owned: bool,
|
||||
) -> TokenStream {
|
||||
let _type = match argument_type {
|
||||
ArgumentType::Empty => quote!(()),
|
||||
ArgumentType::Bool => quote!(bool),
|
||||
ArgumentType::Int => quote!(i32),
|
||||
ArgumentType::UInt => quote!(u32),
|
||||
ArgumentType::Float => quote!(f32),
|
||||
ArgumentType::Vec2(t) => {
|
||||
let t = generate_argument_type(t, false, true);
|
||||
quote!(stardust_xr::values::Vector2<#t>)
|
||||
}
|
||||
ArgumentType::Vec3(t) => {
|
||||
let t = generate_argument_type(t, false, true);
|
||||
quote!(stardust_xr::values::Vector3<#t>)
|
||||
}
|
||||
ArgumentType::Quat => quote!(stardust_xr::values::Quaternion),
|
||||
ArgumentType::Mat4 => quote!(stardust_xr::values::Mat4),
|
||||
ArgumentType::Color => quote!(stardust_xr::values::Color),
|
||||
ArgumentType::Bytes => {
|
||||
if !owned {
|
||||
quote!(&[u8])
|
||||
} else {
|
||||
quote!(Vec<u8>)
|
||||
}
|
||||
}
|
||||
ArgumentType::String => {
|
||||
if !owned {
|
||||
quote!(&str)
|
||||
} else {
|
||||
quote!(String)
|
||||
}
|
||||
}
|
||||
ArgumentType::Vec(t) => {
|
||||
let t = generate_argument_type(t, false, true);
|
||||
if !owned {
|
||||
quote!(&[#t])
|
||||
} else {
|
||||
quote!(Vec<#t>)
|
||||
}
|
||||
}
|
||||
ArgumentType::Map(t) => {
|
||||
let t = generate_argument_type(t, false, true);
|
||||
|
||||
if !owned {
|
||||
quote!(&stardust_xr::values::Map<String, #t>)
|
||||
} else {
|
||||
quote!(stardust_xr::values::Map<String, #t>)
|
||||
}
|
||||
}
|
||||
ArgumentType::NodeID => quote!(u64),
|
||||
ArgumentType::Datamap => {
|
||||
if !owned {
|
||||
quote!(&stardust_xr::values::Datamap)
|
||||
} else {
|
||||
quote!(stardust_xr::values::Datamap)
|
||||
}
|
||||
}
|
||||
ArgumentType::ResourceID => {
|
||||
if !owned {
|
||||
quote!(&stardust_xr::values::ResourceID)
|
||||
} else {
|
||||
quote!(stardust_xr::values::ResourceID)
|
||||
}
|
||||
}
|
||||
ArgumentType::Enum(e) => {
|
||||
let enum_name = Ident::new(&e.to_case(Case::Pascal), Span::call_site());
|
||||
quote!(#enum_name)
|
||||
}
|
||||
ArgumentType::Union(u) => {
|
||||
let union_name = Ident::new(&u.to_case(Case::Pascal), Span::call_site());
|
||||
quote!(#union_name)
|
||||
}
|
||||
ArgumentType::Struct(s) => {
|
||||
let struct_name = Ident::new(&s.to_case(Case::Pascal), Span::call_site());
|
||||
if !owned {
|
||||
quote!(&#struct_name)
|
||||
} else {
|
||||
quote!(#struct_name)
|
||||
}
|
||||
}
|
||||
ArgumentType::Node {
|
||||
_type,
|
||||
return_id_parameter_name: _,
|
||||
} => {
|
||||
if !owned {
|
||||
quote!(&std::sync::Arc<crate::nodes::Node>)
|
||||
} else {
|
||||
quote!(std::sync::Arc<crate::nodes::Node>)
|
||||
}
|
||||
}
|
||||
ArgumentType::Fd => {
|
||||
quote!(&std::os::fd::OwnedFd)
|
||||
}
|
||||
};
|
||||
|
||||
if optional {
|
||||
quote!(Option<#_type>)
|
||||
} else {
|
||||
_type
|
||||
}
|
||||
}
|
||||
357
flake.lock
generated
357
flake.lock
generated
@@ -1,61 +1,23 @@
|
||||
{
|
||||
"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": {
|
||||
"crane": {
|
||||
"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",
|
||||
"lastModified": 1712681629,
|
||||
"narHash": "sha256-bMDXn4AkTXLCpoZbII6pDGoSeSe9gI87jxPsHRXgu/E=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "220387ac8e99cbee0ca4c95b621c4bc782b6a235",
|
||||
"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",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -64,11 +26,32 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678379998,
|
||||
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
|
||||
"lastModified": 1743550720,
|
||||
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
|
||||
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"hercules-ci-effects",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743550720,
|
||||
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -76,54 +59,17 @@
|
||||
"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",
|
||||
"crane": "crane",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683766358,
|
||||
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=",
|
||||
"lastModified": 1725868201,
|
||||
"narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=",
|
||||
"owner": "StardustXR",
|
||||
"repo": "flatland",
|
||||
"rev": "24613a496841bdf38e5f136608d5295860a75fce",
|
||||
"rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,78 +78,17 @@
|
||||
"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"
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681898675,
|
||||
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=",
|
||||
"lastModified": 1747284884,
|
||||
"narHash": "sha256-lTSKhRrassMcJ1ZsuUVunyl/F04vvCKY80HB/4rvvm4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-effects",
|
||||
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0",
|
||||
"rev": "7168f6002a6b48a9b6151e1e97e974a0722ecfdc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -212,85 +97,44 @@
|
||||
"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=",
|
||||
"lastModified": 1712791164,
|
||||
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
|
||||
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
|
||||
"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": {
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1673800717,
|
||||
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
|
||||
"lastModified": 1743296961,
|
||||
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1678293141,
|
||||
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
|
||||
"lastModified": 1747179050,
|
||||
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
|
||||
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -302,101 +146,26 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1678891326,
|
||||
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=",
|
||||
"owner": "NixOS",
|
||||
"lastModified": 1747542820,
|
||||
"narHash": "sha256-GaOZntlJ6gPPbbkTLjbd8BMWaDYafhuuYRNrxCGnPJw=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052",
|
||||
"rev": "292fa7d4f6519c074f0a50394dbbe69859bb6043",
|
||||
"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",
|
||||
"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",
|
||||
"flake-parts": "flake-parts",
|
||||
"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"
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
229
flake.nix
229
flake.nix
@@ -1,133 +1,120 @@
|
||||
{
|
||||
nixConfig = {
|
||||
extra-substituters = [ "https://stardustxr.cachix.org" ];
|
||||
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ];
|
||||
extra-trusted-public-keys = [
|
||||
"stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo="
|
||||
];
|
||||
};
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
|
||||
|
||||
# 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, ... }:
|
||||
# Since we do not have a monorepo, we have to fetch Flatland in order to use
|
||||
# it to create VM Tests
|
||||
flatland.url = "github:StardustXR/flatland";
|
||||
};
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
flake-parts,
|
||||
nixpkgs,
|
||||
hercules-ci-effects,
|
||||
flatland,
|
||||
...
|
||||
}:
|
||||
let
|
||||
name = "server";
|
||||
pkgs = system: import nixpkgs {
|
||||
inherit system;
|
||||
name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||
src = builtins.path {
|
||||
name = "${name}-source";
|
||||
path = toString ./.;
|
||||
filter =
|
||||
path: type:
|
||||
nixpkgs.lib.all (n: builtins.baseNameOf path != n) [
|
||||
"flake.nix"
|
||||
"flake.lock"
|
||||
"nix"
|
||||
"README.md"
|
||||
];
|
||||
};
|
||||
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"
|
||||
'';
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [ flake-parts.flakeModules.easyOverlay ];
|
||||
systems = [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
"riscv64-linux"
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
self',
|
||||
inputs',
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
{
|
||||
_module.args.pkgs = import inputs.nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ inputs.self.overlays.default ];
|
||||
};
|
||||
overlayAttrs = config.packages;
|
||||
packages =
|
||||
let
|
||||
sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
|
||||
in
|
||||
{
|
||||
default = self'.packages.${name};
|
||||
gnome-graphical-test = self'.checks.gnome-graphical-test;
|
||||
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
|
||||
inherit name src sk_gpu;
|
||||
};
|
||||
};
|
||||
apps.default = {
|
||||
type = "app";
|
||||
program = self'.packages.${name} + "/bin/stardust-xr-server";
|
||||
};
|
||||
checks.gnome-graphical-test = pkgs.nixosTest (
|
||||
import ./nix/gnome-graphical-test.nix { inherit pkgs self; }
|
||||
);
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [ self'.packages.default ];
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
};
|
||||
};
|
||||
flake = {
|
||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||
effects =
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
|
||||
in
|
||||
{ ref, rev, ... }:
|
||||
{
|
||||
gnome-graphical-test = hci-effects.mkEffect {
|
||||
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
|
||||
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
|
||||
effectScript = ''
|
||||
readSecretString stardustxrDiscord .webhook > .webhook
|
||||
readSecretString stardustxrIpfs .basicauth > .basicauth
|
||||
set -x
|
||||
export RESPONSE=$(curl -H @.basicauth -F file=@${
|
||||
self.packages."x86_64-linux".gnome-graphical-test
|
||||
}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
|
||||
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
|
||||
set +x
|
||||
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
|
||||
${pkgs.discord-sh}/bin/discord.sh \
|
||||
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
|
||||
--field "Ref;${ref}" \
|
||||
--field "Commit ID;${rev}" \
|
||||
--field "Flatland Revision;${flatland.rev}" \
|
||||
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
|
||||
--image "$ADDRESS"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 789 KiB |
BIN
img/workflow.png
Normal file
BIN
img/workflow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 872 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
12
justfile
Normal file
12
justfile
Normal file
@@ -0,0 +1,12 @@
|
||||
PREFIX := "usr"
|
||||
BINARY := PREFIX / "bin"
|
||||
DESTDIR := "/"
|
||||
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
test:
|
||||
cargo test
|
||||
|
||||
install:
|
||||
install -Dm755 target/release/stardust-xr-server {{ DESTDIR }}{{ BINARY }}/stardust-xr-server
|
||||
27
nix/meshoptimizer.nix
Normal file
27
nix/meshoptimizer.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ lib, stdenv, fetchFromGitHub, cmake }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "meshoptimizer";
|
||||
version = "0.20";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "zeux";
|
||||
repo = "meshoptimizer";
|
||||
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
|
||||
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ cmake ];
|
||||
|
||||
cmakeFlags = [
|
||||
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON"
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Mesh optimization library that makes meshes smaller and faster to render";
|
||||
homepage = "https://github.com/zeux/meshoptimizer";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
|
||||
16
nix/sk_gpu.nix
Normal file
16
nix/sk_gpu.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{ stdenv, fetchurl, unzip }:
|
||||
|
||||
let
|
||||
sk_gpu_zip = fetchurl {
|
||||
url =
|
||||
"https://github.com/StereoKit/sk_gpu/releases/download/v2024.9.26/sk_gpu.v2024.9.26.zip";
|
||||
sha256 = "sha256-W32RveeCszioWGtbCsvAqB28YHvOsw2xJ15MosYLFXk=";
|
||||
};
|
||||
in stdenv.mkDerivation rec {
|
||||
name = "sk_gpu";
|
||||
src = sk_gpu_zip;
|
||||
unpackPhase = ''
|
||||
unzip -d $out ${sk_gpu_zip}
|
||||
'';
|
||||
nativeBuildInputs = [ unzip ];
|
||||
}
|
||||
104
nix/stardust-xr-server.nix
Normal file
104
nix/stardust-xr-server.nix
Normal file
@@ -0,0 +1,104 @@
|
||||
{ rustPlatform
|
||||
, src
|
||||
, name
|
||||
, libGL
|
||||
, mesa
|
||||
, xorg
|
||||
, fontconfig
|
||||
, libxkbcommon
|
||||
, xkeyboard_config
|
||||
, libclang
|
||||
|
||||
, cmake
|
||||
, cpm-cmake
|
||||
, pkg-config
|
||||
, llvmPackages
|
||||
, fetchFromGitHub
|
||||
, sk_gpu
|
||||
, libXau
|
||||
, libgbm
|
||||
|
||||
, libXdmcp
|
||||
, stdenv
|
||||
, lib
|
||||
, openxr-loader
|
||||
, makeWrapper
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage rec {
|
||||
inherit src name;
|
||||
cargoLock = {
|
||||
lockFile = (src + "/Cargo.lock");
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
buildFeatures = [ "local_deps" ];
|
||||
FORCE_LOCAL_DEPS = true;
|
||||
CPM_LOCAL_PACKAGES_ONLY = true;
|
||||
CPM_SOURCE_CACHE = "./build";
|
||||
CPM_USE_LOCAL_PACKAGES = true;
|
||||
CPM_DOWNLOAD_ALL = false;
|
||||
|
||||
meshoptimizer = fetchFromGitHub {
|
||||
owner = "zeux";
|
||||
repo = "meshoptimizer";
|
||||
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
|
||||
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
|
||||
};
|
||||
basis_universal = fetchFromGitHub {
|
||||
owner = "BinomialLLC";
|
||||
repo = "basis_universal";
|
||||
rev = "900e40fb5d2502927360fe2f31762bdbb624455f";
|
||||
sha256 = "sha256-zBRAXgG5Fi6+5uPQCI/RCGatY6O4ELuYBoKrPNn4K+8=";
|
||||
};
|
||||
openxr_loader = fetchFromGitHub {
|
||||
owner = "KhronosGroup";
|
||||
repo = "OpenXR-SDK";
|
||||
rev = "288d3a7ebc1ad959f62d51da75baa3d27438c499";
|
||||
sha256 = "sha256-RdmnBe26hqPmqwCHIJolF6bSmZRmIKVlGF+TXAY35ig=";
|
||||
};
|
||||
|
||||
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
|
||||
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
|
||||
DEP_SK_GPU_SOURCE = "${sk_gpu}";
|
||||
DEP_OPENXR_LOADER_SOURCE = "${openxr_loader}";
|
||||
|
||||
postPatch = let libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
|
||||
in ''
|
||||
sk=$(echo $cargoDepsCopy/stereokit-rust-*/StereoKit)
|
||||
mkdir -p $sk/build/cpm
|
||||
|
||||
# This is not ideal, the original approach was to fetch the exact cmake
|
||||
# file version that was wanted from GitHub directly, but at least this way it comes from Nixpkgs.. so meh
|
||||
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.38.7.cmake
|
||||
mkdir -p $sk/sk_gpu
|
||||
cp -R ${sk_gpu}/* $sk/sk_gpu
|
||||
chmod -R 755 $sk/sk_gpu/tools/linux_x64/*
|
||||
export DEP_SK_GPU_SOURCE=$sk/sk_gpu
|
||||
export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib";
|
||||
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
|
||||
--set-rpath "${libPath}" \
|
||||
$sk/sk_gpu/tools/linux_x64/skshaderc
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang makeWrapper ];
|
||||
buildInputs = [
|
||||
libGL
|
||||
mesa
|
||||
xorg.libX11.dev
|
||||
xorg.libXft
|
||||
xorg.libXfixes
|
||||
fontconfig
|
||||
libxkbcommon
|
||||
xkeyboard_config
|
||||
libXau
|
||||
libXdmcp
|
||||
openxr-loader
|
||||
libgbm
|
||||
];
|
||||
LIBCLANG_PATH = "${libclang.lib}/lib";
|
||||
|
||||
postFixup = ''
|
||||
wrapProgram $out/bin/stardust-xr-server \
|
||||
--set XKB_CONFIG_ROOT "${xkeyboard_config}/share/X11/xkb"
|
||||
'';
|
||||
}
|
||||
@@ -1,44 +1,57 @@
|
||||
use super::scenegraph::Scenegraph;
|
||||
#[cfg(feature = "oxr_runtime")]
|
||||
use crate::openxr;
|
||||
use super::{
|
||||
client_state::{CLIENT_STATES, ClientStateParsed},
|
||||
destroy_queue,
|
||||
scenegraph::Scenegraph,
|
||||
};
|
||||
use crate::{
|
||||
core::{registry::OwnedRegistry, task},
|
||||
nodes::{
|
||||
audio, data, drawable, fields, hmd, input, items,
|
||||
root::Root,
|
||||
Node, audio, drawable, fields, input, items,
|
||||
root::{ClientState, Root},
|
||||
spatial,
|
||||
startup::{self, StartupSettings, STARTUP_SETTINGS},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use color_eyre::eyre::{Result, eyre};
|
||||
use global_counter::primitive::exact::CounterU32;
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use stardust_xr::messenger::{self, MessageSenderHandle};
|
||||
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
|
||||
use tokio::{net::UnixStream, task::JoinHandle};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
fs,
|
||||
iter::FromIterator,
|
||||
path::PathBuf,
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::{net::UnixStream, sync::watch, task::JoinHandle};
|
||||
use tracing::info;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CLIENTS: OwnedRegistry<Client> = OwnedRegistry::new();
|
||||
static ref INTERNAL_CLIENT_MESSAGE_TIMES: (watch::Sender<Instant>, watch::Receiver<Instant>) = watch::channel(Instant::now());
|
||||
pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client {
|
||||
pid: None,
|
||||
// env: None,
|
||||
exe: None,
|
||||
|
||||
dispatch_join_handle: OnceCell::new(),
|
||||
flush_join_handle: OnceCell::new(),
|
||||
disconnect_status: OnceCell::new(),
|
||||
dispatch_join_handle: OnceLock::new(),
|
||||
flush_join_handle: OnceLock::new(),
|
||||
disconnect_status: OnceLock::new(),
|
||||
|
||||
id_counter: CounterU32::new(0),
|
||||
message_last_received: INTERNAL_CLIENT_MESSAGE_TIMES.1.clone(),
|
||||
message_sender_handle: None,
|
||||
scenegraph: Default::default(),
|
||||
root: OnceCell::new(),
|
||||
root: OnceLock::new(),
|
||||
base_resource_prefixes: Default::default(),
|
||||
startup_settings: None,
|
||||
state: OnceLock::default(),
|
||||
});
|
||||
}
|
||||
pub fn tick_internal_client() {
|
||||
let _ = INTERNAL_CLIENT_MESSAGE_TIMES.0.send(Instant::now());
|
||||
}
|
||||
|
||||
pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
|
||||
let env = fs::read_to_string(format!("/proc/{pid}/environ"))?;
|
||||
@@ -48,24 +61,26 @@ pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
|
||||
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||
))
|
||||
}
|
||||
pub fn startup_settings(env: &FxHashMap<String, String>) -> Option<StartupSettings> {
|
||||
pub fn state(env: &FxHashMap<String, String>) -> Option<Arc<ClientStateParsed>> {
|
||||
let token = env.get("STARDUST_STARTUP_TOKEN")?;
|
||||
STARTUP_SETTINGS.lock().get(token).cloned()
|
||||
CLIENT_STATES.lock().get(token).cloned()
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
pid: Option<i32>,
|
||||
pub pid: Option<i32>,
|
||||
// env: Option<FxHashMap<String, String>>,
|
||||
exe: Option<PathBuf>,
|
||||
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||
disconnect_status: OnceCell<Result<()>>,
|
||||
dispatch_join_handle: OnceLock<JoinHandle<Result<()>>>,
|
||||
flush_join_handle: OnceLock<JoinHandle<Result<()>>>,
|
||||
disconnect_status: OnceLock<Result<()>>,
|
||||
|
||||
id_counter: CounterU32,
|
||||
message_last_received: watch::Receiver<Instant>,
|
||||
pub message_sender_handle: Option<MessageSenderHandle>,
|
||||
pub scenegraph: Arc<Scenegraph>,
|
||||
pub root: OnceCell<Arc<Root>>,
|
||||
pub root: OnceLock<Arc<Root>>,
|
||||
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
|
||||
pub startup_settings: Option<StartupSettings>,
|
||||
pub state: OnceLock<ClientState>,
|
||||
}
|
||||
impl Client {
|
||||
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
|
||||
@@ -82,36 +97,40 @@ impl Client {
|
||||
|
||||
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 state = env
|
||||
.as_ref()
|
||||
.and_then(state)
|
||||
.unwrap_or_else(|| Arc::new(ClientStateParsed::default()));
|
||||
|
||||
let (message_time_tx, message_last_received) = watch::channel(Instant::now());
|
||||
let client = CLIENTS.add(Client {
|
||||
pid,
|
||||
// env,
|
||||
exe: exe.clone(),
|
||||
|
||||
dispatch_join_handle: OnceCell::new(),
|
||||
flush_join_handle: OnceCell::new(),
|
||||
disconnect_status: OnceCell::new(),
|
||||
dispatch_join_handle: OnceLock::new(),
|
||||
flush_join_handle: OnceLock::new(),
|
||||
disconnect_status: OnceLock::new(),
|
||||
|
||||
id_counter: CounterU32::new(256),
|
||||
message_last_received,
|
||||
message_sender_handle: Some(messenger_tx.handle()),
|
||||
scenegraph: scenegraph.clone(),
|
||||
root: OnceCell::new(),
|
||||
root: OnceLock::new(),
|
||||
base_resource_prefixes: Default::default(),
|
||||
startup_settings,
|
||||
state: OnceLock::default(),
|
||||
});
|
||||
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
|
||||
let _ = client.root.set(Root::create(&client)?);
|
||||
hmd::make_alias(&client)?;
|
||||
let _ = client.root.set(Root::create(&client, state.root)?);
|
||||
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)?;
|
||||
#[cfg(feature = "openxr_runtime")]
|
||||
openxr::create_interface(&client);
|
||||
items::camera::create_interface(&client)?;
|
||||
items::panel::create_interface(&client)?;
|
||||
|
||||
let _ = client.state.set(state.apply_to(&client));
|
||||
|
||||
let pid_printable = pid
|
||||
.map(|pid| pid.to_string())
|
||||
@@ -123,7 +142,7 @@ impl Client {
|
||||
.map(|exe| exe.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "??".to_string());
|
||||
let _ = client.dispatch_join_handle.get_or_try_init(|| {
|
||||
let _ = client.dispatch_join_handle.get_or_init(|| {
|
||||
task::new(
|
||||
|| {
|
||||
format!(
|
||||
@@ -135,46 +154,73 @@ impl Client {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
loop {
|
||||
match messenger_rx.dispatch(&*scenegraph).await {
|
||||
Err(e) => {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
_ => (),
|
||||
if let Err(e) = messenger_rx.dispatch(&*scenegraph).await {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
let _ = message_time_tx.send(Instant::now());
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
let _ = client.flush_join_handle.get_or_try_init(|| {
|
||||
let _ = client.flush_join_handle.get_or_init(|| {
|
||||
task::new(
|
||||
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
|
||||
{
|
||||
let client = client.clone();
|
||||
async move {
|
||||
loop {
|
||||
match messenger_tx.flush().await {
|
||||
Err(e) => {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
_ => (),
|
||||
if let Err(e) = messenger_tx.flush().await {
|
||||
client.disconnect(Err(e.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn get_cmdline(&self) -> Option<Vec<String>> {
|
||||
let pid = self.pid?;
|
||||
let exe_proc_path = format!("/proc/{pid}/exe");
|
||||
let cmdline_proc_path = format!("/proc/{pid}/cmdline");
|
||||
let exe = std::fs::read_link(exe_proc_path).ok()?;
|
||||
let cmdline = std::fs::read_to_string(cmdline_proc_path).ok()?;
|
||||
let mut cmdline_split: Vec<_> = cmdline.split('\0').map(ToString::to_string).collect();
|
||||
cmdline_split.pop();
|
||||
*cmdline_split.get_mut(0).unwrap() = exe.to_str()?.to_string();
|
||||
Some(cmdline_split)
|
||||
}
|
||||
pub fn get_cwd(&self) -> Option<PathBuf> {
|
||||
let pid = self.pid?;
|
||||
let cwd_proc_path = format!("/proc/{pid}/cwd");
|
||||
std::fs::read_link(cwd_proc_path).ok()
|
||||
}
|
||||
pub async fn save_state(&self) -> Option<ClientStateParsed> {
|
||||
let internal = self.root.get()?.save_state().await.ok()?;
|
||||
Some(ClientStateParsed::from_deserialized(self, internal))
|
||||
}
|
||||
|
||||
pub fn generate_id(&self) -> u64 {
|
||||
self.id_counter.inc() as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
|
||||
pub fn get_node(&self, name: &'static str, id: u64) -> Result<Arc<Node>> {
|
||||
self.scenegraph
|
||||
.get_node(path)
|
||||
.get_node(id)
|
||||
.ok_or_else(|| eyre!("{} not found", name))
|
||||
}
|
||||
|
||||
pub fn unresponsive(&self) -> bool {
|
||||
let time_since_last_message = self.message_last_received.borrow().elapsed();
|
||||
time_since_last_message.as_millis() > 500
|
||||
}
|
||||
|
||||
pub fn disconnect(&self, reason: Result<()>) {
|
||||
let _ = self.disconnect_status.set(reason);
|
||||
if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() {
|
||||
@@ -183,7 +229,22 @@ impl Client {
|
||||
if let Some(flush_join_handle) = self.flush_join_handle.get() {
|
||||
flush_join_handle.abort();
|
||||
}
|
||||
CLIENTS.remove(self);
|
||||
if let Some(client) = CLIENTS.remove(self) {
|
||||
destroy_queue::add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Debug for Client {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Client")
|
||||
.field("pid", &self.pid)
|
||||
.field("exe", &self.exe)
|
||||
.field("dispatch_join_handle", &self.dispatch_join_handle)
|
||||
.field("flush_join_handle", &self.flush_join_handle)
|
||||
.field("disconnect_status", &self.disconnect_status)
|
||||
.field("base_resource_prefixes", &self.base_resource_prefixes)
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl Drop for Client {
|
||||
|
||||
119
src/core/client_state.rs
Normal file
119
src/core/client_state.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use super::client::{Client, get_env};
|
||||
use crate::nodes::{Node, root::ClientState, spatial::Spatial};
|
||||
use glam::Mat4;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CLIENT_STATES: Mutex<FxHashMap<String, Arc<ClientStateParsed>>> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LaunchInfo {
|
||||
pub cmdline: Vec<String>,
|
||||
pub cwd: PathBuf,
|
||||
pub env: FxHashMap<String, String>,
|
||||
}
|
||||
impl LaunchInfo {
|
||||
fn from_client(client: &Client) -> Option<Self> {
|
||||
Some(LaunchInfo {
|
||||
cmdline: client.get_cmdline()?,
|
||||
cwd: client.get_cwd()?,
|
||||
env: get_env(client.pid?).ok()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ClientStateParsed {
|
||||
pub launch_info: Option<LaunchInfo>,
|
||||
pub data: Vec<u8>,
|
||||
pub root: Mat4,
|
||||
pub spatial_anchors: FxHashMap<String, Mat4>,
|
||||
}
|
||||
impl ClientStateParsed {
|
||||
pub fn from_deserialized(client: &Client, state: ClientState) -> Self {
|
||||
ClientStateParsed {
|
||||
launch_info: LaunchInfo::from_client(client),
|
||||
data: state.data.unwrap_or_default(),
|
||||
root: Self::spatial_transform(client, state.root).unwrap_or_default(),
|
||||
spatial_anchors: state
|
||||
.spatial_anchors
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| Some((k, Self::spatial_transform(client, v)?)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
fn spatial_transform(client: &Client, id: u64) -> Option<Mat4> {
|
||||
let node = client.scenegraph.get_node(id)?;
|
||||
let spatial = node.get_aspect::<Spatial>().ok()?;
|
||||
Some(spatial.global_transform())
|
||||
}
|
||||
|
||||
pub fn token(self) -> String {
|
||||
let token = nanoid::nanoid!();
|
||||
CLIENT_STATES.lock().insert(token.clone(), Arc::new(self));
|
||||
token
|
||||
}
|
||||
pub fn from_file(file: &Path) -> Option<Self> {
|
||||
let file_string = std::fs::read_to_string(file).ok()?;
|
||||
toml::from_str(&file_string).ok()
|
||||
}
|
||||
pub fn to_file(&self, directory: &Path) {
|
||||
let app_name = self
|
||||
.launch_info
|
||||
.as_ref()
|
||||
.map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap())
|
||||
.unwrap_or("unknown");
|
||||
let state_file_path = directory
|
||||
.join(format!("{app_name}-{}", nanoid::nanoid!()))
|
||||
.with_extension("toml");
|
||||
|
||||
std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
pub fn apply_to(&self, client: &Arc<Client>) -> ClientState {
|
||||
if let Some(root) = client.root.get() {
|
||||
root.set_transform(self.root)
|
||||
}
|
||||
ClientState {
|
||||
data: Some(self.data.clone()),
|
||||
root: 0,
|
||||
spatial_anchors: self
|
||||
.spatial_anchors
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let node = Node::generate(client, true).add_to_scenegraph().unwrap();
|
||||
Spatial::add_to(&node, None, *v, false);
|
||||
(k.clone(), node.get_id())
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
pub fn launch_command(self) -> Option<Command> {
|
||||
let launch_info = self.launch_info.as_ref()?;
|
||||
let mut cmdline = launch_info.cmdline.iter();
|
||||
let mut command = Command::new(cmdline.next()?);
|
||||
command.args(cmdline);
|
||||
command.current_dir(&launch_info.cwd);
|
||||
command.envs(launch_info.env.iter());
|
||||
command.env("STARDUST_STARTUP_TOKEN", self.token());
|
||||
Some(command)
|
||||
}
|
||||
}
|
||||
impl Default for ClientStateParsed {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
launch_info: None,
|
||||
data: Default::default(),
|
||||
root: Mat4::IDENTITY,
|
||||
spatial_anchors: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,23 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::any::Any;
|
||||
use std::{any::Any, sync::LazyLock};
|
||||
use tokio::sync::mpsc::{self, unbounded_channel};
|
||||
|
||||
static MAIN_DESTROY_QUEUE: Mutex<Vec<Box<dyn Any + Send + Sync>>> = Mutex::new(Vec::new());
|
||||
type Anything = Box<dyn Any + Send + Sync>;
|
||||
|
||||
static MAIN_DESTROY_QUEUE: LazyLock<(
|
||||
mpsc::UnboundedSender<Anything>,
|
||||
Mutex<mpsc::UnboundedReceiver<Anything>>,
|
||||
)> = LazyLock::new(|| {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
(tx, Mutex::new(rx))
|
||||
});
|
||||
|
||||
pub fn add<T: Any + Sync + Send>(thing: T) {
|
||||
MAIN_DESTROY_QUEUE.lock().push(Box::new(thing));
|
||||
MAIN_DESTROY_QUEUE.0.send(Box::new(thing)).unwrap();
|
||||
}
|
||||
|
||||
pub fn clear() {
|
||||
MAIN_DESTROY_QUEUE.lock().clear();
|
||||
while let Ok(thing) = MAIN_DESTROY_QUEUE.1.lock().try_recv() {
|
||||
drop(thing)
|
||||
}
|
||||
}
|
||||
|
||||
75
src/core/error.rs
Normal file
75
src/core/error.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use color_eyre::eyre::Report;
|
||||
use stardust_xr::{
|
||||
messenger::MessengerError,
|
||||
schemas::flex::{
|
||||
FlexSerializeError,
|
||||
flexbuffers::{DeserializationError, ReaderError},
|
||||
},
|
||||
};
|
||||
use stereokit_rust::StereoKitError;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T, E = ServerError> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServerError {
|
||||
#[error("Internal: Unable to get client")]
|
||||
NoClient,
|
||||
#[error("Messenger does not exist for this node")]
|
||||
NoMessenger,
|
||||
#[error("Messenger error: {0}")]
|
||||
MessengerError(#[from] MessengerError),
|
||||
#[error("Remote method error: {0}")]
|
||||
RemoteMethodError(String),
|
||||
#[error("Serialization error: {0}")]
|
||||
SerializationError(#[from] FlexSerializeError),
|
||||
#[error("Deserialization error: {0}")]
|
||||
DeserializationError(#[from] DeserializationError),
|
||||
#[error("Reader error: {0}")]
|
||||
ReaderError(#[from] ReaderError),
|
||||
#[error("StereoKit error: {0}")]
|
||||
StereoKitError(#[from] StereoKitError),
|
||||
#[error("Aspect {} does not exist for node", 0.to_string())]
|
||||
NoAspect(TypeId),
|
||||
#[error("{0}")]
|
||||
Report(#[from] Report),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($msg:literal $(,)?) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||
};
|
||||
($err:expr $(,)?) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($cond:expr $(,)?) => {
|
||||
if !$cond {
|
||||
$crate::ensure!($cond, concat!("Condition failed: `", stringify!($cond), "`"))
|
||||
}
|
||||
};
|
||||
($cond:expr, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($msg)));
|
||||
}
|
||||
};
|
||||
($cond:expr, $err:expr $(,)?) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($err)));
|
||||
}
|
||||
};
|
||||
($cond:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
return Err($crate::core::error::ServerError::from(color_eyre::eyre::eyre!($fmt, $($arg)*)));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use super::client::Client;
|
||||
use super::task;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::error;
|
||||
|
||||
pub struct EventLoop {
|
||||
join_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
|
||||
let socket = UnixListener::bind(socket_path)?;
|
||||
|
||||
let join_handle = task::new(|| "event loop", async move {
|
||||
loop {
|
||||
let Ok((socket, _)) = socket.accept().await else { continue };
|
||||
if let Err(e) = Client::from_connection(socket) {
|
||||
error!(?e, "Unable to create client from connection");
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let event_loop = Arc::new(EventLoop { join_handle });
|
||||
|
||||
Ok(event_loop)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventLoop {
|
||||
fn drop(&mut self) {
|
||||
self.join_handle.abort();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
pub mod client;
|
||||
pub mod client_state;
|
||||
pub mod delta;
|
||||
pub mod destroy_queue;
|
||||
pub mod eventloop;
|
||||
pub mod node_collections;
|
||||
pub mod error;
|
||||
pub mod registry;
|
||||
pub mod resource;
|
||||
pub mod scenegraph;
|
||||
pub mod task;
|
||||
pub mod typed_datamap;
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
use crate::nodes::Node;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
hash::Hash,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LifeLinkedNodeList {
|
||||
nodes: Mutex<Vec<Weak<Node>>>,
|
||||
}
|
||||
impl LifeLinkedNodeList {
|
||||
pub fn add(&self, node: Weak<Node>) {
|
||||
self.nodes.lock().push(node);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.nodes
|
||||
.lock()
|
||||
.iter()
|
||||
.filter_map(|node| node.upgrade())
|
||||
.for_each(|node| {
|
||||
node.destroy();
|
||||
});
|
||||
self.nodes.lock().clear();
|
||||
}
|
||||
}
|
||||
impl Drop for LifeLinkedNodeList {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
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,20 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use parking_lot::{const_mutex, MappedMutexGuard, Mutex, MutexGuard};
|
||||
use dashmap::DashMap;
|
||||
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard, const_mutex};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::{Arc, LazyLock, Weak};
|
||||
|
||||
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
|
||||
#[derive(Debug)]
|
||||
pub struct Registry<T: Send + Sync + ?Sized>(MaybeLazy<DashMap<usize, Weak<T>>>);
|
||||
|
||||
impl<T: Send + Sync + ?Sized> Registry<T> {
|
||||
pub const fn new() -> Self {
|
||||
Registry(const_mutex(None))
|
||||
}
|
||||
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
|
||||
MutexGuard::map(self.0.lock(), |r| {
|
||||
r.get_or_insert_with(|| FxHashMap::default())
|
||||
})
|
||||
Registry(MaybeLazy::Lazy(LazyLock::new(DashMap::default)))
|
||||
}
|
||||
pub fn add(&self, t: T) -> Arc<T>
|
||||
where
|
||||
@@ -25,39 +23,117 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
||||
t_arc
|
||||
}
|
||||
pub fn add_raw(&self, t: &Arc<T>) {
|
||||
self.lock()
|
||||
self.0
|
||||
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t));
|
||||
}
|
||||
pub fn contains(&self, t: &T) -> bool {
|
||||
self.lock()
|
||||
self.0
|
||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
||||
}
|
||||
pub fn get_changes(old: &Registry<T>, new: &Registry<T>) -> (Vec<Arc<T>>, Vec<Arc<T>>) {
|
||||
let mut added = Vec::new();
|
||||
let mut removed = Vec::new();
|
||||
|
||||
for pair in new.0.iter() {
|
||||
let (id, entry) = pair.pair();
|
||||
if let Some(entry) = entry.upgrade() {
|
||||
if !old.0.contains_key(id) {
|
||||
added.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
for pair in old.0.iter() {
|
||||
let (id, entry) = pair.pair();
|
||||
if let Some(entry) = entry.upgrade() {
|
||||
if !new.0.contains_key(id) {
|
||||
removed.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
(added, removed)
|
||||
}
|
||||
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
|
||||
self.lock()
|
||||
self.0
|
||||
.iter()
|
||||
.filter_map(|pair| pair.1.upgrade())
|
||||
.filter_map(|pair| pair.value().upgrade())
|
||||
.collect()
|
||||
}
|
||||
pub fn set(&self, other: &Registry<T>) {
|
||||
self.clear();
|
||||
for (key, value) in other.0.deref().clone().into_iter() {
|
||||
self.0.insert(key, value);
|
||||
}
|
||||
}
|
||||
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
|
||||
self.0
|
||||
.lock()
|
||||
.take()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|pair| pair.1.upgrade())
|
||||
.collect()
|
||||
let contents = self.get_valid_contents();
|
||||
self.0.clear();
|
||||
contents
|
||||
}
|
||||
pub fn retain<F: Fn(&Arc<T>) -> bool>(&self, f: F) {
|
||||
self.0.retain(|_, v| {
|
||||
let Some(v) = v.upgrade() else {
|
||||
// why would we want to retain things we can't upgrade?
|
||||
return true;
|
||||
};
|
||||
(f)(&v)
|
||||
})
|
||||
}
|
||||
pub fn remove(&self, t: &T) {
|
||||
self.lock()
|
||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||
self.0.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
self.lock().clear();
|
||||
self.0.clear();
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
if self.0.is_empty() {
|
||||
return true;
|
||||
}
|
||||
self.0.iter().all(|v| v.value().strong_count() == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Mutex::new(self.0.lock().clone()))
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<T: Send + Sync + ?Sized> Default for Registry<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + Sized> FromIterator<Arc<T>> for Registry<T> {
|
||||
fn from_iter<I: IntoIterator<Item = Arc<T>>>(iter: I) -> Self {
|
||||
Registry(MaybeLazy::NonLazy(
|
||||
iter.into_iter()
|
||||
.map(|i| (Arc::as_ptr(&i) as usize, Arc::downgrade(&i)))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MaybeLazy<T> {
|
||||
Lazy(LazyLock<T>),
|
||||
NonLazy(T),
|
||||
}
|
||||
impl<T: Clone> Clone for MaybeLazy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
MaybeLazy::Lazy(lazy_lock) => Self::NonLazy(lazy_lock.deref().clone()),
|
||||
MaybeLazy::NonLazy(v) => Self::NonLazy(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Deref for MaybeLazy<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
MaybeLazy::Lazy(lazy_lock) => lazy_lock,
|
||||
MaybeLazy::NonLazy(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +144,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
|
||||
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())
|
||||
})
|
||||
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
|
||||
}
|
||||
pub fn add(&self, t: T) -> Arc<T>
|
||||
where
|
||||
@@ -90,9 +164,12 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
|
||||
self.lock()
|
||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
||||
}
|
||||
pub fn remove(&self, t: &T) {
|
||||
pub fn remove(&self, t: &T) -> Option<Arc<T>>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
self.lock()
|
||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||
.remove(&(ptr::addr_of!(*t) as *const () as usize))
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
self.lock().clear();
|
||||
|
||||
@@ -1,83 +1,49 @@
|
||||
use color_eyre::eyre::eyre;
|
||||
use serde::{de::Visitor, Deserialize};
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use stardust_xr::values::ResourceID;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResourceID {
|
||||
File(PathBuf),
|
||||
Namespaced { namespace: String, path: PathBuf },
|
||||
use super::client::Client;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(':').map(PathBuf::from).collect()).unwrap_or_default();
|
||||
}
|
||||
impl ResourceID {
|
||||
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
|
||||
match self {
|
||||
ResourceID::File(file) => (file.is_absolute()
|
||||
&& file.exists() && Self::has_extension(file, extensions))
|
||||
.then_some(file.clone()),
|
||||
ResourceID::Namespaced { namespace, path } => {
|
||||
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
|
||||
fn has_extension(path: &Path, extensions: &[&OsStr]) -> bool {
|
||||
if let Some(path_extension) = path.extension() {
|
||||
extensions.contains(&path_extension)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resource_file(
|
||||
resource: &ResourceID,
|
||||
client: &Client,
|
||||
extensions: &[&OsStr],
|
||||
) -> Option<PathBuf> {
|
||||
match resource {
|
||||
ResourceID::Direct(file) => {
|
||||
(file.is_absolute() && file.exists() && has_extension(file, extensions))
|
||||
.then_some(file.clone())
|
||||
}
|
||||
ResourceID::Namespaced { namespace, path } => {
|
||||
let file_name = path.file_name()?;
|
||||
let base_prefixes = client.base_resource_prefixes.lock().clone();
|
||||
THEMES
|
||||
.iter()
|
||||
.chain(base_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| has_extension(path, extensions))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for ResourceID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(ResourceVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceVisitor;
|
||||
impl<'de> Visitor<'de> for ResourceVisitor {
|
||||
type Value = ResourceID;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("A string containing an absolute path to file or \"[namespace]:[path]\" for a namespaced resource")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(if v.starts_with('/') {
|
||||
let path = PathBuf::from(v);
|
||||
path.metadata().map_err(serde::de::Error::custom)?;
|
||||
ResourceID::File(path)
|
||||
} else if let Some((namespace, path)) = v.split_once(':') {
|
||||
ResourceID::Namespaced {
|
||||
namespace: namespace.to_string(),
|
||||
path: PathBuf::from(path),
|
||||
}
|
||||
} else {
|
||||
return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
self.visit_str(&v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::Node;
|
||||
use crate::nodes::alias::get_original;
|
||||
use crate::{core::client::Client, nodes::Message};
|
||||
use color_eyre::eyre::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Serialize;
|
||||
use stardust_xr::scenegraph;
|
||||
use stardust_xr::scenegraph::ScenegraphError;
|
||||
use stardust_xr::schemas::flex::serialize;
|
||||
use std::future::Future;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::{debug, debug_span, instrument};
|
||||
|
||||
use core::hash::BuildHasherDefault;
|
||||
use dashmap::DashMap;
|
||||
use rustc_hash::FxHasher;
|
||||
use std::sync::{Arc, OnceLock, Weak};
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::{debug, debug_span};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Scenegraph {
|
||||
pub(super) client: OnceCell<Weak<Client>>,
|
||||
nodes: DashMap<String, Arc<Node>, BuildHasherDefault<FxHasher>>,
|
||||
pub(super) client: OnceLock<Weak<Client>>,
|
||||
nodes: Mutex<FxHashMap<u64, Arc<Node>>>,
|
||||
}
|
||||
|
||||
impl Scenegraph {
|
||||
@@ -30,40 +32,74 @@ impl Scenegraph {
|
||||
}
|
||||
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);
|
||||
self.nodes.lock().insert(node.get_id(), node);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
|
||||
let mut node = self.nodes.get(path)?.clone();
|
||||
while let Some(alias) = node.alias.get() {
|
||||
node = alias.original.upgrade()?;
|
||||
}
|
||||
Some(node)
|
||||
pub fn get_node(&self, node: u64) -> Option<Arc<Node>> {
|
||||
let node = self.nodes.lock().get(&node)?.clone();
|
||||
get_original(node, true)
|
||||
}
|
||||
|
||||
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
|
||||
debug!(path, "Remove node");
|
||||
let (_, node) = self.nodes.remove(path)?;
|
||||
Some(node)
|
||||
pub fn remove_node(&self, node: u64) -> Option<Arc<Node>> {
|
||||
debug!(node, "Remove node");
|
||||
self.nodes.lock().remove(&node)
|
||||
}
|
||||
}
|
||||
|
||||
pub type MethodResponse = Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>;
|
||||
pub struct MethodResponseSender(oneshot::Sender<MethodResponse>);
|
||||
impl MethodResponseSender {
|
||||
pub fn send(self, t: Result<Message, ScenegraphError>) {
|
||||
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
|
||||
}
|
||||
// pub fn send_method_return<T: Serialize>(
|
||||
// self,
|
||||
// result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
|
||||
// ) {
|
||||
// let _ = self.0.send(map_method_return(result));
|
||||
// }
|
||||
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
|
||||
self.send(f().map_err(|e| ScenegraphError::MemberError {
|
||||
error: e.to_string(),
|
||||
}))
|
||||
}
|
||||
pub fn wrap_async<T: Serialize>(
|
||||
self,
|
||||
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
||||
) {
|
||||
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
|
||||
}
|
||||
}
|
||||
fn map_method_return<T: Serialize>(
|
||||
result: Result<(T, Vec<OwnedFd>)>,
|
||||
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
||||
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
|
||||
error: e.to_string(),
|
||||
})?;
|
||||
|
||||
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
|
||||
error: format!("Internal: Serialization failed: {e}"),
|
||||
})?;
|
||||
Ok((serialized_value, fds))
|
||||
}
|
||||
impl scenegraph::Scenegraph for Scenegraph {
|
||||
fn send_signal(
|
||||
&self,
|
||||
path: &str,
|
||||
method: &str,
|
||||
node_id: u64,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
data: &[u8],
|
||||
fds: Vec<OwnedFd>,
|
||||
) -> Result<(), ScenegraphError> {
|
||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)};
|
||||
debug_span!("Handle signal", path, method).in_scope(|| {
|
||||
self.get_node(path)
|
||||
let Some(client) = self.get_client() else {
|
||||
return Err(ScenegraphError::NodeNotFound);
|
||||
};
|
||||
debug_span!("Handle signal", aspect_id, node_id, method).in_scope(|| {
|
||||
self.get_node(node_id)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.send_local_signal(
|
||||
client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: data.to_vec(),
|
||||
@@ -74,25 +110,31 @@ impl scenegraph::Scenegraph for Scenegraph {
|
||||
}
|
||||
fn execute_method(
|
||||
&self,
|
||||
path: &str,
|
||||
method: &str,
|
||||
node_id: u64,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
data: &[u8],
|
||||
fds: Vec<OwnedFd>,
|
||||
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)};
|
||||
debug_span!("Handle method", path, method).in_scope(|| {
|
||||
let message = self
|
||||
.get_node(path)
|
||||
.ok_or(ScenegraphError::NodeNotFound)?
|
||||
.execute_local_method(
|
||||
client,
|
||||
method,
|
||||
Message {
|
||||
data: data.to_vec(),
|
||||
fds,
|
||||
},
|
||||
)?;
|
||||
Ok((message.data, message.fds))
|
||||
})
|
||||
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
||||
) {
|
||||
let Some(client) = self.get_client() else {
|
||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||
return;
|
||||
};
|
||||
debug!(aspect_id, node_id, method, "Handle method");
|
||||
let Some(node) = self.get_node(node_id) else {
|
||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||
return;
|
||||
};
|
||||
node.execute_local_method(
|
||||
client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: data.to_vec(),
|
||||
fds,
|
||||
},
|
||||
MethodResponseSender(response),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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>,
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use stardust_xr::schemas::{
|
||||
flat::Datamap,
|
||||
flex::flexbuffers::{FlexbufferSerializer, Reader, ReaderError},
|
||||
};
|
||||
|
||||
use crate::nodes::Message;
|
||||
|
||||
pub struct TypedDatamap<T: DeserializeOwned + Serialize>(T);
|
||||
impl<T: DeserializeOwned + Serialize> TypedDatamap<T> {
|
||||
pub fn new(data: T) -> Self {
|
||||
TypedDatamap(data)
|
||||
}
|
||||
pub fn from_flex(message: Message) -> Result<Self> {
|
||||
let root = Reader::get_root(message.as_ref())?;
|
||||
T::deserialize(root).map(Self::new).map_err(|e| e.into())
|
||||
}
|
||||
pub fn to_datamap(&mut self) -> Result<Datamap> {
|
||||
let mut serializer = FlexbufferSerializer::default();
|
||||
self.0.serialize(&mut serializer)?;
|
||||
Datamap::new(serializer.take_buffer()).map_err(|e| e.into())
|
||||
}
|
||||
pub fn serialize(&mut self) -> Option<Vec<u8>> {
|
||||
let mut serializer = FlexbufferSerializer::default();
|
||||
self.0.serialize(&mut serializer).ok()?;
|
||||
// check if this is actually a map
|
||||
Reader::get_root(serializer.view()).ok()?.get_map().ok()?;
|
||||
Some(serializer.take_buffer())
|
||||
}
|
||||
}
|
||||
impl<T: DeserializeOwned + Serialize> Default for TypedDatamap<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self(T::default())
|
||||
}
|
||||
}
|
||||
impl<T: DeserializeOwned + Serialize> Deref for TypedDatamap<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T: DeserializeOwned + Serialize> DerefMut for TypedDatamap<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
504
src/main.rs
504
src/main.rs
@@ -1,76 +1,92 @@
|
||||
#![allow(clippy::empty_docs)]
|
||||
mod core;
|
||||
mod nodes;
|
||||
mod objects;
|
||||
#[cfg(feature = "openxr_runtime")]
|
||||
mod openxr;
|
||||
mod session;
|
||||
#[cfg(feature = "wayland")]
|
||||
mod wayland;
|
||||
|
||||
use crate::core::destroy_queue;
|
||||
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::objects::play_space::PlaySpace;
|
||||
use crate::nodes::items::camera;
|
||||
use crate::nodes::{audio, drawable, input};
|
||||
|
||||
use self::core::eventloop::EventLoop;
|
||||
use clap::Parser;
|
||||
use core::client::{Client, tick_internal_client};
|
||||
use core::task;
|
||||
use directories::ProjectDirs;
|
||||
use once_cell::sync::OnceCell;
|
||||
use objects::ServerObjects;
|
||||
use session::{launch_start, save_session};
|
||||
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||
use stardust_xr::server;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::Duration;
|
||||
use stereokit::{
|
||||
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread,
|
||||
TextureFormat, TextureType,
|
||||
use stereokit_rust::material::Material;
|
||||
use stereokit_rust::shader::Shader;
|
||||
use stereokit_rust::sk::{
|
||||
AppMode, DepthMode, DisplayBlend, OriginMode, QuitReason, SkSettings, sk_quit,
|
||||
};
|
||||
use stereokit::{DisplayBlend, Sk};
|
||||
use tokio::{runtime::Handle, sync::oneshot};
|
||||
use stereokit_rust::system::{Handed, Input, LogLevel, Renderer};
|
||||
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
|
||||
use stereokit_rust::ui::Ui;
|
||||
use stereokit_rust::util::{Color128, SphericalHarmonics, Time};
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::sync::Notify;
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing::{debug_span, error, info};
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
||||
use zbus::Connection;
|
||||
use zbus::fdo::ObjectManager;
|
||||
|
||||
pub static SK_INFO: OnceCell<SystemInfo> = OnceCell::new();
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct CliArgs {
|
||||
/// Force flatscreen mode and use the mouse pointer as a 3D pointer
|
||||
#[clap(short, long, action)]
|
||||
flatscreen: bool,
|
||||
|
||||
/// If monado insists on emulating them, set this flag...we want the raw input
|
||||
#[clap(long)]
|
||||
disable_controllers: bool,
|
||||
/// If monado insists on emulating , set this flag...we want the raw input
|
||||
#[clap(long)]
|
||||
disable_hands: bool,
|
||||
|
||||
/// Run Stardust XR as an overlay with given priority
|
||||
#[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,
|
||||
/// Debug the clients started by the server
|
||||
#[clap(short = 'd', long = "debug", action)]
|
||||
debug_launched_clients: bool,
|
||||
|
||||
/// Run a script when ready for clients to connect. If this is not set the script at $HOME/.config/stardust/startup will be ran if it exists.
|
||||
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
|
||||
startup_script: Option<PathBuf>,
|
||||
|
||||
/// Restore the session with the given ID (or `latest`), ignoring the startup script. Sessions are stored in directories at `~/.local/state/stardust/`.
|
||||
#[clap(id = "SESSION_ID", long = "restore", action)]
|
||||
restore: Option<String>,
|
||||
/// this should fix nvidia issues, it'll only help on driver 565+
|
||||
/// and only if running under wayland, probably
|
||||
#[clap(long)]
|
||||
nvidia: bool,
|
||||
}
|
||||
|
||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
|
||||
static STARDUST_INSTANCE: OnceLock<String> = OnceLock::new();
|
||||
|
||||
struct EventLoopInfo {
|
||||
tokio_handle: Handle,
|
||||
socket_path: PathBuf,
|
||||
}
|
||||
// #[tokio::main(flavor = "current_thread")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
|
||||
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);
|
||||
let registry = registry.with(
|
||||
tracing_tracy::TracyLayer::new(tracing_tracy::DefaultConfig::default())
|
||||
.with_filter(LevelFilter::DEBUG),
|
||||
);
|
||||
|
||||
#[cfg(feature = "profile_tokio")]
|
||||
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
|
||||
@@ -81,25 +97,135 @@ fn main() {
|
||||
.with_thread_names(true)
|
||||
.with_ansi(true)
|
||||
.with_line_number(true)
|
||||
.with_filter(EnvFilter::from_default_env());
|
||||
.with_filter(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::WARN.into())
|
||||
.from_env_lossy(),
|
||||
);
|
||||
registry.with(log_layer).init();
|
||||
|
||||
let cli_args = CliArgs::parse();
|
||||
|
||||
if cli_args.nvidia && !cli_args.flatscreen {
|
||||
// Only call this while singlethreaded since it can/will cause raceconditions with other
|
||||
// functions reading or writing from the env
|
||||
unsafe {
|
||||
std::env::set_var("__GLX_VENDOR_LIBRARY_NAME", "mesa");
|
||||
std::env::set_var(
|
||||
"__EGL_VENDOR_LIBRARY_FILENAMES",
|
||||
"/usr/share/glvnd/egl_vendor.d/50_mesa.json",
|
||||
);
|
||||
std::env::set_var("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
std::env::set_var("GALLIUM_DRIVER", "zink");
|
||||
}
|
||||
}
|
||||
|
||||
let socket_path =
|
||||
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
|
||||
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
|
||||
info!(
|
||||
socket_path = ?socket_path.display(),
|
||||
"Stardust socket created"
|
||||
);
|
||||
let socket =
|
||||
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
|
||||
task::new(|| "client join loop", async move {
|
||||
loop {
|
||||
let Ok((stream, _)) = socket.accept().await else {
|
||||
continue;
|
||||
};
|
||||
if let Err(e) = Client::from_connection(stream) {
|
||||
error!(?e, "Unable to create client from connection");
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
info!("Init client join loop");
|
||||
|
||||
let project_dirs = ProjectDirs::from("", "", "stardust");
|
||||
if project_dirs.is_none() {
|
||||
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
|
||||
error!(
|
||||
"Unable to get Stardust project directories, default skybox and startup script will not work."
|
||||
);
|
||||
}
|
||||
let cli_args = Arc::new(CliArgs::parse());
|
||||
|
||||
let sk = stereokit::Settings {
|
||||
app_name: "Stardust XR".to_string(),
|
||||
display_preference: if cli_args.flatscreen {
|
||||
DisplayMode::Flatscreen
|
||||
let dbus_connection = Connection::session()
|
||||
.await
|
||||
.expect("Could not open dbus session");
|
||||
dbus_connection
|
||||
.request_name("org.stardustxr.HMD")
|
||||
.await
|
||||
.expect(
|
||||
"Another instance of the server is running. This is not supported currently (but is planned).",
|
||||
);
|
||||
|
||||
dbus_connection
|
||||
.object_server()
|
||||
.at("/", ObjectManager)
|
||||
.await
|
||||
.expect("Couldn't add the object manager");
|
||||
|
||||
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect(
|
||||
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
|
||||
);
|
||||
|
||||
let sk_ready_notifier = Arc::new(Notify::new());
|
||||
let stereokit_loop = tokio::task::spawn_blocking({
|
||||
let sk_ready_notifier = sk_ready_notifier.clone();
|
||||
let project_dirs = project_dirs.clone();
|
||||
let cli_args = cli_args.clone();
|
||||
let dbus_connection = dbus_connection.clone();
|
||||
move || {
|
||||
stereokit_loop(
|
||||
sk_ready_notifier,
|
||||
project_dirs,
|
||||
cli_args,
|
||||
dbus_connection,
|
||||
object_registry,
|
||||
)
|
||||
}
|
||||
});
|
||||
sk_ready_notifier.notified().await;
|
||||
let mut startup_children = project_dirs
|
||||
.as_ref()
|
||||
.map(|project_dirs| launch_start(&cli_args, project_dirs))
|
||||
.unwrap_or_default();
|
||||
|
||||
tokio::select! {
|
||||
_ = stereokit_loop => (),
|
||||
_ = tokio::signal::ctrl_c() => unsafe {sk_quit(QuitReason::SystemClose)},
|
||||
}
|
||||
info!("Stopping...");
|
||||
if let Some(project_dirs) = project_dirs {
|
||||
save_session(&project_dirs).await;
|
||||
}
|
||||
for mut startup_child in startup_children.drain(..) {
|
||||
let _ = startup_child.kill();
|
||||
}
|
||||
|
||||
info!("Cleanly shut down Stardust");
|
||||
}
|
||||
|
||||
static DEFAULT_SKYTEX: OnceLock<Tex> = OnceLock::new();
|
||||
static DEFAULT_SKYLIGHT: OnceLock<SphericalHarmonics> = OnceLock::new();
|
||||
|
||||
fn stereokit_loop(
|
||||
sk_ready_notifier: Arc<Notify>,
|
||||
project_dirs: Option<ProjectDirs>,
|
||||
args: CliArgs,
|
||||
dbus_connection: Connection,
|
||||
object_registry: ObjectRegistry,
|
||||
) {
|
||||
let sk = SkSettings::default()
|
||||
.app_name("Stardust XR")
|
||||
.blend_preference(DisplayBlend::AnyTransparent)
|
||||
.mode(if args.flatscreen {
|
||||
AppMode::Simulator
|
||||
} else {
|
||||
DisplayMode::MixedReality
|
||||
},
|
||||
blend_preference: DisplayBlend::AnyTransparent,
|
||||
depth_mode: DepthMode::D32,
|
||||
log_filter: match EnvFilter::from_default_env().max_level_hint() {
|
||||
AppMode::XR
|
||||
})
|
||||
.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,
|
||||
@@ -107,213 +233,100 @@ fn main() {
|
||||
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());
|
||||
})
|
||||
.overlay_app(args.overlay_priority.is_some())
|
||||
.overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
|
||||
.disable_desktop_input_window(true)
|
||||
.origin(OriginMode::Local)
|
||||
.init()
|
||||
.expect("StereoKit failed to initialize");
|
||||
info!("Init StereoKit");
|
||||
|
||||
SK_INFO.set(stereokit.system_info()).unwrap();
|
||||
Renderer::multisample(0);
|
||||
Material::default().shader(Shader::pbr_clip());
|
||||
Ui::enable_far_interact(false);
|
||||
|
||||
sk.material_set_shader(
|
||||
sk.material_find("default/material_pbr").unwrap(),
|
||||
sk.shader_find("default/shader_pbr_clip").unwrap(),
|
||||
);
|
||||
let left_hand_material = Material::find("default/material_hand").unwrap();
|
||||
let mut right_hand_material = left_hand_material.copy();
|
||||
right_hand_material.id("right_hand");
|
||||
Input::hand_material(Handed::Right, Some(Material::find("right_hand").unwrap()));
|
||||
|
||||
Input::hand_visible(Handed::Left, false);
|
||||
Input::hand_visible(Handed::Right, false);
|
||||
|
||||
// Skytex/light stuff
|
||||
{
|
||||
if let Some((light, tex)) = project_dirs
|
||||
let _ = DEFAULT_SKYTEX.set(Tex::gen_color(
|
||||
Color128::BLACK,
|
||||
1,
|
||||
1,
|
||||
TexType::Cubemap,
|
||||
TexFormat::RGBA32,
|
||||
));
|
||||
let _ = DEFAULT_SKYLIGHT.set(Renderer::get_skylight());
|
||||
if let Some(sky) = project_dirs
|
||||
.as_ref()
|
||||
.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()
|
||||
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||
.filter(|f| f.exists())
|
||||
.and_then(|p| SHCubemap::from_cubemap(p, true, 100).ok())
|
||||
{
|
||||
sk.render_set_skytex(&tex);
|
||||
sk.render_set_skylight(light);
|
||||
sky.render_as_sky();
|
||||
} else {
|
||||
sk.render_set_skytex(sk.tex_gen_color(
|
||||
BLACK,
|
||||
1,
|
||||
1,
|
||||
TextureType::CUBEMAP,
|
||||
TextureFormat::RGBA32,
|
||||
));
|
||||
Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let mut mouse_pointer = cli_args
|
||||
.flatscreen
|
||||
.then(MousePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
let mut hands = (!cli_args.flatscreen)
|
||||
.then(|| {
|
||||
let left = SkHand::new(Handed::Left).ok();
|
||||
let right = SkHand::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
|
||||
.then(|| {
|
||||
let left = SkController::new(&sk, Handed::Left).ok();
|
||||
let right = SkController::new(&sk, Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze())
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
|
||||
if hands.is_none() {
|
||||
sk.input_hand_visible(Handed::Left, false);
|
||||
sk.input_hand_visible(Handed::Right, false);
|
||||
}
|
||||
|
||||
let play_space = sk
|
||||
.world_has_bounds()
|
||||
.then(|| PlaySpace::new().ok())
|
||||
.flatten();
|
||||
|
||||
let (event_stop_tx, event_stop_rx) = oneshot::channel::<()>();
|
||||
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
|
||||
let event_thread = std::thread::Builder::new()
|
||||
.name("event_loop".to_owned())
|
||||
.spawn(move || event_loop(info_sender, event_stop_rx))
|
||||
.unwrap();
|
||||
let event_loop_info = info_receiver.blocking_recv().unwrap();
|
||||
let _tokio_handle = event_loop_info.tokio_handle.enter();
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
let mut wayland = Some(wayland::Wayland::new().expect("Could not initialize wayland"));
|
||||
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.make_context_current();
|
||||
sk_ready_notifier.notify_waiters();
|
||||
info!("Stardust ready!");
|
||||
|
||||
let mut startup_child = 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 mut startup_command = Command::new(startup_script_path);
|
||||
|
||||
startup_command.stdin(Stdio::null());
|
||||
startup_command.env(
|
||||
"FLAT_WAYLAND_DISPLAY",
|
||||
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
|
||||
);
|
||||
startup_command.env(
|
||||
"STARDUST_INSTANCE",
|
||||
event_loop_info
|
||||
.socket_path
|
||||
.file_name()
|
||||
.expect("Stardust socket path not found"),
|
||||
);
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
startup_command.env("WAYLAND_DISPLAY", &wayland.as_ref().unwrap().socket_name);
|
||||
#[cfg(feature = "xwayland")]
|
||||
startup_command.env(
|
||||
"DISPLAY",
|
||||
format!(":{}", wayland.as_ref().unwrap().xwayland_state.display),
|
||||
);
|
||||
startup_command.env("GDK_BACKEND", "wayland");
|
||||
startup_command.env("QT_QPA_PLATFORM", "wayland");
|
||||
startup_command.env("MOZ_ENABLE_WAYLAND", "1");
|
||||
startup_command.env("CLUTTER_BACKEND", "wayland");
|
||||
startup_command.env("SDL_VIDEODRIVER", "wayland");
|
||||
}
|
||||
startup_command.spawn().ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut objects = ServerObjects::new(
|
||||
dbus_connection.clone(),
|
||||
&sk,
|
||||
[left_hand_material, right_hand_material],
|
||||
args.disable_controllers,
|
||||
args.disable_hands,
|
||||
);
|
||||
|
||||
let mut last_frame_delta = Duration::ZERO;
|
||||
let mut sleep_duration = Duration::ZERO;
|
||||
debug_span!("StereoKit").in_scope(|| {
|
||||
sk.run_stateful(
|
||||
&mut wayland,
|
||||
move |wayland, _, sk| {
|
||||
let _span = debug_span!("StereoKit step");
|
||||
let _span = _span.enter();
|
||||
while let Some(token) = sk.step() {
|
||||
let _span = debug_span!("StereoKit step");
|
||||
let _span = _span.enter();
|
||||
|
||||
hmd::frame(sk);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.as_mut().unwrap().frame_event(sk);
|
||||
destroy_queue::clear();
|
||||
camera::update(token);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.frame_event();
|
||||
destroy_queue::clear();
|
||||
|
||||
if let Some(mouse_pointer) = &mut 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),
|
||||
);
|
||||
objects.update(&sk, token, &dbus_connection, &object_registry);
|
||||
input::process_input();
|
||||
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
|
||||
adaptive_sleep(
|
||||
&mut last_frame_delta,
|
||||
&mut sleep_duration,
|
||||
Duration::from_micros(250),
|
||||
);
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.as_mut().unwrap().update(sk);
|
||||
drawable::draw(sk);
|
||||
audio::update(sk);
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.as_mut().unwrap().make_context_current();
|
||||
},
|
||||
|wayland, _sk| {
|
||||
info!("Cleanly shut down StereoKit");
|
||||
tick_internal_client();
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.update();
|
||||
drawable::draw(token);
|
||||
audio::update();
|
||||
}
|
||||
|
||||
if let Some(mut startup_child) = startup_child.take() {
|
||||
let _ = startup_child.kill();
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland.take();
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let _ = event_stop_tx.send(());
|
||||
event_thread
|
||||
.join()
|
||||
.expect("Failed to cleanly shut down event loop")
|
||||
.unwrap();
|
||||
// #[cfg(feature = "wayland")]
|
||||
// let _wayland = ManuallyDrop::new(wayland);
|
||||
|
||||
info!("Cleanly shut down Stardust");
|
||||
info!("Cleanly shut down StereoKit");
|
||||
}
|
||||
|
||||
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());
|
||||
let frame_delta = Duration::from_secs_f64(Time::get_step_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) {
|
||||
@@ -329,42 +342,3 @@ fn adaptive_sleep(
|
||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
// #[tokio::main(flavor = "current_thread")]
|
||||
async fn event_loop(
|
||||
info_sender: oneshot::Sender<EventLoopInfo>,
|
||||
stop_rx: oneshot::Receiver<()>,
|
||||
) -> 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,
|
||||
});
|
||||
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
stop_rx.await?;
|
||||
} else {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = tokio::signal::ctrl_c() => (),
|
||||
_ = stop_rx => (),
|
||||
};
|
||||
}
|
||||
|
||||
info!("Cleanly shut down event loop");
|
||||
|
||||
unsafe {
|
||||
stereokit::sys::sk_quit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,46 +1,163 @@
|
||||
use super::Node;
|
||||
use crate::core::client::Client;
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use std::sync::{Arc, Weak};
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::{client::Client, error::Result, registry::Registry};
|
||||
use std::{
|
||||
ops::Add,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct AliasInfo {
|
||||
pub(super) server_signals: Vec<&'static str>,
|
||||
pub(super) server_methods: Vec<&'static str>,
|
||||
pub(super) client_signals: Vec<&'static str>,
|
||||
pub(super) server_signals: Vec<u64>,
|
||||
pub(super) server_methods: Vec<u64>,
|
||||
pub(super) client_signals: Vec<u64>,
|
||||
}
|
||||
impl Add for AliasInfo {
|
||||
type Output = AliasInfo;
|
||||
fn add(mut self, mut rhs: Self) -> Self::Output {
|
||||
self.server_signals.append(&mut rhs.server_signals);
|
||||
self.server_methods.append(&mut rhs.server_methods);
|
||||
self.client_signals.append(&mut rhs.client_signals);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct Alias {
|
||||
pub(super) node: Weak<Node>,
|
||||
pub original: Weak<Node>,
|
||||
|
||||
pub info: AliasInfo,
|
||||
pub(super) original: Weak<Node>,
|
||||
pub(super) info: AliasInfo,
|
||||
}
|
||||
impl Alias {
|
||||
pub fn create(
|
||||
client: &Arc<Client>,
|
||||
parent: &str,
|
||||
name: &str,
|
||||
original: &Arc<Node>,
|
||||
client: &Arc<Client>,
|
||||
info: AliasInfo,
|
||||
list: Option<&AliasList>,
|
||||
) -> Result<Arc<Node>> {
|
||||
ensure!(
|
||||
client
|
||||
.scenegraph
|
||||
.get_node(&(parent.to_string() + "/" + name))
|
||||
.is_none(),
|
||||
"Node already exists"
|
||||
);
|
||||
let node = Node::generate(client, true).add_to_scenegraph()?;
|
||||
Self::add_to(&node, original, info)?;
|
||||
if let Some(list) = list {
|
||||
list.add(&node);
|
||||
}
|
||||
Ok(node)
|
||||
}
|
||||
pub fn create_with_id(
|
||||
original: &Arc<Node>,
|
||||
client: &Arc<Client>,
|
||||
new_id: u64,
|
||||
info: AliasInfo,
|
||||
list: Option<&AliasList>,
|
||||
) -> Result<Arc<Node>> {
|
||||
let node = Node::from_id(client, new_id, true).add_to_scenegraph()?;
|
||||
Self::add_to(&node, original, info)?;
|
||||
if let Some(list) = list {
|
||||
list.add(&node);
|
||||
}
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
let node = Node::create(client, parent, name, true).add_to_scenegraph()?;
|
||||
fn add_to(new_node: &Arc<Node>, original: &Arc<Node>, info: AliasInfo) -> Result<()> {
|
||||
let alias = Alias {
|
||||
node: Arc::downgrade(&node),
|
||||
node: Arc::downgrade(new_node),
|
||||
original: Arc::downgrade(original),
|
||||
info,
|
||||
};
|
||||
let alias = original.aliases.add(alias);
|
||||
let _ = node.alias.set(alias);
|
||||
Ok(node)
|
||||
new_node.add_aspect_raw(alias);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Alias {
|
||||
const ID: u64 = 0;
|
||||
}
|
||||
impl Aspect for Alias {
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
||||
self
|
||||
}
|
||||
fn run_signal(
|
||||
&self,
|
||||
_calling_client: Arc<Client>,
|
||||
_node: Arc<Node>,
|
||||
_signal: u64,
|
||||
_message: super::Message,
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError> {
|
||||
Ok(())
|
||||
}
|
||||
fn run_method(
|
||||
&self,
|
||||
_calling_client: Arc<Client>,
|
||||
_node: Arc<Node>,
|
||||
_method: u64,
|
||||
_message: super::Message,
|
||||
_response: crate::core::scenegraph::MethodResponseSender,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_original(node: Arc<Node>, stop_on_disabled: bool) -> Option<Arc<Node>> {
|
||||
let Ok(alias) = node.get_aspect::<Alias>() else {
|
||||
return Some(node);
|
||||
};
|
||||
if stop_on_disabled && !node.enabled() {
|
||||
return None;
|
||||
}
|
||||
get_original(alias.original.upgrade()?, stop_on_disabled)
|
||||
}
|
||||
pub fn links_to(alias: Arc<Node>, original: Weak<Node>) -> bool {
|
||||
let Ok(alias) = alias.get_aspect::<Alias>() else {
|
||||
return false;
|
||||
};
|
||||
if alias.original.ptr_eq(&original) {
|
||||
return true;
|
||||
}
|
||||
let Some(original_strong) = alias.original.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
links_to(original_strong, original)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct AliasList(Registry<Node>);
|
||||
impl AliasList {
|
||||
fn add(&self, node: &Arc<Node>) {
|
||||
self.0.add_raw(node);
|
||||
}
|
||||
pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> {
|
||||
self.0
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.find(move |node| links_to(node.clone(), original.clone()))
|
||||
}
|
||||
pub fn get_from_aspect<A: AspectIdentifier>(&self, aspect: &A) -> Option<Arc<Node>> {
|
||||
self.0.get_valid_contents().into_iter().find(|node| {
|
||||
let Some(node) = get_original(node.clone(), false) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(aspect2) = node.get_aspect::<A>() else {
|
||||
return false;
|
||||
};
|
||||
Arc::as_ptr(&aspect2) == (aspect as *const A)
|
||||
})
|
||||
}
|
||||
pub fn get_aliases(&self) -> Vec<Arc<Node>> {
|
||||
self.0.get_valid_contents()
|
||||
}
|
||||
pub fn remove_aspect<A: AspectIdentifier>(&self, aspect: &A) {
|
||||
self.0.retain(|node| {
|
||||
let Some(original) = get_original(node.clone(), false) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(aspect2) = original.get_aspect::<A>() else {
|
||||
return false;
|
||||
};
|
||||
Arc::as_ptr(&aspect2) != (aspect as *const A)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Drop for AliasList {
|
||||
fn drop(&mut self) {
|
||||
for node in self.0.take_valid_contents() {
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,125 @@
|
||||
use super::{Message, Node};
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::destroy_queue;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::resource::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 crate::core::resource::get_resource_file;
|
||||
use crate::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform};
|
||||
use color_eyre::eyre::eyre;
|
||||
use glam::{Vec4Swizzles, vec3};
|
||||
use parking_lot::Mutex;
|
||||
use send_wrapper::SendWrapper;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use stardust_xr::values::ResourceID;
|
||||
|
||||
use std::ops::DerefMut;
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
|
||||
|
||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||
|
||||
stardust_xr_server_codegen::codegen_audio_protocol!();
|
||||
pub struct Sound {
|
||||
space: Arc<Spatial>,
|
||||
|
||||
volume: f32,
|
||||
pending_audio_path: PathBuf,
|
||||
sk_sound: OnceCell<SendWrapper<SkSound>>,
|
||||
instance: Mutex<Option<SoundInstance>>,
|
||||
sk_sound: OnceLock<SkSound>,
|
||||
instance: Mutex<Option<SoundInst>>,
|
||||
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 pending_audio_path = get_resource_file(
|
||||
&resource_id,
|
||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||
&[OsStr::new("wav"), OsStr::new("mp3")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
let sound = Sound {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
volume: 1.0,
|
||||
pending_audio_path,
|
||||
sk_sound: OnceCell::new(),
|
||||
sk_sound: OnceLock::new(),
|
||||
instance: Mutex::new(None),
|
||||
stop: Mutex::new(None),
|
||||
play: Mutex::new(None),
|
||||
};
|
||||
let sound_arc = SOUND_REGISTRY.add(sound);
|
||||
node.add_local_signal("play", Sound::play_flex);
|
||||
node.add_local_signal("stop", Sound::stop_flex);
|
||||
let _ = node.sound.set(sound_arc.clone());
|
||||
node.add_aspect_raw(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())
|
||||
});
|
||||
fn update(&self) {
|
||||
let sound = self
|
||||
.sk_sound
|
||||
.get_or_init(|| SkSound::from_file(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);
|
||||
instance.stop();
|
||||
}
|
||||
}
|
||||
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 self.instance.lock().is_none() && self.play.lock().take().is_some() {
|
||||
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
|
||||
self.instance.lock().replace(instance);
|
||||
}
|
||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
||||
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
|
||||
instance.position(self.space.global_transform().w_axis.xyz());
|
||||
}
|
||||
}
|
||||
|
||||
fn play_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
|
||||
let sound = node.sound.get().unwrap();
|
||||
}
|
||||
impl AspectIdentifier for Sound {
|
||||
impl_aspect_for_sound_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Sound {
|
||||
impl_aspect_for_sound_aspect! {}
|
||||
}
|
||||
impl SoundAspect for Sound {
|
||||
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let sound = node.get_aspect::<Sound>().unwrap();
|
||||
sound.play.lock().replace(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
|
||||
let sound = node.sound.get().unwrap();
|
||||
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let sound = node.get_aspect::<Sound>().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>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateSoundInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
resource: ResourceID,
|
||||
}
|
||||
let info: CreateSoundInfo = deserialize(message.as_ref())?;
|
||||
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);
|
||||
instance.stop();
|
||||
}
|
||||
if let Some(sk_sound) = self.sk_sound.take() {
|
||||
destroy_queue::add(sk_sound);
|
||||
}
|
||||
SOUND_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update() {
|
||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||
sound.update()
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
||||
fn create_sound(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
resource: ResourceID,
|
||||
) -> Result<()> {
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Sound::add_to(&node, resource)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
use super::alias::AliasInfo;
|
||||
use super::fields::Field;
|
||||
use super::spatial::{parse_transform, Spatial};
|
||||
use super::{Alias, Message, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO};
|
||||
use crate::nodes::spatial::find_spatial_parent;
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use glam::vec3a;
|
||||
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();
|
||||
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
||||
|
||||
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 = mask_map_lesser.get_mask()?.index(key)?;
|
||||
let greater_key = mask_map_greater.get_mask()?.index(key)?;
|
||||
if lesser_key.flexbuffer_type() != greater_key.flexbuffer_type() {
|
||||
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
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]>> {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SendDataInfo<'a> {
|
||||
uid: &'a str,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct PulseSender {
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
pub mask: Mask,
|
||||
aliases: LifeLinkedNodeMap<String>,
|
||||
}
|
||||
impl PulseSender {
|
||||
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 = PulseSender {
|
||||
uid: nanoid!(),
|
||||
node: Arc::downgrade(node),
|
||||
mask,
|
||||
aliases: LifeLinkedNodeMap::default(),
|
||||
};
|
||||
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
||||
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())
|
||||
}
|
||||
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()
|
||||
},
|
||||
);
|
||||
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(),
|
||||
);
|
||||
if let Ok(rx_field_alias) = rx_field_alias {
|
||||
self.aliases
|
||||
.add(receiver.uid.clone() + "-field", &rx_field_alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NewReceiverInfo<'a> {
|
||||
uid: &'a str,
|
||||
distance: f32,
|
||||
position: Vector3<f32>,
|
||||
rotation: Quaternion<f32>,
|
||||
}
|
||||
|
||||
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>, message: Message) -> Result<()> {
|
||||
let info: SendDataInfo = deserialize(message.as_ref())?;
|
||||
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 {
|
||||
fn drop(&mut self) {
|
||||
PULSE_SENDER_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PulseReceiver {
|
||||
uid: String,
|
||||
pub node: Weak<Node>,
|
||||
pub field: Arc<Field>,
|
||||
pub mask: Mask,
|
||||
}
|
||||
impl PulseReceiver {
|
||||
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: nanoid!(),
|
||||
node: Arc::downgrade(node),
|
||||
field,
|
||||
mask,
|
||||
};
|
||||
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
||||
|
||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
||||
sender.handle_new_receiver(&receiver);
|
||||
}
|
||||
let _ = node.pulse_receiver.set(receiver.clone());
|
||||
Ok(receiver)
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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>) -> Result<()> {
|
||||
let node = Node::create(client, "", "data", false);
|
||||
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 create_pulse_sender_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreatePulseSenderInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
mask: Vec<u8>,
|
||||
}
|
||||
let info: CreatePulseSenderInfo = deserialize(message.as_ref())?;
|
||||
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 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 create_pulse_receiver_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreatePulseReceiverInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
mask: Vec<u8>,
|
||||
}
|
||||
let info: CreatePulseReceiverInfo = deserialize(message.as_ref())?;
|
||||
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 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, false)?;
|
||||
PulseReceiver::add_to(&node, field, mask)?;
|
||||
Ok(())
|
||||
}
|
||||
BIN
src/nodes/drawable/assets/shaders/shader_unlit_gamma.hlsl.sks
Normal file
BIN
src/nodes/drawable/assets/shaders/shader_unlit_gamma.hlsl.sks
Normal file
Binary file not shown.
BIN
src/nodes/drawable/assets/shaders/shader_unlit_simula.hlsl.sks
Normal file
BIN
src/nodes/drawable/assets/shaders/shader_unlit_simula.hlsl.sks
Normal file
Binary file not shown.
@@ -1,126 +1,89 @@
|
||||
use super::{Line, LinesAspect};
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
nodes::{
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Message, Node,
|
||||
},
|
||||
core::{client::Client, error::Result, registry::Registry},
|
||||
nodes::{Node, spatial::Spatial},
|
||||
};
|
||||
use color_eyre::eyre::{bail, ensure, Result};
|
||||
use glam::Vec3A;
|
||||
use mint::Vector3;
|
||||
use glam::{FloatExt, Vec3};
|
||||
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;
|
||||
use stereokit_rust::{
|
||||
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
|
||||
};
|
||||
|
||||
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>,
|
||||
data: Mutex<Vec<Line>>,
|
||||
}
|
||||
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
|
||||
});
|
||||
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
|
||||
let _ = node
|
||||
.get_aspect::<Spatial>()
|
||||
.unwrap()
|
||||
.bounding_box_calc
|
||||
.set(|node| {
|
||||
let mut bounds = Bounds::default();
|
||||
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||
for line in &*lines.data.lock() {
|
||||
for point in &line.points {
|
||||
bounds.grown_point(Vec3::from(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 }),
|
||||
space: node.get_aspect::<Spatial>()?.clone(),
|
||||
data: Mutex::new(lines),
|
||||
});
|
||||
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()));
|
||||
node.add_aspect_raw(lines.clone());
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
|
||||
fn draw(&self, token: &MainThreadToken) {
|
||||
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);
|
||||
for line in &data {
|
||||
let mut points: VecDeque<SkLinePoint> = line
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| SkLinePoint {
|
||||
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
|
||||
thickness: p.thickness,
|
||||
color: Color128::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
|
||||
})
|
||||
.collect();
|
||||
if line.cyclic && !points.is_empty() {
|
||||
let first = line.points.first().unwrap();
|
||||
let last = line.points.last().unwrap();
|
||||
|
||||
let color = Color128 {
|
||||
r: first.color.c.r.lerp(last.color.c.r, 0.5),
|
||||
g: first.color.c.g.lerp(last.color.c.g, 0.5),
|
||||
b: first.color.c.b.lerp(last.color.c.b, 0.5),
|
||||
a: first.color.a.lerp(last.color.a, 0.5),
|
||||
};
|
||||
let connect_point = SkLinePoint {
|
||||
pt: transform_mat
|
||||
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
|
||||
.into(),
|
||||
thickness: (first.thickness + last.thickness) * 0.5,
|
||||
color: color.into(),
|
||||
};
|
||||
points.push_front(connect_point);
|
||||
points.push_back(connect_point);
|
||||
}
|
||||
stereokit_rust::system::Lines::add_list(token, points.make_contiguous());
|
||||
}
|
||||
draw_ctx.line_add_listv(points.make_contiguous());
|
||||
}
|
||||
|
||||
pub fn set_points_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
let mut points: Vec<LinePointRaw> = deserialize(message.as_ref())?;
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
lines.data.lock().cyclic = deserialize(message.as_ref())?;
|
||||
}
|
||||
impl LinesAspect for Lines {
|
||||
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
||||
let lines_aspect = node.get_aspect::<Lines>()?;
|
||||
*lines_aspect.data.lock() = lines;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -130,36 +93,12 @@ impl Drop for Lines {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
|
||||
pub fn draw_all(token: &MainThreadToken) {
|
||||
for lines in LINES_REGISTRY.get_valid_contents() {
|
||||
if lines.enabled.load(Ordering::Relaxed) {
|
||||
lines.draw(draw_ctx);
|
||||
if let Some(node) = lines.space.node() {
|
||||
if node.enabled() {
|
||||
lines.draw(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> 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(message.as_ref())?;
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -3,74 +3,164 @@ pub mod model;
|
||||
pub mod shaders;
|
||||
pub mod text;
|
||||
|
||||
use self::{
|
||||
lines::Lines,
|
||||
model::{Model, ModelPart},
|
||||
text::Text,
|
||||
use self::{lines::Lines, model::Model, text::Text};
|
||||
use super::{
|
||||
Aspect, AspectIdentifier, Node,
|
||||
spatial::{Spatial, Transform},
|
||||
};
|
||||
|
||||
use super::{Message, Node};
|
||||
use crate::core::client::Client;
|
||||
use color_eyre::eyre::Result;
|
||||
use crate::{DEFAULT_SKYLIGHT, nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO};
|
||||
use crate::{
|
||||
DEFAULT_SKYTEX,
|
||||
core::{client::Client, error::Result, resource::get_resource_file},
|
||||
};
|
||||
use color_eyre::eyre::eyre;
|
||||
use model::ModelPart;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use stereokit::StereoKitDraw;
|
||||
use tracing::instrument;
|
||||
use stardust_xr::values::ResourceID;
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "drawable", false);
|
||||
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 enum Drawable {
|
||||
Lines(Arc<Lines>),
|
||||
Model(Arc<Model>),
|
||||
ModelPart(Arc<ModelPart>),
|
||||
Text(Arc<Text>),
|
||||
}
|
||||
|
||||
#[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) = QUEUED_SKYTEX.lock().take() {
|
||||
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
|
||||
sk.render_set_skytex(&skytex);
|
||||
// #[instrument(level = "debug", skip(sk))]
|
||||
pub fn draw(token: &MainThreadToken) {
|
||||
lines::draw_all(token);
|
||||
model::draw_all(token);
|
||||
text::draw_all(token);
|
||||
match QUEUED_SKYTEX.lock().take() {
|
||||
Some(Some(skytex)) => {
|
||||
if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) {
|
||||
Renderer::skytex(skytex.tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
||||
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
|
||||
sk.render_set_skylight(skylight);
|
||||
Some(None) => {
|
||||
Renderer::skytex(DEFAULT_SKYTEX.get().unwrap());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
match QUEUED_SKYLIGHT.lock().take() {
|
||||
Some(Some(skylight)) => {
|
||||
if let Ok(skylight) = SHCubemap::from_cubemap(skylight, true, 100) {
|
||||
Renderer::skylight(skylight.sh);
|
||||
}
|
||||
}
|
||||
Some(None) => {
|
||||
Renderer::skylight(*DEFAULT_SKYLIGHT.get().unwrap());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
static QUEUED_SKYLIGHT: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
|
||||
static QUEUED_SKYTEX: Mutex<Option<Option<PathBuf>>> = Mutex::new(None);
|
||||
|
||||
fn set_sky_file_flex(_node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct SkyFileInfo {
|
||||
path: PathBuf,
|
||||
skytex: Option<bool>,
|
||||
skylight: Option<bool>,
|
||||
}
|
||||
let info: SkyFileInfo = deserialize(message.as_ref())?;
|
||||
info.path.metadata()?;
|
||||
if info.skytex.unwrap_or_default() {
|
||||
QUEUED_SKYTEX.lock().replace(info.path.clone());
|
||||
}
|
||||
if info.skylight.unwrap_or_default() {
|
||||
QUEUED_SKYLIGHT.lock().replace(info.path);
|
||||
}
|
||||
stardust_xr_server_codegen::codegen_drawable_protocol!();
|
||||
|
||||
Ok(())
|
||||
impl AspectIdentifier for Lines {
|
||||
impl_aspect_for_lines_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Lines {
|
||||
impl_aspect_for_lines_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for Model {
|
||||
impl_aspect_for_model_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Model {
|
||||
impl_aspect_for_model_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for ModelPart {
|
||||
impl_aspect_for_model_part_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ModelPart {
|
||||
impl_aspect_for_model_part_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for Text {
|
||||
impl_aspect_for_text_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Text {
|
||||
impl_aspect_for_text_aspect! {}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
fn set_sky_tex(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
tex: Option<ResourceID>,
|
||||
) -> Result<()> {
|
||||
let resource_path = tex
|
||||
.map(|tex| {
|
||||
get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
|
||||
.ok_or(eyre!("Could not find resource"))
|
||||
})
|
||||
.transpose()?;
|
||||
QUEUED_SKYTEX.lock().replace(resource_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_sky_light(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
light: Option<ResourceID>,
|
||||
) -> Result<()> {
|
||||
let resource_path = light
|
||||
.map(|light| {
|
||||
get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
|
||||
.ok_or(eyre!("Could not find resource"))
|
||||
})
|
||||
.transpose()?;
|
||||
QUEUED_SKYLIGHT.lock().replace(resource_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_lines(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
lines: Vec<Line>,
|
||||
) -> Result<()> {
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Lines::add_to(&node, lines)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_model(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
model: ResourceID,
|
||||
) -> Result<()> {
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Model::add_to(&node, model)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_text(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
text: String,
|
||||
style: TextStyle,
|
||||
) -> Result<()> {
|
||||
let node = Node::from_id(&calling_client, id, true);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Text::add_to(&node, text, style)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,142 @@
|
||||
use super::Node;
|
||||
use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect};
|
||||
use crate::bail;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::error::Result;
|
||||
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 crate::nodes::Message;
|
||||
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 crate::core::resource::get_resource_file;
|
||||
use crate::nodes::Node;
|
||||
use crate::nodes::alias::{Alias, AliasList};
|
||||
use crate::nodes::spatial::Spatial;
|
||||
use color_eyre::eyre::eyre;
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use parking_lot::Mutex;
|
||||
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 stardust_xr::values::ResourceID;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Weak};
|
||||
use stereokit::named_colors::WHITE;
|
||||
use stereokit::{
|
||||
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
|
||||
StereoKitMultiThread,
|
||||
};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, LazyLock, OnceLock, Weak};
|
||||
use stereokit_rust::material::Transparency;
|
||||
use stereokit_rust::maths::Bounds;
|
||||
use stereokit_rust::sk::MainThreadToken;
|
||||
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
|
||||
|
||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum MaterialParameter {
|
||||
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),
|
||||
pub struct MaterialWrapper(pub Material);
|
||||
impl Drop for MaterialWrapper {
|
||||
fn drop(&mut self) {
|
||||
MATERIAL_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for MaterialWrapper {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.get_shader().0.as_ptr().hash(state);
|
||||
for param in self.0.get_all_param_info() {
|
||||
param.name.hash(state);
|
||||
param.to_string().hash(state);
|
||||
}
|
||||
self.0.get_chain().map(MaterialWrapper).hash(state)
|
||||
}
|
||||
}
|
||||
impl PartialEq for MaterialWrapper {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.0.get_shader().0.as_ptr() != other.0.get_shader().0.as_ptr() {
|
||||
return false;
|
||||
}
|
||||
if self.0.get_all_param_info().count() != other.0.get_all_param_info().count() {
|
||||
return false;
|
||||
}
|
||||
for self_param in self.0.get_all_param_info() {
|
||||
let Some(other_param) = other
|
||||
.0
|
||||
.get_all_param_info()
|
||||
.get_data(self_param.get_name(), self_param.get_type())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if self_param.to_string() != other_param.to_string() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.0.get_chain().map(MaterialWrapper) == other.0.get_chain().map(MaterialWrapper)
|
||||
}
|
||||
}
|
||||
impl Eq for MaterialWrapper {}
|
||||
unsafe impl Send for MaterialWrapper {}
|
||||
unsafe impl Sync for MaterialWrapper {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MaterialRegistry(Mutex<FxHashMap<u64, Weak<MaterialWrapper>>>);
|
||||
impl MaterialRegistry {
|
||||
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
|
||||
let hash = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
material.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
let mut lock = self.0.lock();
|
||||
if let Some(mat) = lock.get(&hash) {
|
||||
if let Some(mat) = mat.upgrade() {
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
|
||||
lock.insert(hash, Arc::downgrade(&material));
|
||||
material
|
||||
}
|
||||
fn remove(&self, material: &MaterialWrapper) {
|
||||
let hash = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
material.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
let mut lock = self.0.lock();
|
||||
lock.remove(&hash);
|
||||
}
|
||||
}
|
||||
|
||||
static MATERIAL_REGISTRY: LazyLock<MaterialRegistry> = LazyLock::new(MaterialRegistry::default);
|
||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||
static HOLDOUT_MATERIAL: OnceLock<Arc<MaterialWrapper>> = OnceLock::new();
|
||||
|
||||
impl MaterialParameter {
|
||||
fn apply_to_material(
|
||||
&self,
|
||||
client: &Client,
|
||||
sk: &impl StereoKitMultiThread,
|
||||
material: &Material,
|
||||
parameter_name: &str,
|
||||
) {
|
||||
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
|
||||
let mut params = material.get_all_param_info();
|
||||
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::Bool(val) => {
|
||||
params.set_bool(parameter_name, *val);
|
||||
}
|
||||
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);
|
||||
params.set_int(parameter_name, &[*val]);
|
||||
}
|
||||
MaterialParameter::UInt(val) => {
|
||||
sk.material_set_uint(material, parameter_name, *val);
|
||||
params.set_uint(parameter_name, &[*val]);
|
||||
}
|
||||
MaterialParameter::UInt2(val) => {
|
||||
sk.material_set_uint2(material, parameter_name, val.x, val.y);
|
||||
MaterialParameter::Float(val) => {
|
||||
params.set_float(parameter_name, *val);
|
||||
}
|
||||
MaterialParameter::UInt3(val) => {
|
||||
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z);
|
||||
MaterialParameter::Vec2(val) => {
|
||||
params.set_vec2(parameter_name, Vec2::from(*val));
|
||||
}
|
||||
MaterialParameter::UInt4(val) => {
|
||||
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z);
|
||||
MaterialParameter::Vec3(val) => {
|
||||
params.set_vec3(parameter_name, Vec3::from(*val));
|
||||
}
|
||||
MaterialParameter::Matrix(val) => {
|
||||
sk.material_set_matrix(material, parameter_name, Mat4::from(*val));
|
||||
MaterialParameter::Color(val) => {
|
||||
params.set_color(
|
||||
parameter_name,
|
||||
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
|
||||
);
|
||||
}
|
||||
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);
|
||||
let Some(texture_path) =
|
||||
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Ok(tex) = Tex::from_file(texture_path, true, None) {
|
||||
params.set_texture(parameter_name, &tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,245 +145,276 @@ impl MaterialParameter {
|
||||
|
||||
pub struct ModelPart {
|
||||
id: i32,
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
space: Arc<Spatial>,
|
||||
model: Weak<Model>,
|
||||
material: Mutex<Option<Arc<MaterialWrapper>>>,
|
||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
||||
pending_material_replacement: Mutex<Option<Arc<SendWrapper<Material>>>>,
|
||||
pending_material_replacement: Mutex<Option<Arc<MaterialWrapper>>>,
|
||||
aliases: AliasList,
|
||||
}
|
||||
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);
|
||||
fn create_for_model(model: &Arc<Model>, sk_model: &SKModel) {
|
||||
HOLDOUT_MATERIAL.get_or_init(|| {
|
||||
let mut mat = Material::copy(&Material::unlit());
|
||||
mat.transparency(Transparency::None);
|
||||
mat.color_tint(Color128::BLACK_TRANSPARENT);
|
||||
Arc::new(MaterialWrapper(mat))
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
let nodes = sk_model.get_nodes();
|
||||
for part in nodes.all() {
|
||||
ModelPart::create(model, &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,
|
||||
});
|
||||
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
|
||||
let mut parts = model.parts.lock();
|
||||
let parent_part = part
|
||||
.get_parent()
|
||||
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
|
||||
|
||||
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())
|
||||
let mut part_path = parent_part
|
||||
.map(|n| n.path.clone() + "/")
|
||||
.unwrap_or_default();
|
||||
part_path += part.get_name().unwrap();
|
||||
|
||||
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||
let spatial_parent = parent_part
|
||||
.map(|n| n.space.clone())
|
||||
.unwrap_or_else(|| model.space.clone());
|
||||
|
||||
let local_transform = unsafe { part.get_local_transform().m };
|
||||
let space = Spatial::add_to(
|
||||
&node,
|
||||
Some(spatial_parent),
|
||||
sk.model_node_get_transform_local(sk_model, id),
|
||||
Mat4::from_cols_array(&local_transform),
|
||||
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 _ = space.bounding_box_calc.set(|node| {
|
||||
let Ok(model_part) = node.get_aspect::<ModelPart>() 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 model_nodes = sk_model.get_nodes();
|
||||
let Some(model_node) = model_nodes.get_index(model_part.id) else {
|
||||
return Bounds::default();
|
||||
};
|
||||
let Some(sk_mesh) = model_node.get_mesh() else {
|
||||
return Bounds::default();
|
||||
};
|
||||
sk_mesh.get_bounds()
|
||||
});
|
||||
|
||||
let model_part = Arc::new(ModelPart {
|
||||
id,
|
||||
id: *part.get_id(),
|
||||
path: part_path,
|
||||
space,
|
||||
model: Arc::downgrade(model),
|
||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||
pending_material_replacement: Mutex::new(None),
|
||||
aliases: AliasList::default(),
|
||||
material: Mutex::new(part.get_material().map(MaterialWrapper).map(Arc::new)),
|
||||
});
|
||||
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);
|
||||
node.add_aspect_raw(model_part.clone());
|
||||
parts.push(model_part.clone());
|
||||
Some(model_part)
|
||||
}
|
||||
|
||||
fn set_material_parameter_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
let (name, value): (String, MaterialParameter) = deserialize(message.as_ref())?;
|
||||
|
||||
model_part
|
||||
.pending_material_parameters
|
||||
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
|
||||
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
|
||||
self.pending_material_replacement
|
||||
.lock()
|
||||
.insert(name, value);
|
||||
.replace(shared_material);
|
||||
}
|
||||
/// only to be run on the main thread
|
||||
pub fn replace_material_now(&self, replacement: &Material) {
|
||||
let Some(model) = self.model.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(sk_model) = model.sk_model.get() else {
|
||||
return;
|
||||
};
|
||||
let nodes = sk_model.get_nodes();
|
||||
let Some(mut part) = nodes.get_index(self.id) else {
|
||||
return;
|
||||
};
|
||||
let shared_material =
|
||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
|
||||
|
||||
let mut lock = self.material.lock();
|
||||
part.material(&shared_material.0);
|
||||
lock.replace(shared_material);
|
||||
}
|
||||
|
||||
fn update(&self) {
|
||||
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 nodes = sk_model.get_nodes();
|
||||
let Some(mut part) = nodes.get_index(self.id) else {
|
||||
return;
|
||||
};
|
||||
part.model_transform(Spatial::space_to_space_matrix(
|
||||
Some(&self.space),
|
||||
Some(&model.space),
|
||||
));
|
||||
|
||||
let Some(client) = node.get_client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
||||
let mut lock = self.material.lock();
|
||||
part.material(&material_replacement.0);
|
||||
lock.replace(material_replacement);
|
||||
}
|
||||
|
||||
'mat_params: {
|
||||
let mut material_parameters = self.pending_material_parameters.lock();
|
||||
if !material_parameters.is_empty() {
|
||||
let Some(material) = part.get_material() else {
|
||||
break 'mat_params;
|
||||
};
|
||||
let new_material = material.copy();
|
||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||
parameter_value.apply_to_material(&client, &new_material, ¶meter_name);
|
||||
}
|
||||
|
||||
let shared_material =
|
||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(new_material)));
|
||||
let mut lock = self.material.lock();
|
||||
part.material(&shared_material.0);
|
||||
lock.replace(shared_material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ModelPartAspect for ModelPart {
|
||||
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
|
||||
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let model_part = node.get_aspect::<ModelPart>()?;
|
||||
model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
|
||||
self.pending_material_replacement
|
||||
#[doc = "Set the material parameter with `parameter_name` to `value`"]
|
||||
fn set_material_parameter(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
parameter_name: String,
|
||||
value: MaterialParameter,
|
||||
) -> Result<()> {
|
||||
let model_part = node.get_aspect::<ModelPart>()?;
|
||||
model_part
|
||||
.pending_material_parameters
|
||||
.lock()
|
||||
.replace(replacement);
|
||||
}
|
||||
.insert(parameter_name, value);
|
||||
|
||||
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)),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
self_ref: Weak<Model>,
|
||||
enabled: Arc<AtomicBool>,
|
||||
space: Arc<Spatial>,
|
||||
_resource_id: ResourceID,
|
||||
sk_model: OnceCell<SKModel>,
|
||||
parts: LifeLinkedNodeMap<i32>,
|
||||
sk_model: OnceLock<SKModel>,
|
||||
parts: Mutex<Vec<Arc<ModelPart>>>,
|
||||
}
|
||||
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>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
ensure!(
|
||||
node.drawable.get().is_none(),
|
||||
"Internal: Node already has a drawable attached!"
|
||||
);
|
||||
let pending_model_path = get_resource_file(
|
||||
&resource_id,
|
||||
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||
&[OsStr::new("glb"), OsStr::new("gltf")],
|
||||
)
|
||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||
|
||||
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(),
|
||||
let model = Arc::new(Model {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
_resource_id: resource_id,
|
||||
sk_model: OnceCell::new(),
|
||||
parts: LifeLinkedNodeMap::default(),
|
||||
sk_model: OnceLock::new(),
|
||||
parts: Mutex::new(Vec::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>)?,
|
||||
);
|
||||
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
|
||||
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
|
||||
let sk_model = SKModel::copy(SKModel::from_file(
|
||||
pending_model_path.to_str().unwrap(),
|
||||
None,
|
||||
)?);
|
||||
ModelPart::create_for_model(&model, &sk_model);
|
||||
let _ = model.sk_model.set(sk_model);
|
||||
let _ = node.drawable.set(Drawable::Model(model.clone()));
|
||||
node.add_aspect_raw(model.clone());
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
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);
|
||||
fn draw(&self, token: &MainThreadToken) {
|
||||
let Some(sk_model) = self.sk_model.get() else {
|
||||
return;
|
||||
};
|
||||
let parts = self.parts.lock();
|
||||
for model_node in &*parts {
|
||||
model_node.update();
|
||||
}
|
||||
drop(parts);
|
||||
|
||||
sk.model_draw(
|
||||
sk_model,
|
||||
self.space.global_transform(),
|
||||
WHITE,
|
||||
RenderLayer::LAYER0,
|
||||
);
|
||||
if let Some(node) = self.space.node() {
|
||||
if node.enabled() {
|
||||
sk_model.draw(token, self.space.global_transform(), None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
|
||||
unsafe impl Send for Model {}
|
||||
unsafe impl Sync for Model {}
|
||||
impl ModelAspect for Model {
|
||||
#[doc = "Bind a model part to the node with the ID input."]
|
||||
fn bind_model_part(
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
part_path: String,
|
||||
) -> Result<()> {
|
||||
let model = node.get_aspect::<Model>()?;
|
||||
let parts = model.parts.lock();
|
||||
let Some(part) = parts.iter().find(|p| p.path == part_path) else {
|
||||
let paths = parts.iter().map(|p| &p.path).collect::<Vec<_>>();
|
||||
bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",);
|
||||
};
|
||||
Alias::create_with_id(
|
||||
&part.space.node().unwrap(),
|
||||
&calling_client,
|
||||
id,
|
||||
MODEL_PART_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&part.aliases),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Model {
|
||||
fn drop(&mut self) {
|
||||
MODEL_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(sk: &impl StereoKitDraw) {
|
||||
pub fn draw_all(token: &MainThreadToken) {
|
||||
for model in MODEL_REGISTRY.get_valid_contents() {
|
||||
if model.enabled.load(Ordering::Relaxed) {
|
||||
model.draw(sk);
|
||||
}
|
||||
model.draw(token);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateModelInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
resource: ResourceID,
|
||||
}
|
||||
let info: CreateModelInfo = deserialize(message.as_ref())?;
|
||||
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, false)?;
|
||||
Model::add_to(&node, info.resource)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,139 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_gamma.hlsl.sks");
|
||||
|
||||
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(())
|
||||
}
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");
|
||||
|
||||
@@ -1,172 +1,152 @@
|
||||
use crate::{
|
||||
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
|
||||
nodes::{
|
||||
drawable::Drawable,
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Message, Node,
|
||||
core::{
|
||||
client::Client, destroy_queue, error::Result, registry::Registry,
|
||||
resource::get_resource_file,
|
||||
},
|
||||
nodes::{Node, spatial::Spatial},
|
||||
};
|
||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||
use glam::{vec3, Mat4, Vec2};
|
||||
use mint::Vector2;
|
||||
use once_cell::sync::OnceCell;
|
||||
use color_eyre::eyre::eyre;
|
||||
use glam::{Mat4, Vec2, vec3};
|
||||
use parking_lot::Mutex;
|
||||
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::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::PathBuf,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use stereokit_rust::{
|
||||
font::Font,
|
||||
sk::MainThreadToken,
|
||||
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
|
||||
util::{Color32, Color128},
|
||||
};
|
||||
|
||||
use super::{TextAspect, TextStyle};
|
||||
|
||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||
|
||||
struct TextData {
|
||||
text: String,
|
||||
character_height: f32,
|
||||
text_align: TextAlign,
|
||||
bounds: Option<Vec2>,
|
||||
fit: TextFit,
|
||||
bounds_align: TextAlign,
|
||||
color: Rgba<f32>,
|
||||
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
|
||||
match (x_align, y_align) {
|
||||
(super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
|
||||
(super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
|
||||
(super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
|
||||
(super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
|
||||
(super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
|
||||
(super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter,
|
||||
(super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight,
|
||||
(super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight,
|
||||
(super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
enabled: Arc<AtomicBool>,
|
||||
space: Arc<Spatial>,
|
||||
font_path: Option<PathBuf>,
|
||||
style: OnceCell<SendWrapper<TextStyle>>,
|
||||
style: OnceLock<SkTextStyle>,
|
||||
|
||||
data: Mutex<TextData>,
|
||||
text: Mutex<String>,
|
||||
data: Mutex<TextStyle>,
|
||||
}
|
||||
impl Text {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
font_resource_id: Option<ResourceID>,
|
||||
text: String,
|
||||
character_height: f32,
|
||||
text_align: TextAlign,
|
||||
bounds: Option<Vector2<f32>>,
|
||||
fit: TextFit,
|
||||
bounds_align: TextAlign,
|
||||
color: Rgba<f32>,
|
||||
) -> Result<Arc<Text>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
ensure!(
|
||||
node.drawable.get().is_none(),
|
||||
"Internal: Node already has a drawable attached!"
|
||||
);
|
||||
|
||||
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
||||
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
|
||||
let 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(),
|
||||
&[OsStr::new("ttf"), OsStr::new("otf")],
|
||||
)
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
font_path: style.font.as_ref().and_then(|res| {
|
||||
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
||||
}),
|
||||
style: OnceCell::new(),
|
||||
style: OnceLock::new(),
|
||||
|
||||
data: Mutex::new(TextData {
|
||||
text,
|
||||
character_height,
|
||||
text_align,
|
||||
bounds: bounds.map(|b| b.into()),
|
||||
fit,
|
||||
bounds_align,
|
||||
color,
|
||||
}),
|
||||
text: Mutex::new(text),
|
||||
data: Mutex::new(style),
|
||||
});
|
||||
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()));
|
||||
node.add_aspect_raw(text.clone());
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
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)
|
||||
}))
|
||||
},
|
||||
);
|
||||
fn draw(&self, token: &MainThreadToken) {
|
||||
let style = self.style.get_or_init(|| {
|
||||
let font = self
|
||||
.font_path
|
||||
.as_deref()
|
||||
.and_then(|path| Font::from_file(path).ok())
|
||||
.unwrap_or_default();
|
||||
SkTextStyle::from_font(font, 1.0, Color32::WHITE)
|
||||
});
|
||||
|
||||
if let Ok(style) = style {
|
||||
let data = self.data.lock();
|
||||
let transform = self.space.global_transform()
|
||||
* Mat4::from_scale(vec3(
|
||||
data.character_height,
|
||||
data.character_height,
|
||||
data.character_height,
|
||||
));
|
||||
if let Some(bounds) = data.bounds {
|
||||
sk.text_add_in(
|
||||
&data.text,
|
||||
transform,
|
||||
bounds / data.character_height,
|
||||
data.fit,
|
||||
**style,
|
||||
data.bounds_align,
|
||||
data.text_align,
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
Color128::from([
|
||||
data.color.red(),
|
||||
data.color.green(),
|
||||
data.color.blue(),
|
||||
data.color.alpha(),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
sk.text_add_at(
|
||||
&data.text,
|
||||
transform,
|
||||
**style,
|
||||
data.bounds_align,
|
||||
data.text_align,
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
Color128::from([
|
||||
data.color.red(),
|
||||
data.color.green(),
|
||||
data.color.blue(),
|
||||
data.color.alpha(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
let text = self.text.lock();
|
||||
let data = self.data.lock();
|
||||
let transform = self.space.global_transform()
|
||||
* Mat4::from_scale(vec3(
|
||||
data.character_height,
|
||||
data.character_height,
|
||||
data.character_height,
|
||||
));
|
||||
if let Some(bounds) = &data.bounds {
|
||||
stereokit_rust::system::Text::add_in(
|
||||
token,
|
||||
&*text,
|
||||
transform,
|
||||
Vec2::from(bounds.bounds) / data.character_height,
|
||||
match bounds.fit {
|
||||
super::TextFit::Wrap => TextFit::Wrap,
|
||||
super::TextFit::Clip => TextFit::Clip,
|
||||
super::TextFit::Squeeze => TextFit::Squeeze,
|
||||
super::TextFit::Exact => TextFit::Exact,
|
||||
super::TextFit::Overflow => TextFit::Overflow,
|
||||
},
|
||||
Some(*style),
|
||||
Some(Color128::new(
|
||||
data.color.c.r,
|
||||
data.color.c.g,
|
||||
data.color.c.b,
|
||||
data.color.a,
|
||||
)),
|
||||
data.bounds
|
||||
.as_ref()
|
||||
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
|
||||
Some(convert_align(data.text_align_x, data.text_align_y)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
stereokit_rust::system::Text::add_at(
|
||||
token,
|
||||
&*text,
|
||||
transform,
|
||||
Some(*style),
|
||||
Some(Color128::new(
|
||||
data.color.c.r,
|
||||
data.color.c.g,
|
||||
data.color.c.b,
|
||||
data.color.a,
|
||||
)),
|
||||
data.bounds
|
||||
.as_ref()
|
||||
.map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)),
|
||||
Some(convert_align(data.text_align_x, data.text_align_y)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_character_height_flex(
|
||||
node: &Node,
|
||||
}
|
||||
impl TextAspect for Text {
|
||||
fn set_character_height(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
height: f32,
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
text.data.lock().character_height = deserialize(message.as_ref())?;
|
||||
let this_text = node.get_aspect::<Text>()?;
|
||||
this_text.data.lock().character_height = height;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_text_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
||||
|
||||
text.data.lock().text = deserialize(message.as_ref())?;
|
||||
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
||||
let this_text = node.get_aspect::<Text>()?;
|
||||
*this_text.text.lock() = text;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -179,47 +159,12 @@ impl Drop for Text {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_all(sk: &impl StereoKitDraw) {
|
||||
pub fn draw_all(token: &MainThreadToken) {
|
||||
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||
if text.enabled.load(Ordering::Relaxed) {
|
||||
text.draw(sk);
|
||||
if let Some(node) = text.space.node() {
|
||||
if node.enabled() {
|
||||
text.draw(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateTextInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
text: String,
|
||||
font_resource: Option<ResourceID>,
|
||||
character_height: f32,
|
||||
text_align: TextAlign,
|
||||
bounds: Option<Vector2<f32>>,
|
||||
fit: TextFit,
|
||||
bounds_align: TextAlign,
|
||||
color: [f32; 4],
|
||||
}
|
||||
let info: CreateTextInfo = deserialize(message.as_ref())?;
|
||||
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 color = Rgba::from_slice(&info.color);
|
||||
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
Text::add_to(
|
||||
&node,
|
||||
info.font_resource,
|
||||
info.text,
|
||||
info.character_height,
|
||||
info.text_align,
|
||||
info.bounds,
|
||||
info.fit,
|
||||
info.bounds_align,
|
||||
color,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
295
src/nodes/fields.rs
Normal file
295
src/nodes/fields.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
use super::alias::{Alias, AliasInfo};
|
||||
use super::spatial::{
|
||||
SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
||||
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
||||
Spatial,
|
||||
};
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::Transform;
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use glam::{Vec3, Vec3A, Vec3Swizzles, vec2, vec3, vec3a};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use stardust_xr::values::Vector3;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
// TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against
|
||||
|
||||
pub static FIELD_ALIAS_INFO: LazyLock<AliasInfo> = LazyLock::new(|| AliasInfo {
|
||||
server_methods: vec![
|
||||
SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
||||
SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
||||
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE,
|
||||
FIELD_REF_DISTANCE_SERVER_OPCODE,
|
||||
FIELD_REF_NORMAL_SERVER_OPCODE,
|
||||
FIELD_REF_CLOSEST_POINT_SERVER_OPCODE,
|
||||
FIELD_REF_RAY_MARCH_SERVER_OPCODE,
|
||||
],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
stardust_xr_server_codegen::codegen_field_protocol!();
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EXPORTED_FIELDS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
|
||||
}
|
||||
|
||||
pub trait FieldTrait: Send + Sync + 'static {
|
||||
fn spatial_ref(&self) -> &Spatial;
|
||||
|
||||
fn local_distance(&self, p: Vec3A) -> f32;
|
||||
fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
|
||||
let d = self.local_distance(p);
|
||||
let e = vec2(r, 0_f32);
|
||||
|
||||
let n = vec3a(d, d, d)
|
||||
- vec3a(
|
||||
self.local_distance(vec3a(e.x, e.y, e.y)),
|
||||
self.local_distance(vec3a(e.y, e.x, e.y)),
|
||||
self.local_distance(vec3a(e.y, e.y, e.x)),
|
||||
);
|
||||
|
||||
n.normalize()
|
||||
}
|
||||
fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
|
||||
p - (self.local_normal(p, r) * self.local_distance(p))
|
||||
}
|
||||
|
||||
fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
self.local_distance(local_p)
|
||||
}
|
||||
fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
reference_to_local_space
|
||||
.inverse()
|
||||
.transform_vector3a(self.local_normal(local_p, r))
|
||||
}
|
||||
fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
reference_to_local_space
|
||||
.inverse()
|
||||
.transform_point3a(self.local_closest_point(local_p, r))
|
||||
}
|
||||
|
||||
fn ray_march(&self, ray: Ray) -> RayMarchResult {
|
||||
let mut result = RayMarchResult {
|
||||
ray_origin: ray.origin.into(),
|
||||
ray_direction: ray.direction.into(),
|
||||
min_distance: f32::MAX,
|
||||
deepest_point_distance: 0_f32,
|
||||
ray_length: 0_f32,
|
||||
ray_steps: 0,
|
||||
};
|
||||
|
||||
let ray_to_field_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
|
||||
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
|
||||
let ray_direction = ray_to_field_matrix
|
||||
.transform_vector3a(ray.direction.into())
|
||||
.normalize();
|
||||
|
||||
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
|
||||
let distance = self.local_distance(ray_point);
|
||||
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
|
||||
|
||||
result.ray_length += march_distance;
|
||||
ray_point += ray_direction * march_distance;
|
||||
|
||||
if result.min_distance > distance {
|
||||
result.deepest_point_distance = result.ray_length;
|
||||
result.min_distance = distance;
|
||||
}
|
||||
|
||||
result.ray_steps += 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub space: Arc<Spatial>,
|
||||
}
|
||||
|
||||
// const MIN_RAY_STEPS: u32 = 0;
|
||||
const MAX_RAY_STEPS: u32 = 1000;
|
||||
|
||||
const MIN_RAY_MARCH: f32 = 0.001_f32;
|
||||
const MAX_RAY_MARCH: f32 = f32::MAX;
|
||||
|
||||
// const MIN_RAY_LENGTH: f32 = 0_f32;
|
||||
const MAX_RAY_LENGTH: f32 = 1000_f32;
|
||||
|
||||
pub struct Field {
|
||||
pub spatial: Arc<Spatial>,
|
||||
pub shape: Mutex<Shape>,
|
||||
}
|
||||
impl Field {
|
||||
pub fn add_to(node: &Arc<Node>, shape: Shape) -> Result<Arc<Field>> {
|
||||
let spatial = node.get_aspect::<Spatial>()?;
|
||||
let field = Field {
|
||||
spatial,
|
||||
shape: Mutex::new(shape),
|
||||
};
|
||||
let field = node.add_aspect(field);
|
||||
node.add_aspect(FieldRef);
|
||||
Ok(field)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Field {
|
||||
impl_aspect_for_field_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Field {
|
||||
impl_aspect_for_field_aspect! {}
|
||||
}
|
||||
impl FieldAspect for Field {
|
||||
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
*field.shape.lock() = shape;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||
let id = rand::random();
|
||||
EXPORTED_FIELDS.lock().insert(id, node);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
impl FieldTrait for Field {
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
&self.spatial
|
||||
}
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
match self.shape.lock().clone() {
|
||||
Shape::Box(size) => {
|
||||
let q = vec3(
|
||||
p.x.abs() - (size.x * 0.5_f32),
|
||||
p.y.abs() - (size.y * 0.5_f32),
|
||||
p.z.abs() - (size.z * 0.5_f32),
|
||||
);
|
||||
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||
}
|
||||
Shape::Cylinder(CylinderShape { length, radius }) => {
|
||||
let d = vec2(p.xz().length().abs() - radius, p.y.abs() - (length * 0.5));
|
||||
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||
}
|
||||
Shape::Sphere(radius) => p.length() - radius,
|
||||
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||
q.length() - radius_b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldRef;
|
||||
impl AspectIdentifier for FieldRef {
|
||||
impl_aspect_for_field_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for FieldRef {
|
||||
impl_aspect_for_field_ref_aspect! {}
|
||||
}
|
||||
impl FieldRefAspect for FieldRef {
|
||||
async fn distance(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
space: Arc<Node>,
|
||||
point: Vector3<f32>,
|
||||
) -> Result<f32> {
|
||||
let reference_space = space.get_aspect::<Spatial>()?;
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
Ok(field.distance(&reference_space, point.into()))
|
||||
}
|
||||
|
||||
async fn normal(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
space: Arc<Node>,
|
||||
point: Vector3<f32>,
|
||||
) -> Result<Vector3<f32>> {
|
||||
let reference_space = space.get_aspect::<Spatial>()?;
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
Ok(field.normal(&reference_space, point.into(), 0.0001).into())
|
||||
}
|
||||
|
||||
async fn closest_point(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
space: Arc<Node>,
|
||||
point: Vector3<f32>,
|
||||
) -> Result<Vector3<f32>> {
|
||||
let reference_space = space.get_aspect::<Spatial>()?;
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
Ok(field
|
||||
.closest_point(&reference_space, point.into(), 0.0001)
|
||||
.into())
|
||||
}
|
||||
|
||||
async fn ray_march(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
space: Arc<Node>,
|
||||
ray_origin: Vector3<f32>,
|
||||
ray_direction: Vector3<f32>,
|
||||
) -> Result<RayMarchResult> {
|
||||
let space = space.get_aspect::<Spatial>()?;
|
||||
let field = node.get_aspect::<Field>()?;
|
||||
Ok(field.ray_march(Ray {
|
||||
origin: ray_origin.into(),
|
||||
direction: ray_direction.into(),
|
||||
space,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
async fn import_field_ref(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
uid: u64,
|
||||
) -> Result<Arc<Node>> {
|
||||
Ok(EXPORTED_FIELDS
|
||||
.lock()
|
||||
.get(&uid)
|
||||
.map(|s| {
|
||||
Alias::create(
|
||||
s,
|
||||
&calling_client,
|
||||
FIELD_REF_ASPECT_ALIAS_INFO.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||
}
|
||||
|
||||
fn create_field(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
shape: Shape,
|
||||
) -> Result<()> {
|
||||
let transform = transform.to_mat4(true, true, false);
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Field::add_to(&node, shape)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::Message;
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{vec3, vec3a, Vec3, Vec3A};
|
||||
use mint::Vector3;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BoxField {
|
||||
space: Arc<Spatial>,
|
||||
size: Mutex<Vec3>,
|
||||
}
|
||||
|
||||
impl BoxField {
|
||||
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<Arc<Field>> {
|
||||
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 box_field = BoxField {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
size: Mutex::new(size.into()),
|
||||
};
|
||||
box_field.add_field_methods(node);
|
||||
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>) {
|
||||
*self.size.lock() = size.into();
|
||||
}
|
||||
|
||||
pub fn set_size_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
box_field.set_size(deserialize(message.as_ref())?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldTrait for BoxField {
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
let size = self.size.lock();
|
||||
let q = vec3(
|
||||
p.x.abs() - (size.x * 0.5_f32),
|
||||
p.y.abs() - (size.y * 0.5_f32),
|
||||
p.z.abs() - (size.z * 0.5_f32),
|
||||
);
|
||||
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||
}
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
self.space.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_box_field_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateFieldInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
size: Vector3<f32>,
|
||||
}
|
||||
let info: CreateFieldInfo = deserialize(message.as_ref())?;
|
||||
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)?;
|
||||
BoxField::add_to(&node, info.size)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::Message;
|
||||
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 CylinderField {
|
||||
space: Arc<Spatial>,
|
||||
length: AtomicF32,
|
||||
radius: AtomicF32,
|
||||
}
|
||||
|
||||
impl CylinderField {
|
||||
pub fn add_to(node: &Arc<Node>, length: f32, radius: 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 cylinder_field = CylinderField {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
length: AtomicF32::new(length.abs()),
|
||||
radius: AtomicF32::new(radius.abs()),
|
||||
};
|
||||
cylinder_field.add_field_methods(node);
|
||||
node.add_local_signal("set_size", CylinderField::set_size_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_size(&self, length: f32, radius: f32) {
|
||||
self.length.store(length.abs(), Ordering::Relaxed);
|
||||
self.radius.store(radius.abs(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_size_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
let (length, radius) = deserialize(message.as_ref())?;
|
||||
cylinder_field.set_size(length, radius);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldTrait for CylinderField {
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
let radius = self.radius.load(Ordering::Relaxed);
|
||||
let length = self.length.load(Ordering::Relaxed);
|
||||
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
||||
|
||||
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||
}
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
self.space.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cylinder_field_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateFieldInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
length: f32,
|
||||
radius: f32,
|
||||
}
|
||||
let info: CreateFieldInfo = deserialize(message.as_ref())?;
|
||||
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)?;
|
||||
CylinderField::add_to(&node, info.length, info.radius)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
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::{Message, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::find_reference_space;
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec2, vec3a, Vec3, Vec3A};
|
||||
use mint::Vector3;
|
||||
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);
|
||||
let e = vec2(r, 0_f32);
|
||||
|
||||
let n = vec3a(d, d, d)
|
||||
- vec3a(
|
||||
self.local_distance(vec3a(e.x, e.y, e.y)),
|
||||
self.local_distance(vec3a(e.y, e.x, e.y)),
|
||||
self.local_distance(vec3a(e.y, e.y, e.x)),
|
||||
);
|
||||
|
||||
n.normalize()
|
||||
}
|
||||
fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
|
||||
p - (self.local_normal(p, r) * self.local_distance(p))
|
||||
}
|
||||
|
||||
fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
self.local_distance(local_p)
|
||||
}
|
||||
fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
reference_to_local_space
|
||||
.inverse()
|
||||
.transform_vector3a(self.local_normal(local_p, r))
|
||||
}
|
||||
fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
|
||||
let reference_to_local_space =
|
||||
Spatial::space_to_space_matrix(Some(reference_space), Some(self.spatial_ref()));
|
||||
let local_p = reference_to_local_space.transform_point3a(p);
|
||||
reference_to_local_space
|
||||
.inverse()
|
||||
.transform_point3a(self.local_closest_point(local_p, r))
|
||||
}
|
||||
|
||||
fn ray_march(&self, ray: Ray) -> RayMarchResult {
|
||||
let mut result = RayMarchResult {
|
||||
min_distance: f32::MAX,
|
||||
deepest_point_distance: 0_f32,
|
||||
ray_length: 0_f32,
|
||||
ray_steps: 0,
|
||||
};
|
||||
|
||||
let ray_to_field_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
|
||||
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
|
||||
let ray_direction = ray_to_field_matrix.transform_vector3a(ray.direction.into());
|
||||
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
reference_space_path: &'a str,
|
||||
point: Vector3<f32>,
|
||||
}
|
||||
let args: FieldInfoArgs = deserialize(message.as_ref())?;
|
||||
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
|
||||
|
||||
let distance = node
|
||||
.field
|
||||
.get()
|
||||
.unwrap()
|
||||
.distance(reference_space.as_ref(), args.point.into());
|
||||
Ok(serialize(distance)?.into())
|
||||
}
|
||||
fn field_normal_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
reference_space_path: &'a str,
|
||||
point: Vector3<f32>,
|
||||
radius: Option<f32>,
|
||||
}
|
||||
let args: FieldInfoArgs = deserialize(message.as_ref())?;
|
||||
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
|
||||
|
||||
let normal = node.field.get().as_ref().unwrap().normal(
|
||||
reference_space.as_ref(),
|
||||
args.point.into(),
|
||||
args.radius.unwrap_or(0.001),
|
||||
);
|
||||
Ok(serialize(mint::Vector3::from(normal))?.into())
|
||||
}
|
||||
fn field_closest_point_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
reference_space_path: &'a str,
|
||||
point: Vector3<f32>,
|
||||
radius: Option<f32>,
|
||||
}
|
||||
let args: FieldInfoArgs = deserialize(message.as_ref())?;
|
||||
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
|
||||
|
||||
let closest_point = node.field.get().as_ref().unwrap().closest_point(
|
||||
reference_space.as_ref(),
|
||||
args.point.into(),
|
||||
args.radius.unwrap_or(0.001),
|
||||
);
|
||||
Ok(serialize(mint::Vector3::from(closest_point))?.into())
|
||||
}
|
||||
fn field_ray_march_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
#[derive(Deserialize)]
|
||||
struct FieldInfoArgs<'a> {
|
||||
reference_space_path: &'a str,
|
||||
ray_origin: Vector3<f32>,
|
||||
ray_direction: Vector3<f32>,
|
||||
}
|
||||
let args: FieldInfoArgs = deserialize(message.as_ref())?;
|
||||
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)?.into())
|
||||
}
|
||||
|
||||
pub enum Field {
|
||||
Box(BoxField),
|
||||
Cylinder(CylinderField),
|
||||
Sphere(SphereField),
|
||||
Torus(TorusField),
|
||||
}
|
||||
|
||||
impl Deref for Field {
|
||||
type Target = dyn FieldTrait;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Field::Box(field) => field,
|
||||
Field::Cylinder(field) => field,
|
||||
Field::Sphere(field) => field,
|
||||
Field::Torus(field) => field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "field", false);
|
||||
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>> {
|
||||
client
|
||||
.get_node("Field", path)?
|
||||
.get_aspect("Field", "info", |n| &n.field)
|
||||
.cloned()
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, Spatial};
|
||||
use crate::nodes::Message;
|
||||
use color_eyre::eyre::{ensure, Result};
|
||||
use glam::{Mat4, Vec3A};
|
||||
use mint::Vector3;
|
||||
use portable_atomic::AtomicF32;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct SphereField {
|
||||
space: Arc<Spatial>,
|
||||
radius: AtomicF32,
|
||||
}
|
||||
|
||||
impl SphereField {
|
||||
pub fn add_to(node: &Arc<Node>, radius: f32) -> 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 sphere_field = SphereField {
|
||||
space: node.spatial.get().unwrap().clone(),
|
||||
radius: AtomicF32::new(radius),
|
||||
};
|
||||
sphere_field.add_field_methods(node);
|
||||
node.add_local_signal("set_radius", SphereField::set_radius_flex);
|
||||
let _ = node.field.set(Arc::new(Field::Sphere(sphere_field)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_radius(&self, radius: f32) {
|
||||
self.radius.store(radius, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_radius_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
sphere_field.set_radius(deserialize(message.as_ref())?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldTrait for SphereField {
|
||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||
p.length() - self.radius.load(Ordering::Relaxed)
|
||||
}
|
||||
fn local_normal(&self, p: Vec3A, _r: f32) -> Vec3A {
|
||||
-p.normalize()
|
||||
}
|
||||
fn local_closest_point(&self, p: Vec3A, _r: f32) -> Vec3A {
|
||||
p.normalize() * self.radius.load(Ordering::Relaxed)
|
||||
}
|
||||
fn spatial_ref(&self) -> &Spatial {
|
||||
self.space.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_sphere_field_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateFieldInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
origin: Option<Vector3<f32>>,
|
||||
radius: f32,
|
||||
}
|
||||
let info: CreateFieldInfo = deserialize(message.as_ref())?;
|
||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = Mat4::from_translation(
|
||||
info.origin
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]))
|
||||
.into(),
|
||||
);
|
||||
let node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
SphereField::add_to(&node, info.radius)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use super::{Field, FieldTrait, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::Message;
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
||||
let (radius_a, radius_b) = deserialize(message.as_ref())?;
|
||||
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>,
|
||||
message: Message,
|
||||
) -> 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(message.as_ref())?;
|
||||
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(())
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use super::{alias::Alias, spatial::Spatial, Node};
|
||||
use crate::{
|
||||
core::client::{Client, INTERNAL_CLIENT},
|
||||
nodes::alias::AliasInfo,
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3, Mat4};
|
||||
use std::sync::Arc;
|
||||
use stereokit::StereoKitMultiThread;
|
||||
use tracing::instrument;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HMD: Arc<Node> = create();
|
||||
}
|
||||
|
||||
fn create() -> Arc<Node> {
|
||||
let node = Arc::new(Node::create(&INTERNAL_CLIENT, "", "hmd", false));
|
||||
Spatial::add_to(&node, None, Mat4::IDENTITY, false).expect("Unable to make spatial for HMD");
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
#[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),
|
||||
hmd_pose.orientation.into(),
|
||||
hmd_pose.position.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn make_alias(client: &Arc<Client>) -> Result<Arc<Node>> {
|
||||
Alias::create(
|
||||
client,
|
||||
"",
|
||||
"hmd",
|
||||
&HMD,
|
||||
AliasInfo {
|
||||
server_signals: vec!["get_bounds", "get_transform"],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,51 +1,89 @@
|
||||
use crate::nodes::fields::Field;
|
||||
use super::{Finger, Hand, InputDataTrait, InputHandler, InputMethod, Joint, Thumb};
|
||||
use crate::nodes::fields::{Field, FieldTrait};
|
||||
use crate::nodes::spatial::Spatial;
|
||||
use glam::{vec3a, Mat4};
|
||||
use stardust_xr::schemas::flat::{Hand as FlatHand, InputDataType, Joint};
|
||||
use glam::{Mat4, Quat, vec3a};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{DistanceLink, InputSpecialization};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Hand {
|
||||
pub base: FlatHand,
|
||||
}
|
||||
impl InputSpecialization for Hand {
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
self.true_distance(space, field).abs()
|
||||
impl Default for Joint {
|
||||
fn default() -> Self {
|
||||
Joint {
|
||||
position: [0.0; 3].into(),
|
||||
rotation: Quat::IDENTITY.into(),
|
||||
radius: 0.0,
|
||||
distance: 0.0,
|
||||
}
|
||||
}
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
}
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Finger {
|
||||
fn default() -> Self {
|
||||
Finger {
|
||||
tip: Default::default(),
|
||||
distal: Default::default(),
|
||||
intermediate: Default::default(),
|
||||
proximal: Default::default(),
|
||||
metacarpal: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Thumb {
|
||||
fn default() -> Self {
|
||||
Thumb {
|
||||
tip: Default::default(),
|
||||
distal: Default::default(),
|
||||
proximal: Default::default(),
|
||||
metacarpal: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Hand {
|
||||
fn default() -> Self {
|
||||
Hand {
|
||||
right: Default::default(),
|
||||
thumb: Default::default(),
|
||||
index: Default::default(),
|
||||
middle: Default::default(),
|
||||
ring: Default::default(),
|
||||
little: Default::default(),
|
||||
palm: Default::default(),
|
||||
wrist: Default::default(),
|
||||
elbow: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputDataTrait for Hand {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
let mut min_distance = f32::MAX;
|
||||
|
||||
for tip in [
|
||||
&self.base.thumb.tip.position,
|
||||
&self.base.index.tip.position,
|
||||
&self.base.middle.tip.position,
|
||||
&self.base.ring.tip.position,
|
||||
&self.base.little.tip.position,
|
||||
&self.thumb.tip.position,
|
||||
&self.index.tip.position,
|
||||
&self.middle.tip.position,
|
||||
&self.ring.tip.position,
|
||||
&self.little.tip.position,
|
||||
] {
|
||||
min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z)));
|
||||
}
|
||||
|
||||
min_distance
|
||||
}
|
||||
fn serialize(
|
||||
&self,
|
||||
_distance_link: &DistanceLink,
|
||||
local_to_handler_matrix: Mat4,
|
||||
) -> InputDataType {
|
||||
let mut hand = self.base;
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||
let local_to_handler_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
|
||||
let mut joints: Vec<&mut Joint> = Vec::new();
|
||||
|
||||
joints.extend([&mut hand.palm, &mut hand.wrist]);
|
||||
if let Some(elbow) = &mut hand.elbow {
|
||||
joints.extend([&mut self.palm, &mut self.wrist]);
|
||||
if let Some(elbow) = &mut self.elbow {
|
||||
joints.push(elbow);
|
||||
}
|
||||
for finger in [
|
||||
&mut hand.index,
|
||||
&mut hand.middle,
|
||||
&mut hand.ring,
|
||||
&mut hand.little,
|
||||
&mut self.index,
|
||||
&mut self.middle,
|
||||
&mut self.ring,
|
||||
&mut self.little,
|
||||
] {
|
||||
joints.extend([
|
||||
&mut finger.tip,
|
||||
@@ -56,10 +94,10 @@ impl InputSpecialization for Hand {
|
||||
]);
|
||||
}
|
||||
joints.extend([
|
||||
&mut hand.thumb.tip,
|
||||
&mut hand.thumb.distal,
|
||||
&mut hand.thumb.proximal,
|
||||
&mut hand.thumb.metacarpal,
|
||||
&mut self.thumb.tip,
|
||||
&mut self.thumb.distal,
|
||||
&mut self.thumb.proximal,
|
||||
&mut self.thumb.metacarpal,
|
||||
]);
|
||||
|
||||
for joint in joints {
|
||||
@@ -68,8 +106,7 @@ impl InputSpecialization for Hand {
|
||||
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
|
||||
joint.position = position.into();
|
||||
joint.rotation = rotation.into();
|
||||
joint.distance = handler.field.distance(&handler.spatial, position.into());
|
||||
}
|
||||
|
||||
InputDataType::Hand(Box::new(hand))
|
||||
}
|
||||
}
|
||||
|
||||
39
src/nodes/input/handler.rs
Normal file
39
src/nodes/input/handler.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::{INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY, InputHandlerAspect};
|
||||
use crate::nodes::{Node, alias::AliasList, fields::Field, spatial::Spatial};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct InputHandler {
|
||||
pub spatial: Arc<Spatial>,
|
||||
pub field: Arc<Field>,
|
||||
pub(super) method_aliases: AliasList,
|
||||
}
|
||||
impl InputHandler {
|
||||
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
|
||||
let handler = InputHandler {
|
||||
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
field: field.clone(),
|
||||
method_aliases: AliasList::default(),
|
||||
};
|
||||
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
|
||||
method.handle_new_handler(&handler);
|
||||
}
|
||||
let handler = INPUT_HANDLER_REGISTRY.add(handler);
|
||||
node.add_aspect_raw(handler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl InputHandlerAspect for InputHandler {}
|
||||
impl PartialEq for InputHandler {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.spatial == other.spatial
|
||||
}
|
||||
}
|
||||
impl Drop for InputHandler {
|
||||
fn drop(&mut self) {
|
||||
INPUT_HANDLER_REGISTRY.remove(self);
|
||||
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
|
||||
method.handle_drop_handler(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
256
src/nodes/input/method.rs
Normal file
256
src/nodes/input/method.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
use super::{
|
||||
INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO, INPUT_METHOD_REGISTRY, InputData,
|
||||
InputDataTrait, InputDataType, InputHandler, InputMethodAspect, InputMethodRefAspect,
|
||||
input_method_client,
|
||||
};
|
||||
use crate::{
|
||||
core::{client::Client, error::Result, registry::Registry},
|
||||
nodes::{
|
||||
Node,
|
||||
alias::{Alias, AliasList},
|
||||
fields::{FIELD_ALIAS_INFO, Field},
|
||||
spatial::Spatial,
|
||||
},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub struct InputMethod {
|
||||
pub spatial: Arc<Spatial>,
|
||||
pub data: Mutex<InputDataType>,
|
||||
pub datamap: Mutex<Datamap>,
|
||||
|
||||
handler_aliases: AliasList,
|
||||
handler_field_aliases: AliasList,
|
||||
pub(super) handler_order: Mutex<Vec<Weak<InputHandler>>>,
|
||||
pub capture_attempts: Registry<InputHandler>,
|
||||
pub captures: Registry<InputHandler>,
|
||||
}
|
||||
impl InputMethod {
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
data: InputDataType,
|
||||
datamap: Datamap,
|
||||
) -> Result<Arc<InputMethod>> {
|
||||
let method = InputMethod {
|
||||
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
data: Mutex::new(data),
|
||||
datamap: Mutex::new(datamap),
|
||||
|
||||
handler_aliases: AliasList::default(),
|
||||
handler_field_aliases: AliasList::default(),
|
||||
handler_order: Mutex::new(Vec::new()),
|
||||
capture_attempts: Registry::new(),
|
||||
captures: Registry::new(),
|
||||
};
|
||||
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||
method.handle_new_handler(&handler);
|
||||
}
|
||||
let method = INPUT_METHOD_REGISTRY.add(method);
|
||||
node.add_aspect_raw(method.clone());
|
||||
node.add_aspect(InputMethodRef);
|
||||
Ok(method)
|
||||
}
|
||||
|
||||
pub fn distance(&self, to: &Field) -> f32 {
|
||||
self.data.lock().distance(&self.spatial, to)
|
||||
}
|
||||
|
||||
pub fn set_handler_order<'a>(&self, handlers: impl Iterator<Item = &'a Arc<InputHandler>>) {
|
||||
*self.handler_order.lock() = handlers.map(Arc::downgrade).collect();
|
||||
}
|
||||
|
||||
pub(super) fn make_alias(&self, handler: &InputHandler) {
|
||||
let Some(method_node) = self.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Some(handler_node) = handler.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Some(client) = handler_node.get_client() else {
|
||||
return;
|
||||
};
|
||||
let Ok(method_alias) = Alias::create(
|
||||
&method_node,
|
||||
&client,
|
||||
INPUT_METHOD_REF_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&handler.method_aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
method_alias.set_enabled(false);
|
||||
}
|
||||
pub(super) fn handle_new_handler(&self, handler: &InputHandler) {
|
||||
self.make_alias(handler);
|
||||
|
||||
let Some(method_node) = self.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Some(method_client) = method_node.get_client() else {
|
||||
return;
|
||||
};
|
||||
let Some(handler_node) = handler.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
// Receiver itself
|
||||
let Ok(handler_alias) = Alias::create(
|
||||
&handler_node,
|
||||
&method_client,
|
||||
INPUT_METHOD_REF_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&self.handler_aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(handler_field_node) = handler.field.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
// Handler's field
|
||||
let Ok(rx_field_alias) = Alias::create(
|
||||
&handler_field_node,
|
||||
&method_client,
|
||||
FIELD_ALIAS_INFO.clone(),
|
||||
Some(&self.handler_field_aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = input_method_client::create_handler(&method_node, &handler_alias, &rx_field_alias);
|
||||
}
|
||||
pub(super) fn handle_drop_handler(&self, handler: &InputHandler) {
|
||||
let Some(tx_node) = self.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Some(handler_alias) = self.handler_aliases.get_from_aspect(handler) else {
|
||||
return;
|
||||
};
|
||||
let _ = input_method_client::destroy_handler(&tx_node, handler_alias.id);
|
||||
self.handler_aliases.remove_aspect(handler);
|
||||
self.handler_field_aliases
|
||||
.remove_aspect(handler.field.as_ref());
|
||||
self.capture_attempts.remove(handler);
|
||||
}
|
||||
|
||||
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
|
||||
let mut input = self.data.lock().clone();
|
||||
input.transform(self, handler);
|
||||
|
||||
InputData {
|
||||
id: alias_id,
|
||||
input,
|
||||
distance: self.distance(&handler.field),
|
||||
datamap: self.datamap.lock().clone(),
|
||||
order: self
|
||||
.handler_order
|
||||
.lock()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, h)| h.ptr_eq(&Arc::downgrade(handler)))
|
||||
.unwrap()
|
||||
.0 as u32,
|
||||
captured: self.captures.get_valid_contents().contains(handler),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn cull_capture_attempts(&self) {
|
||||
let sent = self
|
||||
.handler_order
|
||||
.lock()
|
||||
.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.collect::<Registry<InputHandler>>();
|
||||
self.capture_attempts.retain(|handler| {
|
||||
!handler
|
||||
.spatial
|
||||
.node()
|
||||
.and_then(|n| n.get_client())
|
||||
.map(|c| c.unresponsive())
|
||||
.unwrap_or(false)
|
||||
&& sent.contains(handler)
|
||||
});
|
||||
}
|
||||
}
|
||||
impl InputMethodAspect for InputMethod {
|
||||
#[doc = "Set the spatial input component of this input method. You must keep the same input data type throughout the entire thing."]
|
||||
fn set_input(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
input: InputDataType,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
*input_method.data.lock() = input;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Set the datmap of this input method"]
|
||||
fn set_datamap(node: Arc<Node>, _calling_client: Arc<Client>, datamap: Datamap) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
*input_method.datamap.lock() = datamap;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Manually set the order of handlers to propagate input to, or else let the server decide."]
|
||||
fn set_handler_order(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
handlers: Vec<Arc<Node>>,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
let handlers = handlers
|
||||
.into_iter()
|
||||
.filter_map(|p| p.get_aspect::<InputHandler>().ok())
|
||||
.map(|i| Arc::downgrade(&i))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
*input_method.handler_order.lock() = handlers;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Set which handlers are captured."]
|
||||
fn set_captures(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
handlers: Vec<Arc<Node>>,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
input_method.captures.clear();
|
||||
for handler in handlers {
|
||||
let Ok(handler) = handler.get_aspect::<InputHandler>() else {
|
||||
continue;
|
||||
};
|
||||
input_method.captures.add_raw(&handler);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for InputMethod {
|
||||
fn drop(&mut self) {
|
||||
INPUT_METHOD_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputMethodRef;
|
||||
impl InputMethodRefAspect for InputMethodRef {
|
||||
#[doc = "Try to capture the input method with the given handler. When the handler does not get input from the method, it will be released."]
|
||||
fn try_capture(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
handler: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||
|
||||
input_method.capture_attempts.add_raw(&input_handler);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "If captured by this handler, release it (e.g. the object is let go of after grabbing)."]
|
||||
fn release(node: Arc<Node>, _calling_client: Arc<Client>, handler: Arc<Node>) -> Result<()> {
|
||||
let input_method = node.get_aspect::<InputMethod>()?;
|
||||
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||
|
||||
input_method.capture_attempts.remove(&input_handler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,395 +1,170 @@
|
||||
pub mod hand;
|
||||
pub mod pointer;
|
||||
pub mod tip;
|
||||
#![allow(clippy::needless_question_mark)]
|
||||
|
||||
use self::hand::Hand;
|
||||
use self::pointer::Pointer;
|
||||
use self::tip::Tip;
|
||||
mod hand;
|
||||
mod handler;
|
||||
mod method;
|
||||
mod pointer;
|
||||
mod tip;
|
||||
|
||||
use super::{
|
||||
alias::{Alias, AliasInfo},
|
||||
fields::{find_field, Field, FIELD_ALIAS_INFO},
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Message, 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 once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::AtomicBool;
|
||||
use serde::Deserialize;
|
||||
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};
|
||||
pub use handler::*;
|
||||
pub use method::*;
|
||||
|
||||
use super::Aspect;
|
||||
use super::AspectIdentifier;
|
||||
use super::fields::Field;
|
||||
use super::spatial::Spatial;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use crate::{core::registry::Registry, nodes::spatial::Transform};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
|
||||
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
|
||||
static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
|
||||
pub static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
|
||||
|
||||
pub trait InputSpecialization: Send + Sync {
|
||||
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,
|
||||
local_to_handler_matrix: Mat4,
|
||||
) -> InputDataType;
|
||||
stardust_xr_server_codegen::codegen_input_protocol!();
|
||||
|
||||
impl AspectIdentifier for InputHandler {
|
||||
impl_aspect_for_input_handler_aspect_id! {}
|
||||
}
|
||||
pub enum InputType {
|
||||
Pointer(Pointer),
|
||||
Hand(Box<Hand>),
|
||||
Tip(Tip),
|
||||
impl Aspect for InputHandler {
|
||||
impl_aspect_for_input_handler_aspect! {}
|
||||
}
|
||||
impl Deref for InputType {
|
||||
type Target = dyn InputSpecialization;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
impl AspectIdentifier for InputMethod {
|
||||
impl_aspect_for_input_method_aspect_id! {}
|
||||
}
|
||||
impl Aspect for InputMethod {
|
||||
impl_aspect_for_input_method_aspect! {}
|
||||
}
|
||||
impl AspectIdentifier for InputMethodRef {
|
||||
impl_aspect_for_input_method_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for InputMethodRef {
|
||||
impl_aspect_for_input_method_ref_aspect! {}
|
||||
}
|
||||
|
||||
pub trait InputDataTrait {
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||
}
|
||||
impl InputDataTrait for InputDataType {
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||
match self {
|
||||
InputType::Pointer(p) => p,
|
||||
InputType::Hand(h) => h.as_ref(),
|
||||
InputType::Tip(t) => t,
|
||||
InputDataType::Pointer(i) => i.transform(method, handler),
|
||||
InputDataType::Hand(i) => i.transform(method, handler),
|
||||
InputDataType::Tip(i) => i.transform(method, handler),
|
||||
}
|
||||
}
|
||||
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
match self {
|
||||
InputDataType::Pointer(i) => i.distance(space, field),
|
||||
InputDataType::Hand(i) => i.distance(space, field),
|
||||
InputDataType::Tip(i) => i.distance(space, field),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputMethod {
|
||||
node: Weak<Node>,
|
||||
uid: String,
|
||||
pub enabled: Mutex<bool>,
|
||||
pub spatial: Arc<Spatial>,
|
||||
pub specialization: Mutex<InputType>,
|
||||
captures: Registry<InputHandler>,
|
||||
pub datamap: Mutex<Option<Datamap>>,
|
||||
handler_aliases: LifeLinkedNodeMap<String>,
|
||||
handler_order: OnceCell<Mutex<Vec<Weak<InputHandler>>>>,
|
||||
}
|
||||
impl InputMethod {
|
||||
#[allow(dead_code)]
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
specialization: InputType,
|
||||
datamap: Option<Datamap>,
|
||||
) -> Result<Arc<InputMethod>> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
|
||||
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.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>, message: Message) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
let handler = InputHandler::find(&calling_client, deserialize(message.as_ref())?)?;
|
||||
|
||||
method.captures.add_raw(&handler);
|
||||
node.send_remote_signal("capture", message)
|
||||
}
|
||||
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
method.datamap.lock().replace(Datamap::new(message.data)?);
|
||||
Ok(())
|
||||
}
|
||||
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
let method = InputMethod::get(node)?;
|
||||
let handler_paths: Vec<&str> = deserialize(message.as_ref())?;
|
||||
let handlers: Vec<Weak<InputHandler>> = handler_paths
|
||||
.into_iter()
|
||||
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
|
||||
.map(|h| Arc::downgrade(&h))
|
||||
.collect();
|
||||
|
||||
*method
|
||||
.handler_order
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock() = handlers;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compare_distance(&self, to: &Field) -> f32 {
|
||||
self.specialization
|
||||
.lock()
|
||||
.compare_distance(&self.spatial, to)
|
||||
}
|
||||
fn true_distance(&self, to: &Field) -> f32 {
|
||||
self.specialization.lock().true_distance(&self.spatial, to)
|
||||
}
|
||||
|
||||
fn handle_new_handler(&self, handler: &InputHandler) {
|
||||
let Some(method_node) = self.node.upgrade() else {return};
|
||||
let Some(method_client) = method_node.get_client() else {return};
|
||||
let Some(handler_node) = handler.node.upgrade() else {return};
|
||||
// Receiver itself
|
||||
let Ok(handler_alias) = Alias::create(
|
||||
&method_client,
|
||||
method_node.get_path(),
|
||||
handler.uid.as_str(),
|
||||
&handler_node,
|
||||
AliasInfo {
|
||||
server_methods: vec!["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 {
|
||||
fn drop(&mut self) {
|
||||
INPUT_METHOD_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DistanceLink {
|
||||
distance: f32,
|
||||
method: Arc<InputMethod>,
|
||||
handler: Arc<InputHandler>,
|
||||
}
|
||||
impl DistanceLink {
|
||||
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> {
|
||||
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.compare_distance(&handler.field),
|
||||
method,
|
||||
handler,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_input(&self, order: u32, datamap: Datamap) {
|
||||
self.handler.send_input(order, self, datamap);
|
||||
}
|
||||
#[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)),
|
||||
);
|
||||
|
||||
let root = InputData {
|
||||
uid: self.method.uid.clone(),
|
||||
input,
|
||||
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>,
|
||||
field: Arc<Field>,
|
||||
method_aliases: LifeLinkedNodeList,
|
||||
}
|
||||
impl InputHandler {
|
||||
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
|
||||
ensure!(
|
||||
node.spatial.get().is_some(),
|
||||
"Internal: Node does not have a spatial attached!"
|
||||
);
|
||||
|
||||
let handler = InputHandler {
|
||||
enabled: node.enabled.clone(),
|
||||
uid: node.uid.clone(),
|
||||
node: Arc::downgrade(node),
|
||||
spatial: node.spatial.get().unwrap().clone(),
|
||||
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()
|
||||
}
|
||||
|
||||
#[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>) -> Result<()> {
|
||||
let node = Node::create(client, "", "input", false);
|
||||
node.add_local_signal("create_input_handler", create_input_handler_flex);
|
||||
node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
|
||||
node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
pub fn create_input_handler_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateInputHandlerInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create an input method node"]
|
||||
fn create_input_method(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
}
|
||||
let info: CreateInputHandlerInfo = deserialize(message.as_ref())?;
|
||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
let transform = parse_transform(info.transform, true, true, true);
|
||||
let field = find_field(&calling_client, info.field_path)?;
|
||||
initial_data: InputDataType,
|
||||
datamap: Datamap,
|
||||
) -> Result<()> {
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
|
||||
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(())
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
InputMethod::add_to(&node, initial_data, datamap)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Create an input handler node"]
|
||||
fn create_input_handler(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, true);
|
||||
let field = field.get_aspect::<Field>()?;
|
||||
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
InputHandler::add_to(&node, &field)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn process_input() {
|
||||
// Iterate over all valid input methods
|
||||
let methods = debug_span!("Get valid methods").in_scope(|| {
|
||||
INPUT_METHOD_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.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();
|
||||
let methods = INPUT_METHOD_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|method| {
|
||||
let Some(node) = method.spatial.node() else {
|
||||
return false;
|
||||
};
|
||||
node.enabled()
|
||||
});
|
||||
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||
for method_alias in handler.method_aliases.get_aliases() {
|
||||
method_alias.set_enabled(false);
|
||||
}
|
||||
|
||||
let Some(handler_node) = handler.spatial.node() else {
|
||||
continue;
|
||||
};
|
||||
if !handler_node.enabled() {
|
||||
continue;
|
||||
}
|
||||
if let Some(handler_field_node) = handler.field.spatial.node() {
|
||||
if !handler_field_node.enabled() {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let (methods, datas) = methods
|
||||
.clone()
|
||||
// filter out methods without the handler in their handler order
|
||||
.filter(|a| {
|
||||
a.handler_order
|
||||
.lock()
|
||||
.iter()
|
||||
.any(|h| h.ptr_eq(&Arc::downgrade(&handler)))
|
||||
})
|
||||
// filter out methods without the proper alias
|
||||
.filter_map(|m| {
|
||||
Some((
|
||||
handler
|
||||
.method_aliases
|
||||
.get_from_original_node(m.spatial.node.clone())?,
|
||||
m,
|
||||
))
|
||||
})
|
||||
// make sure the input method alias is enabled
|
||||
.inspect(|(a, _)| {
|
||||
a.set_enabled(true);
|
||||
})
|
||||
// serialize the data
|
||||
.map(|(a, m)| (a.clone(), m.serialize(a.get_id(), &handler)))
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
|
||||
let _ = input_handler_client::input(&handler_node, &methods, &datas);
|
||||
}
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
method.cull_capture_attempts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,52 @@
|
||||
use super::{DistanceLink, InputSpecialization};
|
||||
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::{Message, Node};
|
||||
use glam::{vec3, Mat4};
|
||||
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;
|
||||
use super::{InputDataTrait, InputHandler, InputMethod, Pointer};
|
||||
use crate::nodes::{
|
||||
fields::{Field, FieldTrait, Ray, RayMarchResult},
|
||||
spatial::Spatial,
|
||||
};
|
||||
use glam::{Mat4, Quat, vec3};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pointer;
|
||||
// impl Default for Pointer {
|
||||
// fn default() -> Self {
|
||||
// Pointer {
|
||||
// grab: Default::default(),
|
||||
// select: Default::default(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl Default for Pointer {
|
||||
fn default() -> Self {
|
||||
Pointer {
|
||||
origin: [0.0; 3].into(),
|
||||
orientation: Quat::IDENTITY.into(),
|
||||
deepest_point: [0.0; 3].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pointer {
|
||||
fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
|
||||
fn ray_march(&self, method_space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
|
||||
field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: space.clone(),
|
||||
space: Spatial::new(
|
||||
Weak::new(),
|
||||
Some(method_space.clone()),
|
||||
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into()),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSpecialization for Pointer {
|
||||
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 {
|
||||
impl InputDataTrait for Pointer {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
let ray_info = self.ray_march(space, field);
|
||||
ray_info.min_distance
|
||||
}
|
||||
fn serialize(
|
||||
&self,
|
||||
distance_link: &DistanceLink,
|
||||
local_to_handler_matrix: Mat4,
|
||||
) -> InputDataType {
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||
let local_to_handler_matrix =
|
||||
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into())
|
||||
* Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
|
||||
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
||||
let direction = local_to_handler_matrix.transform_vector3(vec3(0.0, 0.0, -1.0));
|
||||
let ray_march = self.ray_march(&distance_link.method.spatial, &distance_link.handler.field);
|
||||
|
||||
let ray_march = self.ray_march(&method.spatial, &handler.field);
|
||||
let direction = local_to_handler_matrix
|
||||
.transform_vector3(vec3(0.0, 0.0, -1.0))
|
||||
.normalize();
|
||||
let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
|
||||
|
||||
InputDataType::Pointer(FlatPointer {
|
||||
origin: origin.into(),
|
||||
orientation: orientation.into(),
|
||||
deepest_point: deepest_point.into(),
|
||||
})
|
||||
self.origin = origin.into();
|
||||
self.orientation = orientation.into();
|
||||
self.deepest_point = deepest_point.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_pointer_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreatePointerInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
datamap: Option<Vec<u8>>,
|
||||
}
|
||||
let info: CreatePointerInfo = deserialize(message.as_ref())?;
|
||||
let node = Node::create(&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(())
|
||||
}
|
||||
|
||||
@@ -1,73 +1,29 @@
|
||||
use super::{DistanceLink, InputSpecialization};
|
||||
use crate::core::client::Client;
|
||||
use crate::nodes::fields::Field;
|
||||
use crate::nodes::input::{InputMethod, InputType};
|
||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use crate::nodes::{Message, Node};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec3a, Mat4};
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip};
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use stardust_xr::values::Transform;
|
||||
use super::{InputDataTrait, InputHandler, InputMethod, Tip};
|
||||
use crate::nodes::{
|
||||
fields::{Field, FieldTrait},
|
||||
spatial::Spatial,
|
||||
};
|
||||
use glam::{Mat4, Quat};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Tip {
|
||||
pub radius: f32,
|
||||
}
|
||||
impl Tip {
|
||||
fn set_radius(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
if let InputType::Tip(tip) = &mut *node.input_method.get().unwrap().specialization.lock() {
|
||||
tip.radius = deserialize(message.as_ref())?;
|
||||
impl Default for Tip {
|
||||
fn default() -> Self {
|
||||
Tip {
|
||||
origin: [0.0; 3].into(),
|
||||
orientation: Quat::IDENTITY.into(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl InputSpecialization for Tip {
|
||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
|
||||
impl InputDataTrait for Tip {
|
||||
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
field.distance(space, self.origin.into())
|
||||
}
|
||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||
field.distance(space, vec3a(0.0, 0.0, 0.0))
|
||||
}
|
||||
fn serialize(
|
||||
&self,
|
||||
_distance_link: &DistanceLink,
|
||||
local_to_handler_matrix: Mat4,
|
||||
) -> InputDataType {
|
||||
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||
let local_to_handler_matrix =
|
||||
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial))
|
||||
* Mat4::from_rotation_translation(self.orientation.into(), self.origin.into());
|
||||
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
||||
InputDataType::Tip(FlatTip {
|
||||
origin: origin.into(),
|
||||
orientation: orientation.into(),
|
||||
radius: self.radius,
|
||||
})
|
||||
self.origin = origin.into();
|
||||
self.orientation = orientation.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateTipInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
radius: f32,
|
||||
datamap: Option<Vec<u8>>,
|
||||
}
|
||||
let info: CreateTipInfo = deserialize(message.as_ref())?;
|
||||
let node = Node::create(&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 node = node.add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
||||
InputMethod::add_to(
|
||||
&node,
|
||||
InputType::Tip(Tip {
|
||||
radius: info.radius,
|
||||
}),
|
||||
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
|
||||
)?;
|
||||
node.add_local_signal("set_radius", Tip::set_radius);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
250
src/nodes/items/camera.rs
Normal file
250
src/nodes/items/camera.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use super::{Item, ItemType, create_item_acceptor_flex, register_item_ui_flex};
|
||||
use crate::bail;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::Aspect;
|
||||
use crate::nodes::AspectIdentifier;
|
||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
||||
nodes::{
|
||||
Message, Node,
|
||||
drawable::{
|
||||
model::{MaterialWrapper, ModelPart},
|
||||
shaders::UNLIT_SHADER_BYTES,
|
||||
},
|
||||
items::TypeInfo,
|
||||
spatial::{Spatial, Transform},
|
||||
},
|
||||
};
|
||||
use glam::Mat4;
|
||||
use lazy_static::lazy_static;
|
||||
use mint::{ColumnMatrix4, Vector2};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use stereokit_rust::{
|
||||
material::{Material, Transparency},
|
||||
shader::Shader,
|
||||
sk::MainThreadToken,
|
||||
system::Renderer,
|
||||
tex::{Tex, TexFormat, TexType},
|
||||
util::Color128,
|
||||
};
|
||||
|
||||
pub struct TexWrapper(pub Tex);
|
||||
unsafe impl Send for TexWrapper {}
|
||||
unsafe impl Sync for TexWrapper {}
|
||||
|
||||
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
||||
lazy_static! {
|
||||
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
||||
type_name: "camera",
|
||||
alias_info: CAMERA_ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||
ui_node_id: INTERFACE_NODE_ID,
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
add_acceptor_aspect: |node| {
|
||||
node.add_aspect(CameraItemAcceptor);
|
||||
},
|
||||
add_ui_aspect: |node| {
|
||||
node.add_aspect(CameraItemUi);
|
||||
},
|
||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||
let _ = camera_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct FrameInfo {
|
||||
proj_matrix: Mat4,
|
||||
px_size: Vector2<u32>,
|
||||
}
|
||||
|
||||
pub struct CameraItem {
|
||||
space: Arc<Spatial>,
|
||||
frame_info: Mutex<FrameInfo>,
|
||||
sk_tex: OnceLock<TexWrapper>,
|
||||
sk_mat: OnceLock<Arc<MaterialWrapper>>,
|
||||
applied_to: Registry<ModelPart>,
|
||||
apply_to: Registry<ModelPart>,
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl CameraItem {
|
||||
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
|
||||
let item = Arc::new(CameraItem {
|
||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||
frame_info: Mutex::new(FrameInfo {
|
||||
proj_matrix,
|
||||
px_size,
|
||||
}),
|
||||
sk_tex: OnceLock::new(),
|
||||
sk_mat: OnceLock::new(),
|
||||
applied_to: Registry::new(),
|
||||
apply_to: Registry::new(),
|
||||
});
|
||||
Item::add_to(node, &ITEM_TYPE_INFO_CAMERA, ItemType::Camera(item.clone()));
|
||||
node.add_aspect_raw(item);
|
||||
}
|
||||
|
||||
fn frame_flex(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
_message: Message,
|
||||
response: MethodResponseSender,
|
||||
) {
|
||||
response.wrap_sync(move || {
|
||||
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
|
||||
else {
|
||||
bail!("Wrong item type?");
|
||||
};
|
||||
Ok(serialize(())?.into())
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_preview_material_flex(
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
|
||||
bail!("Wrong item type?");
|
||||
};
|
||||
let model_part_node =
|
||||
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
|
||||
let model_part = model_part_node.get_aspect::<ModelPart>()?;
|
||||
camera.applied_to.add_raw(&model_part);
|
||||
camera.apply_to.add_raw(&model_part);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
let _ = camera_item_ui_client::create_item(node, item);
|
||||
}
|
||||
pub fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
let _ = camera_item_acceptor_client::capture_item(node, item);
|
||||
}
|
||||
|
||||
pub fn update(&self, token: &MainThreadToken) {
|
||||
let frame_info = self.frame_info.lock();
|
||||
let sk_tex = self.sk_tex.get_or_init(|| {
|
||||
TexWrapper(Tex::gen_color(
|
||||
Color128::default(),
|
||||
frame_info.px_size.x as i32,
|
||||
frame_info.px_size.y as i32,
|
||||
TexType::Rendertarget,
|
||||
TexFormat::RGBA32Linear,
|
||||
))
|
||||
});
|
||||
let sk_mat = self.sk_mat.get_or_init(|| {
|
||||
let shader = Shader::from_memory(UNLIT_SHADER_BYTES).unwrap();
|
||||
let mut mat = Material::new(&shader, None);
|
||||
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
|
||||
mat.transparency(Transparency::Blend);
|
||||
Arc::new(MaterialWrapper(mat))
|
||||
});
|
||||
for model_part in self.apply_to.take_valid_contents() {
|
||||
model_part.replace_material(sk_mat.clone())
|
||||
}
|
||||
|
||||
if !self.applied_to.is_empty() {
|
||||
Renderer::render_to(
|
||||
token,
|
||||
&sk_tex.0,
|
||||
self.space.global_transform(),
|
||||
frame_info.proj_matrix,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for CameraItem {
|
||||
impl_aspect_for_camera_item_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItem {
|
||||
impl_aspect_for_camera_item_aspect! {}
|
||||
}
|
||||
impl CameraItemAspect for CameraItem {}
|
||||
|
||||
pub struct CameraItemUi;
|
||||
impl AspectIdentifier for CameraItemUi {
|
||||
impl_aspect_for_camera_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItemUi {
|
||||
impl_aspect_for_camera_item_ui_aspect! {}
|
||||
}
|
||||
impl CameraItemUiAspect for CameraItemUi {}
|
||||
|
||||
pub struct CameraItemAcceptor;
|
||||
impl AspectIdentifier for CameraItemAcceptor {
|
||||
impl_aspect_for_camera_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for CameraItemAcceptor {
|
||||
impl_aspect_for_camera_item_acceptor_aspect! {}
|
||||
}
|
||||
impl CameraItemAcceptorAspect for CameraItemAcceptor {
|
||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||
super::acceptor_capture_item_flex(node, item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(token: &MainThreadToken) {
|
||||
for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() {
|
||||
let ItemType::Camera(camera) = &camera.specialization else {
|
||||
continue;
|
||||
};
|
||||
camera.update(token);
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Create a camera item at a specific location"]
|
||||
fn create_camera_item(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
proj_matrix: ColumnMatrix4<f32>,
|
||||
px_size: Vector2<u32>,
|
||||
) -> Result<()> {
|
||||
let space = parent.get_aspect::<Spatial>()?;
|
||||
let transform = transform.to_mat4(true, true, false);
|
||||
|
||||
let node = Node::from_id(&calling_client, id, false).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, None, transform * space.global_transform(), false);
|
||||
CameraItem::add_to(&node, proj_matrix.into(), px_size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Register this client to manage camera items and create default 3D UI for them."]
|
||||
fn register_camera_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
node.add_aspect(CameraItemUi);
|
||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_CAMERA)
|
||||
}
|
||||
|
||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/camera/acceptor/<name>`."]
|
||||
fn create_camera_item_acceptor(
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
node.add_aspect(CameraItemAcceptor);
|
||||
create_item_acceptor_flex(
|
||||
calling_client,
|
||||
id,
|
||||
parent,
|
||||
transform,
|
||||
&ITEM_TYPE_INFO_CAMERA,
|
||||
field,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
use super::{Item, ItemType};
|
||||
use crate::{
|
||||
core::{
|
||||
client::{Client, INTERNAL_CLIENT},
|
||||
registry::Registry,
|
||||
},
|
||||
nodes::{
|
||||
items::TypeInfo,
|
||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
||||
Message, Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use nanoid::nanoid;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::{
|
||||
schemas::flex::{deserialize, serialize},
|
||||
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,
|
||||
}
|
||||
impl EnvironmentItem {
|
||||
pub fn add_to(node: &Arc<Node>, path: String) {
|
||||
Item::add_to(
|
||||
node,
|
||||
nanoid!(),
|
||||
&ITEM_TYPE_INFO_ENVIRONMENT,
|
||||
ItemType::Environment(EnvironmentItem { path }),
|
||||
);
|
||||
node.add_local_method("get_path", EnvironmentItem::get_path_flex);
|
||||
}
|
||||
|
||||
fn get_path_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
_message: Message,
|
||||
) -> Result<Message> {
|
||||
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
|
||||
return Err(eyre!("Wrong item type?"))
|
||||
};
|
||||
Ok(serialize(environment_item.path.as_str())?.into())
|
||||
}
|
||||
|
||||
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
|
||||
Ok(serialize((id, self.path.as_str()))?.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_environment_item_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateEnvironmentItemInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
item_data: String,
|
||||
}
|
||||
let info: CreateEnvironmentItemInfo = deserialize(message.as_ref())?;
|
||||
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
|
||||
let space = find_spatial_parent(&calling_client, info.parent_path)?;
|
||||
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_named(&calling_client, &parent_name, info.name)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,44 +1,28 @@
|
||||
mod environment;
|
||||
pub mod camera;
|
||||
pub mod panel;
|
||||
|
||||
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
|
||||
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL};
|
||||
use super::fields::Field;
|
||||
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
|
||||
use super::{Alias, Message, Node};
|
||||
use self::camera::CameraItem;
|
||||
use self::panel::PanelItemTrait;
|
||||
use super::alias::AliasList;
|
||||
use super::fields::{FIELD_ALIAS_INFO, Field};
|
||||
use super::spatial::Spatial;
|
||||
use super::{Alias, Aspect, AspectIdentifier, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::ensure;
|
||||
use crate::nodes::alias::AliasInfo;
|
||||
use crate::nodes::fields::find_field;
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use nanoid::nanoid;
|
||||
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::spatial::Transform;
|
||||
use parking_lot::Mutex;
|
||||
use portable_atomic::Ordering;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
use std::hash::Hash;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![
|
||||
"get_bounds",
|
||||
"get_transform",
|
||||
"set_transform",
|
||||
"set_spatial_parent",
|
||||
"set_spatial_parent_in_place",
|
||||
"set_zoneable",
|
||||
"release",
|
||||
];
|
||||
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec![];
|
||||
static ref ITEM_ALIAS_REMOTE_SIGNALS: Vec<&'static str> = vec![];
|
||||
}
|
||||
stardust_xr_server_codegen::codegen_item_protocol!();
|
||||
|
||||
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
|
||||
release(item, Some(&acceptor));
|
||||
fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||
if item.captured_acceptor.lock().strong_count() > 0 {
|
||||
release(item);
|
||||
}
|
||||
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
|
||||
acceptor.handle_capture(item);
|
||||
@@ -46,25 +30,27 @@ pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||
ui.handle_capture_item(item, acceptor);
|
||||
}
|
||||
}
|
||||
fn release(item: &Item, acceptor: Option<&ItemAcceptor>) {
|
||||
fn release(item: &Item) {
|
||||
let mut captured_acceptor = item.captured_acceptor.lock();
|
||||
if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) {
|
||||
if let Some(acceptor) = captured_acceptor.upgrade().as_ref() {
|
||||
*captured_acceptor = Weak::default();
|
||||
acceptor.handle_release(item);
|
||||
if let Some(ui) = item.type_info.ui.lock().upgrade() {
|
||||
ui.handle_release_item(item, &acceptor);
|
||||
ui.handle_release_item(item, acceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypeInfo {
|
||||
pub type_name: &'static str,
|
||||
pub aliased_local_signals: Vec<&'static str>,
|
||||
pub aliased_local_methods: Vec<&'static str>,
|
||||
pub aliased_remote_signals: Vec<&'static str>,
|
||||
pub alias_info: AliasInfo,
|
||||
pub ui_node_id: u64,
|
||||
pub ui: Mutex<Weak<ItemUI>>,
|
||||
pub items: Registry<Item>,
|
||||
pub acceptors: Registry<ItemAcceptor>,
|
||||
pub add_ui_aspect: fn(node: &Node),
|
||||
pub add_acceptor_aspect: fn(node: &Node),
|
||||
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
|
||||
}
|
||||
impl Hash for TypeInfo {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
@@ -79,8 +65,7 @@ impl PartialEq for TypeInfo {
|
||||
impl Eq for TypeInfo {}
|
||||
|
||||
pub struct Item {
|
||||
node: Weak<Node>,
|
||||
uid: String,
|
||||
spatial: Arc<Spatial>,
|
||||
type_info: &'static TypeInfo,
|
||||
captured_acceptor: Mutex<Weak<ItemAcceptor>>,
|
||||
pub specialization: ItemType,
|
||||
@@ -88,82 +73,60 @@ pub struct Item {
|
||||
impl Item {
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
uid: String,
|
||||
type_info: &'static TypeInfo,
|
||||
specialization: ItemType,
|
||||
) -> Arc<Self> {
|
||||
let item = Item {
|
||||
node: Arc::downgrade(node),
|
||||
uid,
|
||||
spatial: node.aspects.get::<Spatial>().unwrap(),
|
||||
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());
|
||||
node.add_aspect_raw(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);
|
||||
}
|
||||
// if let Some(auto_acceptor) = node.get_client().and_then(|client| {
|
||||
// client
|
||||
// .state
|
||||
// .as_ref()
|
||||
// .and_then(|settings| settings.acceptors.get(type_info))
|
||||
// .and_then(|acceptor| acceptor.upgrade())
|
||||
// }) {
|
||||
// capture(&item, &auto_acceptor);
|
||||
// }
|
||||
|
||||
item
|
||||
}
|
||||
fn make_alias_named(
|
||||
&self,
|
||||
client: &Arc<Client>,
|
||||
parent: &str,
|
||||
name: &str,
|
||||
) -> Result<Arc<Node>> {
|
||||
fn make_alias(&self, client: &Arc<Client>, alias_list: &AliasList) -> Result<Arc<Node>> {
|
||||
Alias::create(
|
||||
&self.spatial.node().unwrap(),
|
||||
client,
|
||||
parent,
|
||||
name,
|
||||
&self.node.upgrade().unwrap(),
|
||||
AliasInfo {
|
||||
server_signals: [
|
||||
&self.type_info.aliased_local_signals,
|
||||
ITEM_ALIAS_LOCAL_SIGNALS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
server_methods: [
|
||||
&self.type_info.aliased_local_methods,
|
||||
ITEM_ALIAS_LOCAL_METHODS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
client_signals: [
|
||||
&self.type_info.aliased_remote_signals,
|
||||
ITEM_ALIAS_REMOTE_SIGNALS.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
},
|
||||
self.type_info.alias_info.clone() + ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(alias_list),
|
||||
)
|
||||
}
|
||||
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>, _message: Message) -> Result<()> {
|
||||
let item = node.get_aspect("Item", "item", |n| &n.item)?;
|
||||
release(item, None);
|
||||
|
||||
}
|
||||
impl AspectIdentifier for Item {
|
||||
impl_aspect_for_item_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Item {
|
||||
impl_aspect_for_item_aspect! {}
|
||||
}
|
||||
impl ItemAspect for Item {
|
||||
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let item = node.get_aspect::<Item>()?;
|
||||
release(&item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Drop for Item {
|
||||
fn drop(&mut self) {
|
||||
self.type_info.items.remove(self);
|
||||
release(self, None);
|
||||
release(self);
|
||||
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||
ui.handle_destroy_item(self);
|
||||
}
|
||||
@@ -171,14 +134,20 @@ impl Drop for Item {
|
||||
}
|
||||
|
||||
pub enum ItemType {
|
||||
Environment(EnvironmentItem),
|
||||
Camera(Arc<CameraItem>),
|
||||
Panel(Arc<dyn PanelItemTrait>),
|
||||
}
|
||||
impl ItemType {
|
||||
fn serialize_start_data(&self, id: &str) -> Result<Message> {
|
||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
match self {
|
||||
ItemType::Environment(e) => e.serialize_start_data(id),
|
||||
ItemType::Panel(p) => p.serialize_start_data(id),
|
||||
ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
||||
}
|
||||
}
|
||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
match self {
|
||||
ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
||||
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,9 +165,9 @@ impl ItemType {
|
||||
pub struct ItemUI {
|
||||
node: Weak<Node>,
|
||||
type_info: &'static TypeInfo,
|
||||
item_aliases: LifeLinkedNodeMap<String>,
|
||||
acceptor_aliases: LifeLinkedNodeMap<String>,
|
||||
acceptor_field_aliases: LifeLinkedNodeMap<String>,
|
||||
item_aliases: AliasList,
|
||||
acceptor_aliases: AliasList,
|
||||
acceptor_field_aliases: AliasList,
|
||||
}
|
||||
impl ItemUI {
|
||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
|
||||
@@ -210,12 +179,12 @@ impl ItemUI {
|
||||
let ui = Arc::new(ItemUI {
|
||||
node: Arc::downgrade(node),
|
||||
type_info,
|
||||
item_aliases: Default::default(),
|
||||
acceptor_aliases: Default::default(),
|
||||
acceptor_field_aliases: Default::default(),
|
||||
item_aliases: AliasList::default(),
|
||||
acceptor_aliases: AliasList::default(),
|
||||
acceptor_field_aliases: AliasList::default(),
|
||||
});
|
||||
*type_info.ui.lock() = Arc::downgrade(&ui);
|
||||
let _ = node.item_ui.set(ui.clone());
|
||||
node.add_aspect_raw(ui.clone());
|
||||
|
||||
for item in type_info.items.get_valid_contents() {
|
||||
ui.handle_create_item(&item);
|
||||
@@ -225,62 +194,107 @@ impl ItemUI {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn send_state(&self, state: &str, name: &str) {
|
||||
let Ok(serialized_data) = serialize(name) else {return};
|
||||
let _ = self
|
||||
.node
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.send_remote_signal(state, serialized_data);
|
||||
}
|
||||
|
||||
fn handle_create_item(&self, item: &Item) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let Some(client) = node.get_client() else {return};
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(client) = node.get_client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
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 Ok(item_alias) = item.make_alias(&client, &self.item_aliases) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
|
||||
let _ = node.send_remote_signal("create_item", serialized_data);
|
||||
}
|
||||
fn handle_destroy_item(&self, item: &Item) {
|
||||
self.item_aliases.remove(&item.uid);
|
||||
self.send_state("destroy_item", item.uid.as_str());
|
||||
item.specialization.send_ui_item_created(&node, &item_alias);
|
||||
}
|
||||
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
|
||||
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
|
||||
let _ = node.send_remote_signal("capture_item", message);
|
||||
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
|
||||
return;
|
||||
};
|
||||
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
|
||||
return;
|
||||
};
|
||||
let _ = item_ui_client::capture_item(
|
||||
&self.node.upgrade().unwrap(),
|
||||
item_alias.id,
|
||||
acceptor_alias.id,
|
||||
);
|
||||
}
|
||||
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
|
||||
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
|
||||
let _ = node.send_remote_signal("release_item", message);
|
||||
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
|
||||
return;
|
||||
};
|
||||
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
|
||||
return;
|
||||
};
|
||||
let _ = item_ui_client::release_item(
|
||||
&self.node.upgrade().unwrap(),
|
||||
item_alias.id,
|
||||
acceptor_alias.id,
|
||||
);
|
||||
}
|
||||
fn handle_destroy_item(&self, item: &Item) {
|
||||
let Some(item_alias) = self
|
||||
.item_aliases
|
||||
.get_from_original_node(item.spatial.node.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = item_ui_client::destroy_item(&self.node.upgrade().unwrap(), item_alias.id);
|
||||
self.item_aliases.remove_aspect(item);
|
||||
}
|
||||
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let Some(client) = node.get_client() else {return};
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(client) = node.get_client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok((alias, field_alias)) = acceptor.make_aliases(
|
||||
let Some(acceptor_node) = acceptor.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Ok(acceptor_alias) = Alias::create(
|
||||
&acceptor_node,
|
||||
&client,
|
||||
&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 Ok(message) = serialize(&acceptor.uid) else {return};
|
||||
let _ = node.send_remote_signal("create_acceptor", message);
|
||||
ITEM_ACCEPTOR_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&self.acceptor_aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(acceptor_field_node) = acceptor.field.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let Ok(acceptor_field_alias) = Alias::create(
|
||||
&acceptor_field_node,
|
||||
&client,
|
||||
FIELD_ALIAS_INFO.clone(),
|
||||
Some(&self.acceptor_aliases),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
(acceptor.type_info.new_acceptor_fn)(&node, &acceptor_alias, &acceptor_field_alias);
|
||||
}
|
||||
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||
self.send_state("destroy_acceptor", acceptor.uid.as_str());
|
||||
self.acceptor_aliases.remove(&acceptor.uid);
|
||||
self.acceptor_field_aliases.remove(&acceptor.uid);
|
||||
let acceptor_alias = self.acceptor_aliases.get_from_aspect(acceptor).unwrap();
|
||||
let _ = item_ui_client::destroy_acceptor(&self.node.upgrade().unwrap(), acceptor_alias.id);
|
||||
|
||||
self.acceptor_aliases
|
||||
.remove_aspect(acceptor.spatial.as_ref());
|
||||
self.acceptor_field_aliases
|
||||
.remove_aspect(acceptor.field.as_ref());
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for ItemUI {
|
||||
impl_aspect_for_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ItemUI {
|
||||
impl_aspect_for_item_ui_aspect! {}
|
||||
}
|
||||
impl Drop for ItemUI {
|
||||
fn drop(&mut self) {
|
||||
*self.type_info.ui.lock() = Weak::new();
|
||||
@@ -288,93 +302,66 @@ impl Drop for ItemUI {
|
||||
}
|
||||
|
||||
pub struct ItemAcceptor {
|
||||
uid: String,
|
||||
node: Weak<Node>,
|
||||
spatial: Arc<Spatial>,
|
||||
pub type_info: &'static TypeInfo,
|
||||
field: Arc<Field>,
|
||||
accepted_aliases: LifeLinkedNodeMap<String>,
|
||||
accepted_aliases: AliasList,
|
||||
accepted_registry: Registry<Item>,
|
||||
}
|
||||
impl ItemAcceptor {
|
||||
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),
|
||||
spatial: node.get_aspect::<Spatial>().unwrap(),
|
||||
type_info,
|
||||
field,
|
||||
accepted_aliases: Default::default(),
|
||||
accepted_aliases: AliasList::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);
|
||||
node.add_aspect_raw(acceptor.clone());
|
||||
}
|
||||
|
||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
if !node.enabled.load(Ordering::Relaxed) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let acceptor = node.item_acceptor.get().unwrap();
|
||||
let item_path: &str = deserialize(message.as_ref())?;
|
||||
let item_node = calling_client.get_node("Item", item_path)?;
|
||||
let item = item_node.get_aspect("Item", "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::create(
|
||||
client,
|
||||
parent,
|
||||
&self.uid,
|
||||
acceptor_node,
|
||||
AliasInfo {
|
||||
server_signals: vec!["capture"],
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let acceptor_field_alias = Alias::create(
|
||||
client,
|
||||
acceptor_alias.get_path(),
|
||||
"field",
|
||||
&self.field.spatial_ref().node.upgrade().unwrap(),
|
||||
AliasInfo::default(),
|
||||
)?;
|
||||
|
||||
Ok((acceptor_alias, acceptor_field_alias))
|
||||
}
|
||||
fn handle_capture(&self, item: &Arc<Item>) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let Some(client) = node.get_client() else {return};
|
||||
let Some(node) = self.spatial.node() 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 Ok(alias_node) = item.make_alias(&client, &self.accepted_aliases) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
|
||||
let _ = node.send_remote_signal("capture", serialized_data);
|
||||
item.specialization
|
||||
.send_acceptor_item_created(&node, &alias_node);
|
||||
}
|
||||
fn handle_release(&self, item: &Item) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
|
||||
self.accepted_registry.remove(item);
|
||||
self.accepted_aliases.remove(&item.uid);
|
||||
let Ok(message) = serialize(&item.uid) else {return};
|
||||
let _ = node.send_remote_signal("release", message);
|
||||
self.accepted_aliases.remove_aspect(item);
|
||||
|
||||
let Some(node) = self.spatial.node() else {
|
||||
return;
|
||||
};
|
||||
let alias = self.accepted_aliases.get_from_aspect(item).unwrap();
|
||||
let _ = item_acceptor_client::release_item(&node, alias.id);
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for ItemAcceptor {
|
||||
impl_aspect_for_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for ItemAcceptor {
|
||||
impl_aspect_for_item_acceptor_aspect! {}
|
||||
}
|
||||
impl ItemAcceptorAspect for ItemAcceptor {}
|
||||
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));
|
||||
release(&item);
|
||||
}
|
||||
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||
ui.handle_destroy_acceptor(self);
|
||||
@@ -382,70 +369,38 @@ impl Drop for ItemAcceptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||
let node = Node::create(client, "", "item", false);
|
||||
node.add_local_signal(
|
||||
"create_environment_item",
|
||||
environment::create_environment_item_flex,
|
||||
);
|
||||
node.add_local_signal("register_item_ui", register_item_ui_flex);
|
||||
node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
|
||||
node.add_to_scenegraph().map(|_| ())
|
||||
}
|
||||
|
||||
fn type_info(name: &str) -> Result<&'static TypeInfo> {
|
||||
match name {
|
||||
"environment" => Ok(&ITEM_TYPE_INFO_ENVIRONMENT),
|
||||
#[cfg(feature = "wayland")]
|
||||
"panel" => Ok(&ITEM_TYPE_INFO_PANEL),
|
||||
_ => Err(eyre!("Invalid item type")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_item_ui_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
type_info: &'static TypeInfo,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct RegisterItemUIInfo<'a> {
|
||||
item_type: &'a str,
|
||||
}
|
||||
let info: RegisterItemUIInfo = deserialize(message.as_ref())?;
|
||||
let type_info = type_info(info.item_type)?;
|
||||
let ui =
|
||||
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
|
||||
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
|
||||
ItemUI::add_to(&ui, type_info)?;
|
||||
(type_info.add_ui_aspect)(&ui);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_item_acceptor_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
type_info: &'static TypeInfo,
|
||||
field: Arc<Node>,
|
||||
) -> 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(message.as_ref())?;
|
||||
let space = 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 type_info = type_info(info.item_type)?;
|
||||
let space = parent.get_aspect::<Spatial>()?;
|
||||
let field = field.get_aspect::<Field>()?;
|
||||
let transform = transform.to_mat4(true, true, false);
|
||||
|
||||
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)?;
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(space.clone()), transform, false);
|
||||
ItemAcceptor::add_to(&node, type_info, field);
|
||||
(type_info.add_acceptor_aspect)(&node);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn acceptor_capture_item_flex(node: Arc<Node>, item: Arc<Node>) -> Result<()> {
|
||||
let acceptor = node.get_aspect::<ItemAcceptor>()?;
|
||||
let item = item.get_aspect::<Item>()?;
|
||||
capture(&item, &acceptor);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,507 +1,527 @@
|
||||
use super::camera::CameraItemAcceptor;
|
||||
use super::{create_item_acceptor_flex, register_item_ui_flex};
|
||||
use crate::bail;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||
use crate::nodes::{Aspect, AspectIdentifier};
|
||||
use crate::{
|
||||
core::{
|
||||
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
|
||||
client::{Client, INTERNAL_CLIENT, get_env, state},
|
||||
registry::Registry,
|
||||
},
|
||||
nodes::{
|
||||
drawable::{model::ModelPart, Drawable},
|
||||
items::{self, Item, ItemType, TypeInfo},
|
||||
spatial::Spatial,
|
||||
Message, Node,
|
||||
Node,
|
||||
drawable::model::ModelPart,
|
||||
items::{Item, ItemType, TypeInfo},
|
||||
spatial::{Spatial, Transform},
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::{bail, eyre, Result};
|
||||
use glam::Mat4;
|
||||
use lazy_static::lazy_static;
|
||||
use mint::Vector2;
|
||||
use nanoid::nanoid;
|
||||
use serde::{
|
||||
de::{Deserializer, Error, SeqAccess, Visitor},
|
||||
ser::Serializer,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use parking_lot::Mutex;
|
||||
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::debug;
|
||||
use tracing::{debug, info};
|
||||
|
||||
stardust_xr_server_codegen::codegen_item_panel_protocol!();
|
||||
impl Default for Geometry {
|
||||
fn default() -> Self {
|
||||
Geometry {
|
||||
origin: [0, 0].into(),
|
||||
size: [0, 0].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
||||
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
||||
type_name: "panel",
|
||||
aliased_local_signals: vec![
|
||||
"apply_surface_material",
|
||||
"configure_toplevel",
|
||||
"set_toplevel_capabilities",
|
||||
"pointer_scroll",
|
||||
"pointer_button",
|
||||
"pointer_motion",
|
||||
"keyboard_key",
|
||||
"keyboard_set_keymap_names",
|
||||
"keyboard_set_keymap_string",
|
||||
"close",
|
||||
],
|
||||
aliased_local_methods: vec![],
|
||||
aliased_remote_signals: vec![
|
||||
"commit_toplevel",
|
||||
"recommend_toplevel_state",
|
||||
"set_cursor",
|
||||
"new_popup",
|
||||
"reposition_popup",
|
||||
"drop_popup",
|
||||
],
|
||||
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||
ui_node_id: INTERFACE_NODE_ID,
|
||||
ui: Default::default(),
|
||||
items: Registry::new(),
|
||||
acceptors: Registry::new(),
|
||||
add_acceptor_aspect: |node| {
|
||||
node.add_aspect(PanelItemUi);
|
||||
},
|
||||
add_ui_aspect: |node| {
|
||||
node.add_aspect(PanelItemAcceptor);
|
||||
},
|
||||
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// An ID for a surface inside this panel item
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum SurfaceID {
|
||||
Cursor,
|
||||
Toplevel,
|
||||
Popup(String),
|
||||
}
|
||||
impl Default for SurfaceID {
|
||||
fn default() -> Self {
|
||||
Self::Toplevel
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for SurfaceID {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_seq(SurfaceIDVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct SurfaceIDVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SurfaceIDVisitor {
|
||||
type Value = SurfaceID;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("idk")
|
||||
}
|
||||
|
||||
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
|
||||
let Some(discrim) = seq.next_element()? else {
|
||||
return Err(A::Error::missing_field("discrim"));
|
||||
};
|
||||
|
||||
// idk if you wanna check for extraneous elements
|
||||
// I didn't bother
|
||||
|
||||
match discrim {
|
||||
"Cursor" => Ok(SurfaceID::Cursor),
|
||||
"Toplevel" => Ok(SurfaceID::Toplevel),
|
||||
"Popup" => {
|
||||
let Some(text) = seq.next_element()? else {
|
||||
return Err(A::Error::missing_field("popup_text"));
|
||||
};
|
||||
Ok(SurfaceID::Popup(text))
|
||||
}
|
||||
_ => Err(A::Error::unknown_variant(
|
||||
discrim,
|
||||
&["Cursor", "Toplevel", "Popup"],
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for SurfaceID {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Self::Cursor => ["Cursor"].serialize(serializer),
|
||||
Self::Toplevel => ["Toplevel"].serialize(serializer),
|
||||
Self::Popup(text) => ["Popup", text].serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum RecommendedState {
|
||||
Maximize(bool),
|
||||
Fullscreen(bool),
|
||||
Minimize,
|
||||
Move,
|
||||
Resize(u32),
|
||||
}
|
||||
|
||||
pub trait Backend: Send + Sync + 'static {
|
||||
fn serialize_start_data(&self, id: &str) -> Result<Message>;
|
||||
fn serialize_toplevel(&self) -> Result<Message>;
|
||||
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>);
|
||||
fn configure_toplevel(
|
||||
&self,
|
||||
size: Option<Vector2<u32>>,
|
||||
states: Vec<u32>,
|
||||
bounds: Option<Vector2<u32>>,
|
||||
);
|
||||
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>);
|
||||
fn start_data(&self) -> Result<PanelItemInitData>;
|
||||
|
||||
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>);
|
||||
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool);
|
||||
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>);
|
||||
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>);
|
||||
|
||||
fn close_toplevel(&self);
|
||||
fn auto_size_toplevel(&self);
|
||||
fn set_toplevel_size(&self, size: Vector2<u32>);
|
||||
fn set_toplevel_focused_visuals(&self, focused: bool);
|
||||
|
||||
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>);
|
||||
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool);
|
||||
fn pointer_scroll(
|
||||
&self,
|
||||
surface: &SurfaceID,
|
||||
surface: &SurfaceId,
|
||||
scroll_distance: Option<Vector2<f32>>,
|
||||
scroll_steps: Option<Vector2<f32>>,
|
||||
);
|
||||
|
||||
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()>;
|
||||
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool);
|
||||
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool);
|
||||
|
||||
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>);
|
||||
fn touch_move(&self, id: u32, position: Vector2<f32>);
|
||||
fn touch_up(&self, id: u32);
|
||||
fn reset_input(&self);
|
||||
}
|
||||
|
||||
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
|
||||
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
|
||||
let ItemType::Panel(panel_item) = &node.get_aspect::<Item>().ok()?.specialization else {
|
||||
return None;
|
||||
};
|
||||
Some(panel_item.clone())
|
||||
}
|
||||
|
||||
pub trait PanelItemTrait: Backend + Send + Sync + 'static {
|
||||
fn uid(&self) -> &str;
|
||||
// fn node(&self) -> Option<Arc<Node>>;
|
||||
pub trait PanelItemTrait: Send + Sync + 'static {
|
||||
fn backend(&self) -> &dyn Backend;
|
||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>);
|
||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>);
|
||||
}
|
||||
|
||||
pub struct PanelItem<B: Backend + ?Sized> {
|
||||
pub uid: String,
|
||||
node: Weak<Node>,
|
||||
pub struct PanelItem<B: Backend> {
|
||||
pub node: Weak<Node>,
|
||||
pub backend: Box<B>,
|
||||
}
|
||||
impl<B: Backend + ?Sized> PanelItem<B> {
|
||||
impl<B: Backend> PanelItem<B> {
|
||||
pub fn create(backend: Box<B>, pid: Option<i32>) -> (Arc<Node>, Arc<PanelItem<B>>) {
|
||||
debug!(?pid, "Create panel item");
|
||||
|
||||
let startup_settings = pid
|
||||
.and_then(|pid| get_env(pid).ok())
|
||||
.and_then(|env| startup_settings(&env));
|
||||
.and_then(|env| state(&env));
|
||||
|
||||
let uid = nanoid!();
|
||||
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
|
||||
.add_to_scenegraph()
|
||||
.unwrap();
|
||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
||||
let node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
|
||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
|
||||
if let Some(startup_settings) = &startup_settings {
|
||||
spatial.set_local_transform(
|
||||
spatial.global_transform().inverse() * startup_settings.transform,
|
||||
);
|
||||
spatial.set_local_transform(startup_settings.root);
|
||||
}
|
||||
|
||||
let panel_item = Arc::new(PanelItem {
|
||||
uid: uid.clone(),
|
||||
node: Arc::downgrade(&node),
|
||||
backend,
|
||||
});
|
||||
|
||||
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
|
||||
let item = Item::add_to(
|
||||
Item::add_to(
|
||||
&node,
|
||||
uid,
|
||||
&ITEM_TYPE_INFO_PANEL,
|
||||
ItemType::Panel(generic_panel_item),
|
||||
);
|
||||
|
||||
// panel_item
|
||||
// .seat_data
|
||||
// .new_surface(&wl_surface, Arc::downgrade(&panel_item));
|
||||
|
||||
if let Some(startup_settings) = &startup_settings {
|
||||
if let Some(acceptor) = startup_settings
|
||||
.acceptors
|
||||
.get(&*ITEM_TYPE_INFO_PANEL)
|
||||
.and_then(|acc| acc.upgrade())
|
||||
{
|
||||
items::capture(&item, &acceptor);
|
||||
}
|
||||
}
|
||||
node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex);
|
||||
node.add_local_signal("configure_toplevel", Self::configure_toplevel_flex);
|
||||
node.add_local_signal(
|
||||
"set_toplevel_capabilities",
|
||||
Self::set_toplevel_capabilities_flex,
|
||||
);
|
||||
node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex);
|
||||
node.add_local_signal("pointer_button", Self::pointer_button_flex);
|
||||
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
|
||||
|
||||
node.add_local_signal(
|
||||
"keyboard_set_keymap_string",
|
||||
Self::keyboard_set_keymap_string_flex,
|
||||
);
|
||||
// node.add_local_signal(
|
||||
// "keyboard_set_keymap_names",
|
||||
// Self::keyboard_set_keymap_names_flex,
|
||||
// );
|
||||
node.add_local_signal("keyboard_key", Self::keyboard_key_flex);
|
||||
node.add_aspect_raw(panel_item.clone());
|
||||
|
||||
(node, panel_item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node(&self) -> Option<Arc<Node>> {
|
||||
self.node.upgrade()
|
||||
// Remote signals
|
||||
#[allow(unused)]
|
||||
impl<B: Backend> PanelItem<B> {
|
||||
pub fn toplevel_parent_changed(&self, parent: u64) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_parent_changed(&node, parent);
|
||||
}
|
||||
pub fn toplevel_title_changed(&self, title: &str) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_title_changed(&node, title);
|
||||
}
|
||||
pub fn toplevel_app_id_changed(&self, app_id: &str) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_app_id_changed(&node, app_id);
|
||||
}
|
||||
pub fn toplevel_fullscreen_active(&self, active: bool) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_fullscreen_active(&node, active);
|
||||
}
|
||||
pub fn toplevel_move_request(&self) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_move_request(&node);
|
||||
}
|
||||
pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_resize_request(&node, up, down, left, right);
|
||||
}
|
||||
pub fn toplevel_size_changed(&self, size: Vector2<u32>) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::toplevel_size_changed(&node, size);
|
||||
}
|
||||
|
||||
fn apply_surface_material_flex(
|
||||
node: &Node,
|
||||
pub fn set_cursor(&self, geometry: Option<Geometry>) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
if let Some(geometry) = geometry {
|
||||
panel_item_client::set_cursor(&node, &geometry);
|
||||
} else {
|
||||
panel_item_client::hide_cursor(&node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_child(&self, id: u64, info: &ChildInfo) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::create_child(&node, id, info);
|
||||
}
|
||||
pub fn reposition_child(&self, id: u64, geometry: &Geometry) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::reposition_child(&node, id, geometry);
|
||||
}
|
||||
pub fn destroy_child(&self, id: u64) {
|
||||
let Some(node) = self.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
panel_item_client::destroy_child(&node, id);
|
||||
}
|
||||
}
|
||||
impl<B: Backend> AspectIdentifier for PanelItem<B> {
|
||||
impl_aspect_for_panel_item_aspect_id! {}
|
||||
}
|
||||
impl<B: Backend> Aspect for PanelItem<B> {
|
||||
impl_aspect_for_panel_item_aspect! {}
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
||||
#[doc = "Apply the cursor as a material to a model."]
|
||||
fn apply_cursor_material(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
model_part: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
let model_part = model_part.get_aspect::<ModelPart>()?;
|
||||
|
||||
panel_item.backend().apply_cursor_material(&model_part);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Apply a surface's visuals as a material to a model."]
|
||||
fn apply_surface_material(
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
surface: SurfaceId,
|
||||
model_part: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SurfaceMaterialInfo<'a> {
|
||||
surface: SurfaceID,
|
||||
model_node_path: &'a str,
|
||||
}
|
||||
|
||||
let info: SurfaceMaterialInfo = deserialize(message.as_ref())?;
|
||||
|
||||
let model_node = calling_client
|
||||
.scenegraph
|
||||
.get_node(info.model_node_path)
|
||||
.ok_or_else(|| eyre!("Model node not found"))?;
|
||||
let Some(Drawable::ModelPart(model_part)) = model_node.drawable.get() else {bail!("Node is not a model")};
|
||||
debug!(?info, "Apply surface material");
|
||||
|
||||
panel_item.apply_surface_material(info.surface, model_part);
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
let model_part = model_part.get_aspect::<ModelPart>()?;
|
||||
|
||||
panel_item
|
||||
.backend()
|
||||
.apply_surface_material(surface, &model_part);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pointer_motion_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
let (surface_id, position): (SurfaceID, Vector2<f32>) = deserialize(message.as_ref())?;
|
||||
debug!(?surface_id, ?position, "Pointer deactivate");
|
||||
|
||||
panel_item.pointer_motion(&surface_id, position);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn pointer_button_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
|
||||
debug!(?surface_id, button, state, "Pointer button");
|
||||
|
||||
panel_item.pointer_button(&surface_id, button, state == 0);
|
||||
Ok(())
|
||||
}
|
||||
fn pointer_scroll_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PointerScrollInfo {
|
||||
surface_id: SurfaceID,
|
||||
axis_continuous: Option<Vector2<f32>>,
|
||||
axis_discrete: Option<Vector2<f32>>,
|
||||
}
|
||||
let info: PointerScrollInfo = deserialize(message.as_ref())?;
|
||||
debug!(?info, "Pointer scroll");
|
||||
|
||||
panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete);
|
||||
|
||||
#[doc = "Try to close the toplevel.\n \n The panel item UI handler or panel item acceptor will drop the panel item if this succeeds."]
|
||||
fn close_toplevel(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().close_toplevel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn keyboard_set_keymap_string_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let keymap_string: &str = deserialize(message.as_ref())?;
|
||||
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
debug!("Keyboard set keymap");
|
||||
panel_item.keyboard_set_keymap(keymap_string)
|
||||
|
||||
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
|
||||
}
|
||||
// fn keyboard_set_keymap_names_flex(
|
||||
// node: &Node,
|
||||
// _calling_client: Arc<Client>,
|
||||
// message: Message,
|
||||
// ) -> Result<()> {
|
||||
// #[derive(Debug, Deserialize)]
|
||||
// struct Names<'a> {
|
||||
// rules: &'a str,
|
||||
// model: &'a str,
|
||||
// layout: &'a str,
|
||||
// variant: &'a str,
|
||||
// options: Option<String>,
|
||||
// }
|
||||
// let names: Names = deserialize(message.as_ref())?;
|
||||
// let context = xkb::Context::new(0);
|
||||
// let keymap = Keymap::new_from_names(
|
||||
// &context,
|
||||
// names.rules,
|
||||
// names.model,
|
||||
// names.layout,
|
||||
// names.variant,
|
||||
// names.options,
|
||||
// XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
// )
|
||||
// .ok_or_else(|| eyre!("Keymap is not valid"))?;
|
||||
|
||||
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
|
||||
// }
|
||||
// fn keyboard_set_keymap_flex(node: &Node, keymap: &str) -> Result<()> {
|
||||
// let Some(panel_item): Option<Arc<PanelItem<dyn WaylandBackend>>> = panel_item_from_node(node) else { return Ok(()) };
|
||||
// debug!("Keyboard set keymap");
|
||||
|
||||
// panel_item.seat_data.set_keymap(
|
||||
// keymap,
|
||||
// match &panel_item {
|
||||
// Backend::Wayland(w) => w.input_surfaces(),
|
||||
// #[cfg(feature = "xwayland")]
|
||||
// Backend::X11(_) => panel_item
|
||||
// .toplevel_wl_surface()
|
||||
// .map(|s| vec![s])
|
||||
// .unwrap_or_default(),
|
||||
// },
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
fn keyboard_key_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
|
||||
debug!(key, state, "Set keyboard key state");
|
||||
|
||||
panel_item.keyboard_key(&surface_id, key, state == 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
|
||||
let Ok(message) = serialize(sid) else {return};
|
||||
let _ = node.send_remote_signal("grab_keyboard", message);
|
||||
}
|
||||
|
||||
fn configure_toplevel_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ConfigureToplevelInfo {
|
||||
size: Option<Vector2<u32>>,
|
||||
states: Vec<u32>,
|
||||
bounds: Option<Vector2<u32>>,
|
||||
}
|
||||
let info: ConfigureToplevelInfo = deserialize(message.as_ref())?;
|
||||
|
||||
panel_item.configure_toplevel(info.size, info.states, info.bounds);
|
||||
#[doc = "Request a resize of the surface to whatever size the 2D app wants."]
|
||||
fn auto_size_toplevel(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().auto_size_toplevel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_toplevel_capabilities_flex(
|
||||
node: &Node,
|
||||
#[doc = "Request a resize of the surface (in pixels)."]
|
||||
fn set_toplevel_size(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
size: mint::Vector2<u32>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
|
||||
|
||||
let capabilities: Vec<u8> = deserialize(message.as_ref())?;
|
||||
debug!("Set toplevel capabilities");
|
||||
panel_item.set_toplevel_capabilities(capabilities);
|
||||
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().set_toplevel_size(size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commit_toplevel(&self) {
|
||||
debug!("Commit toplevel");
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let Ok(data) = self.backend.serialize_toplevel() else {return};
|
||||
let _ = node.send_remote_signal("commit_toplevel", data);
|
||||
#[doc = "Tell the toplevel to appear focused visually if true, or unfocused if false."]
|
||||
fn set_toplevel_focused_visuals(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
focused: bool,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().set_toplevel_focused_visuals(focused);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
|
||||
let Some(node) = self.node.upgrade() else {return};
|
||||
let data = serialize(state).unwrap();
|
||||
debug!(?state, "Recommend toplevel state");
|
||||
|
||||
let _ = node.send_remote_signal("recommend_toplevel_state", data);
|
||||
}
|
||||
}
|
||||
impl<B: Backend + ?Sized> PanelItemTrait for PanelItem<B> {
|
||||
fn uid(&self) -> &str {
|
||||
&self.uid
|
||||
}
|
||||
}
|
||||
impl<B: Backend + ?Sized> Backend for PanelItem<B> {
|
||||
fn serialize_start_data(&self, id: &str) -> Result<Message> {
|
||||
self.backend.serialize_start_data(id)
|
||||
#[doc = "Send an event to set the pointer's position (in pixels, relative to top-left of surface). This will activate the pointer."]
|
||||
fn pointer_motion(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
position: mint::Vector2<f32>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().pointer_motion(&surface, position);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_toplevel(&self) -> Result<Message> {
|
||||
self.backend.serialize_toplevel()
|
||||
}
|
||||
|
||||
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>) {
|
||||
self.backend.set_toplevel_capabilities(capabilities)
|
||||
}
|
||||
|
||||
fn configure_toplevel(
|
||||
&self,
|
||||
size: Option<Vector2<u32>>,
|
||||
states: Vec<u32>,
|
||||
bounds: Option<Vector2<u32>>,
|
||||
) {
|
||||
self.backend.configure_toplevel(size, states, bounds)
|
||||
}
|
||||
|
||||
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
|
||||
self.backend.apply_surface_material(surface, model_part)
|
||||
}
|
||||
|
||||
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
|
||||
self.backend.pointer_motion(surface, position)
|
||||
}
|
||||
|
||||
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
|
||||
self.backend.pointer_button(surface, button, pressed)
|
||||
#[doc = "Send an event to set a pointer button's state if the pointer's active. The `button` is from the `input_event_codes` crate (e.g. BTN_LEFT for left click)."]
|
||||
fn pointer_button(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
button: u32,
|
||||
pressed: bool,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item
|
||||
.backend()
|
||||
.pointer_button(&surface, button, pressed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Send an event to scroll the pointer if it's active.\nScroll distance is a value in pixels corresponding to the `distance` the surface should be scrolled.\nScroll steps is a value in columns/rows corresponding to the wheel clicks of a mouse or such. This also supports fractions of a wheel click."]
|
||||
fn pointer_scroll(
|
||||
&self,
|
||||
surface: &SurfaceID,
|
||||
scroll_distance: Option<Vector2<f32>>,
|
||||
scroll_steps: Option<Vector2<f32>>,
|
||||
) {
|
||||
self.backend
|
||||
.pointer_scroll(surface, scroll_distance, scroll_steps)
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
scroll_distance: mint::Vector2<f32>,
|
||||
scroll_steps: mint::Vector2<f32>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item
|
||||
.backend()
|
||||
.pointer_scroll(&surface, Some(scroll_distance), Some(scroll_steps));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
|
||||
self.backend.keyboard_set_keymap(keymap)
|
||||
#[doc = "Send an event to stop scrolling the pointer."]
|
||||
fn pointer_stop_scroll(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().pointer_scroll(&surface, None, None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
|
||||
self.backend.keyboard_key(surface, key, state)
|
||||
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
|
||||
fn keyboard_key(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
keymap_id: u64,
|
||||
key: u32,
|
||||
pressed: bool,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item
|
||||
.backend()
|
||||
.keyboard_key(&surface, keymap_id, key, pressed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Put a touch down on this surface with the unique ID `uid` at `position` (in pixels) from top left corner of the surface."]
|
||||
fn touch_down(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
surface: SurfaceId,
|
||||
uid: u32,
|
||||
position: mint::Vector2<f32>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().touch_down(&surface, uid, position);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Move an existing touch point."]
|
||||
fn touch_move(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
uid: u32,
|
||||
position: mint::Vector2<f32>,
|
||||
) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().touch_move(uid, position);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Release a touch from its surface."]
|
||||
fn touch_up(node: Arc<Node>, _calling_client: Arc<Client>, uid: u32) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().touch_up(uid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Reset all input, such as pressed keys and pointer clicks and touches. Useful for when it's newly captured into an item acceptor to make sure no input gets stuck."]
|
||||
fn reset_input(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||
return Ok(());
|
||||
};
|
||||
panel_item.backend().reset_input();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<B: Backend + ?Sized> Drop for PanelItem<B> {
|
||||
|
||||
pub struct PanelItemUi;
|
||||
impl AspectIdentifier for PanelItemUi {
|
||||
impl_aspect_for_panel_item_ui_aspect_id! {}
|
||||
}
|
||||
impl Aspect for PanelItemUi {
|
||||
impl_aspect_for_panel_item_ui_aspect! {}
|
||||
}
|
||||
impl PanelItemUiAspect for PanelItemUi {}
|
||||
|
||||
pub struct PanelItemAcceptor;
|
||||
impl AspectIdentifier for PanelItemAcceptor {
|
||||
impl_aspect_for_panel_item_acceptor_aspect_id! {}
|
||||
}
|
||||
impl Aspect for PanelItemAcceptor {
|
||||
impl_aspect_for_panel_item_acceptor_aspect! {}
|
||||
}
|
||||
impl PanelItemAcceptorAspect for PanelItemAcceptor {
|
||||
fn capture_item(node: Arc<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
||||
super::acceptor_capture_item_flex(node, item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Backend> PanelItemTrait for PanelItem<B> {
|
||||
fn backend(&self) -> &dyn Backend {
|
||||
self.backend.as_ref()
|
||||
}
|
||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
let Ok(init_data) = self.backend.start_data() else {
|
||||
return;
|
||||
};
|
||||
let _ = panel_item_ui_client::create_item(node, item, init_data);
|
||||
}
|
||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||
let Ok(init_data) = self.backend.start_data() else {
|
||||
return;
|
||||
};
|
||||
let _ = panel_item_acceptor_client::capture_item(node, item, init_data);
|
||||
}
|
||||
}
|
||||
impl<B: Backend> Drop for PanelItem<B> {
|
||||
fn drop(&mut self) {
|
||||
// Dropped panel item, basically just a debug breakpoint place
|
||||
info!("Dropped panel item");
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceAspect for Interface {
|
||||
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]
|
||||
fn register_panel_item_ui(node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
node.add_aspect(CameraItemAcceptor);
|
||||
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
|
||||
}
|
||||
|
||||
#[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item/<item_type>/acceptor/<name>`."]
|
||||
fn create_panel_item_acceptor(
|
||||
node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
node.add_aspect(PanelItemAcceptor);
|
||||
create_item_acceptor_flex(
|
||||
calling_client,
|
||||
id,
|
||||
parent,
|
||||
transform,
|
||||
&ITEM_TYPE_INFO_PANEL,
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
async fn register_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap: String,
|
||||
) -> Result<u64> {
|
||||
let mut keymaps = KEYMAPS.lock();
|
||||
if let Some(found_keymap_id) = keymaps
|
||||
.iter()
|
||||
.filter(|(_k, v)| *v == &keymap)
|
||||
.map(|(k, _v)| k)
|
||||
.last()
|
||||
{
|
||||
return Ok(found_keymap_id.data().as_ffi());
|
||||
}
|
||||
|
||||
let key = keymaps.insert(keymap);
|
||||
Ok(key.data().as_ffi())
|
||||
}
|
||||
|
||||
async fn get_keymap(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
keymap_id: u64,
|
||||
) -> Result<String> {
|
||||
let keymaps = KEYMAPS.lock();
|
||||
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
||||
bail!("Could not find keymap. Try registering it");
|
||||
};
|
||||
|
||||
Ok(keymap.clone())
|
||||
}
|
||||
}
|
||||
|
||||
486
src/nodes/mod.rs
486
src/nodes/mod.rs
@@ -1,48 +1,31 @@
|
||||
pub mod alias;
|
||||
pub mod audio;
|
||||
pub mod data;
|
||||
pub mod drawable;
|
||||
pub mod fields;
|
||||
pub mod hmd;
|
||||
pub mod input;
|
||||
pub mod items;
|
||||
pub mod root;
|
||||
pub mod spatial;
|
||||
pub mod startup;
|
||||
|
||||
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 stardust_xr::schemas::flex::deserialize;
|
||||
use std::fmt::Debug;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::vec::Vec;
|
||||
use tracing::{debug_span, instrument};
|
||||
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
#[cfg(feature = "openxr_runtime")]
|
||||
use crate::openxr;
|
||||
|
||||
use self::alias::Alias;
|
||||
use self::audio::Sound;
|
||||
use self::data::{PulseReceiver, PulseSender};
|
||||
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;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::{Result, ServerError};
|
||||
use crate::core::registry::Registry;
|
||||
use crate::core::scenegraph::MethodResponseSender;
|
||||
use dashmap::DashMap;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use spatial::Spatial;
|
||||
use stardust_xr::messenger::MessageSenderHandle;
|
||||
use stardust_xr::scenegraph::ScenegraphError;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Message {
|
||||
pub data: Vec<u8>,
|
||||
pub fds: Vec<OwnedFd>,
|
||||
@@ -61,221 +44,202 @@ impl AsRef<[u8]> for Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Signal = fn(&Node, Arc<Client>, Message) -> Result<()>;
|
||||
pub type Method = fn(&Node, Arc<Client>, Message) -> Result<Message>;
|
||||
stardust_xr_server_codegen::codegen_node_protocol!();
|
||||
|
||||
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: 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 drawable: OnceCell<Drawable>,
|
||||
|
||||
// Input
|
||||
pub input_method: OnceCell<Arc<InputMethod>>,
|
||||
pub input_handler: OnceCell<Arc<InputHandler>>,
|
||||
|
||||
// Item
|
||||
pub item: OnceCell<Arc<Item>>,
|
||||
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>>,
|
||||
|
||||
// OpenXR
|
||||
#[cfg(feature = "openxr_runtime")]
|
||||
pub openxr_object: OnceCell<openxr::Object>,
|
||||
pub struct Owned;
|
||||
impl AspectIdentifier for Owned {
|
||||
impl_aspect_for_owned_aspect_id! {}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn get_client(&self) -> Option<Arc<Client>> {
|
||||
self.client.upgrade()
|
||||
}
|
||||
// pub fn get_name(&self) -> &str {
|
||||
// &self.path[self.trailing_slash_pos + 1..]
|
||||
// }
|
||||
pub fn get_path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
alias: OnceCell::new(),
|
||||
aliases: Registry::new(),
|
||||
|
||||
spatial: OnceCell::new(),
|
||||
field: OnceCell::new(),
|
||||
zone: OnceCell::new(),
|
||||
pulse_sender: OnceCell::new(),
|
||||
pulse_receiver: 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(),
|
||||
#[cfg(feature = "openxr_runtime")]
|
||||
openxr_object: 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) -> Result<Arc<Node>> {
|
||||
Ok(self
|
||||
.get_client()
|
||||
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
||||
.scenegraph
|
||||
.add_node(self))
|
||||
}
|
||||
pub fn destroy(&self) {
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
node.enabled
|
||||
.store(deserialize(message.as_ref())?, Ordering::Relaxed);
|
||||
impl Aspect for Owned {
|
||||
impl_aspect_for_owned_aspect! {}
|
||||
}
|
||||
impl OwnedAspect for Owned {
|
||||
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
||||
node.set_enabled(enabled);
|
||||
Ok(())
|
||||
}
|
||||
pub fn destroy_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
_message: Message,
|
||||
) -> Result<()> {
|
||||
|
||||
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
if node.destroyable {
|
||||
node.destroy();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_local_signal(&self, name: &str, signal: Signal) {
|
||||
self.local_signals.insert(name.to_string(), signal);
|
||||
pub struct OwnedNode(pub Arc<Node>);
|
||||
impl Drop for OwnedNode {
|
||||
fn drop(&mut self) {
|
||||
self.0.destroy();
|
||||
}
|
||||
pub fn add_local_method(&self, name: &str, method: Method) {
|
||||
self.local_methods.insert(name.to_string(), method);
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
enabled: AtomicBool,
|
||||
id: u64,
|
||||
client: Weak<Client>,
|
||||
message_sender_handle: Option<MessageSenderHandle>,
|
||||
|
||||
aliases: Registry<Alias>,
|
||||
aspects: Aspects,
|
||||
destroyable: bool,
|
||||
}
|
||||
impl Node {
|
||||
pub fn get_client(&self) -> Option<Arc<Client>> {
|
||||
self.client.upgrade()
|
||||
}
|
||||
pub fn get_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn get_aspect<F, T>(
|
||||
&self,
|
||||
node_name: &'static str,
|
||||
aspect_type: &'static str,
|
||||
aspect_fn: F,
|
||||
) -> Result<&T>
|
||||
where
|
||||
F: FnOnce(&Node) -> &OnceCell<T>,
|
||||
{
|
||||
aspect_fn(self)
|
||||
.get()
|
||||
.ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
|
||||
pub fn generate(client: &Arc<Client>, destroyable: bool) -> Self {
|
||||
Self::from_id(client, client.generate_id(), destroyable)
|
||||
}
|
||||
pub fn from_id(client: &Arc<Client>, id: u64, destroyable: bool) -> Self {
|
||||
let node = Node {
|
||||
enabled: AtomicBool::new(true),
|
||||
client: Arc::downgrade(client),
|
||||
message_sender_handle: client.message_sender_handle.clone(),
|
||||
id,
|
||||
aliases: Default::default(),
|
||||
aspects: Default::default(),
|
||||
destroyable,
|
||||
};
|
||||
node.aspects.add(Owned);
|
||||
node
|
||||
}
|
||||
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
||||
Ok(self
|
||||
.get_client()
|
||||
.ok_or(ServerError::NoClient)?
|
||||
.scenegraph
|
||||
.add_node(self))
|
||||
}
|
||||
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
|
||||
Ok(OwnedNode(
|
||||
self.get_client()
|
||||
.ok_or(ServerError::NoClient)?
|
||||
.scenegraph
|
||||
.add_node(self),
|
||||
))
|
||||
}
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled.load(Ordering::Relaxed)
|
||||
&& if let Ok(spatial) = self.get_aspect::<Spatial>() {
|
||||
spatial
|
||||
.global_transform()
|
||||
.to_scale_rotation_translation()
|
||||
.0
|
||||
.length_squared()
|
||||
> 0.0
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.enabled.store(enabled, Ordering::Relaxed)
|
||||
}
|
||||
pub fn destroy(&self) {
|
||||
if let Some(client) = self.get_client() {
|
||||
client.scenegraph.remove_node(self.get_id());
|
||||
}
|
||||
}
|
||||
|
||||
// very much up for debate if we should allow this, as you can match objects using this
|
||||
// pub fn get_client_pid_flex(
|
||||
// node: Arc<Node>,
|
||||
// _calling_client: Arc<Client>,
|
||||
// _message: Message,
|
||||
// ) -> Result<Message> {
|
||||
// let client = node
|
||||
// .client
|
||||
// .upgrade()
|
||||
// .ok_or_else(|| eyre!("Could not get client for node?"))?;
|
||||
// let pid = client.pid.ok_or_else(|| eyre!("Client PID is unknown"))?;
|
||||
// Ok(serialize(pid)?.into())
|
||||
// }
|
||||
|
||||
pub fn add_aspect<A: AspectIdentifier>(&self, aspect: A) -> Arc<A> {
|
||||
self.aspects.add(aspect)
|
||||
}
|
||||
pub fn add_aspect_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||
self.aspects.add_raw(aspect)
|
||||
}
|
||||
pub fn get_aspect<A: AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||
self.aspects.get()
|
||||
}
|
||||
|
||||
pub fn send_local_signal(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
calling_client: Arc<Client>,
|
||||
method: &str,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: Message,
|
||||
) -> Result<(), ScenegraphError> {
|
||||
if let Some(alias) = self.alias.get() {
|
||||
if !alias.info.server_signals.iter().any(|e| e == &method) {
|
||||
return Err(ScenegraphError::SignalNotFound);
|
||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||
if !alias.info.server_signals.iter().any(|e| *e == method) {
|
||||
return Err(ScenegraphError::MemberNotFound);
|
||||
}
|
||||
alias
|
||||
.original
|
||||
.upgrade()
|
||||
.ok_or(ScenegraphError::BrokenAlias)?
|
||||
.send_local_signal(calling_client, method, message)
|
||||
.send_local_signal(calling_client, aspect_id, method, message)
|
||||
} else {
|
||||
let signal = self
|
||||
.local_signals
|
||||
.get(method)
|
||||
.ok_or(ScenegraphError::SignalNotFound)?;
|
||||
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
let aspect = self
|
||||
.aspects
|
||||
.0
|
||||
.get(&aspect_id)
|
||||
.ok_or(ScenegraphError::AspectNotFound)?
|
||||
.clone();
|
||||
aspect
|
||||
.run_signal(calling_client, self.clone(), method, message)
|
||||
.map_err(|error| ScenegraphError::MemberError {
|
||||
error: error.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn execute_local_method(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
calling_client: Arc<Client>,
|
||||
method: &str,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: Message,
|
||||
) -> Result<Message, ScenegraphError> {
|
||||
if let Some(alias) = self.alias.get() {
|
||||
if !alias.info.server_methods.iter().any(|e| e == &method) {
|
||||
return Err(ScenegraphError::MethodNotFound);
|
||||
response: MethodResponseSender,
|
||||
) {
|
||||
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||
if !alias.info.server_methods.iter().any(|e| *e == method) {
|
||||
response.send(Err(ScenegraphError::MemberNotFound));
|
||||
return;
|
||||
}
|
||||
alias
|
||||
.original
|
||||
.upgrade()
|
||||
.ok_or(ScenegraphError::BrokenAlias)?
|
||||
.execute_local_method(
|
||||
calling_client,
|
||||
method,
|
||||
Message {
|
||||
data: message.data.clone(),
|
||||
fds: Vec::new(),
|
||||
},
|
||||
)
|
||||
let Some(alias) = alias.original.upgrade() else {
|
||||
response.send(Err(ScenegraphError::BrokenAlias));
|
||||
return;
|
||||
};
|
||||
alias.execute_local_method(
|
||||
calling_client,
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: message.data.clone(),
|
||||
fds: Vec::new(),
|
||||
},
|
||||
response,
|
||||
)
|
||||
} else {
|
||||
let method = self
|
||||
.local_methods
|
||||
.get(method)
|
||||
.ok_or(ScenegraphError::MethodNotFound)?;
|
||||
|
||||
debug_span!("Handle method").in_scope(|| {
|
||||
method(self, calling_client, message).map_err(|error| {
|
||||
ScenegraphError::MethodError {
|
||||
error: error.to_string(),
|
||||
}
|
||||
})
|
||||
})
|
||||
let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else {
|
||||
response.send(Err(ScenegraphError::AspectNotFound));
|
||||
return;
|
||||
};
|
||||
aspect.run_method(calling_client, self.clone(), method, message, response);
|
||||
}
|
||||
}
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn send_remote_signal(&self, method: &str, message: impl Into<Message>) -> Result<()> {
|
||||
pub fn send_remote_signal(
|
||||
&self,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
message: impl Into<Message>,
|
||||
) -> Result<()> {
|
||||
let message = message.into();
|
||||
self.aliases
|
||||
.get_valid_contents()
|
||||
@@ -285,6 +249,7 @@ impl Node {
|
||||
.for_each(|node| {
|
||||
// Beware! file descriptors will not be sent to aliases!!!
|
||||
let _ = node.send_remote_signal(
|
||||
aspect_id,
|
||||
method,
|
||||
Message {
|
||||
data: message.data.clone(),
|
||||
@@ -292,34 +257,39 @@ impl Node {
|
||||
},
|
||||
);
|
||||
});
|
||||
let path = self.path.clone();
|
||||
let method = method.to_string();
|
||||
if let Some(handle) = self.message_sender_handle.as_ref() {
|
||||
handle.signal(path.as_str(), method.as_str(), &message.data, message.fds)?;
|
||||
handle.signal(self.id, aspect_id, method, &message.data, message.fds)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// #[instrument(level = "debug", skip_all)]
|
||||
// pub fn execute_remote_method(
|
||||
// &self,
|
||||
// method: &str,
|
||||
// data: Vec<u8>,
|
||||
// ) -> Result<impl Future<Output = Result<Message>>> {
|
||||
// let message_sender_handle = self
|
||||
// .message_sender_handle
|
||||
// .as_ref()
|
||||
// .ok_or(eyre!("Messenger does not exist for this node"))?;
|
||||
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
|
||||
&self,
|
||||
aspect_id: u64,
|
||||
method: u64,
|
||||
input: S,
|
||||
fds: Vec<OwnedFd>,
|
||||
) -> Result<(D, Vec<OwnedFd>)> {
|
||||
let message_sender_handle = self
|
||||
.message_sender_handle
|
||||
.as_ref()
|
||||
.ok_or(ServerError::NoMessenger)?;
|
||||
|
||||
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?;
|
||||
let serialized = serialize(input)?;
|
||||
let result = message_sender_handle
|
||||
.method(self.id, aspect_id, method, &serialized, fds)
|
||||
.await?
|
||||
.map_err(ServerError::RemoteMethodError)?;
|
||||
|
||||
// Ok(async { future.await.map_err(|e| eyre!(e)) })
|
||||
// }
|
||||
let (message, fds) = result.into_components();
|
||||
let deserialized: D = deserialize(&message)?;
|
||||
Ok((deserialized, fds))
|
||||
}
|
||||
}
|
||||
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)
|
||||
.field("id", &self.id)
|
||||
.field("destroyable", &self.destroyable)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -328,3 +298,53 @@ impl Drop for Node {
|
||||
// Debug breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AspectIdentifier: Aspect {
|
||||
const ID: u64;
|
||||
}
|
||||
pub trait Aspect: Any + Send + Sync + 'static {
|
||||
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
|
||||
fn run_signal(
|
||||
&self,
|
||||
calling_client: Arc<Client>,
|
||||
node: Arc<Node>,
|
||||
signal: u64,
|
||||
message: Message,
|
||||
) -> Result<(), stardust_xr::scenegraph::ScenegraphError>;
|
||||
fn run_method(
|
||||
&self,
|
||||
calling_client: Arc<Client>,
|
||||
node: Arc<Node>,
|
||||
method: u64,
|
||||
message: Message,
|
||||
response: MethodResponseSender,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Aspects(DashMap<u64, Arc<dyn Aspect>>);
|
||||
impl Aspects {
|
||||
fn add<A: AspectIdentifier>(&self, t: A) -> Arc<A> {
|
||||
let aspect = Arc::new(t);
|
||||
self.add_raw(aspect.clone());
|
||||
aspect
|
||||
}
|
||||
fn add_raw<A: AspectIdentifier>(&self, aspect: Arc<A>) {
|
||||
self.0.insert(A::ID, aspect);
|
||||
}
|
||||
fn get<A: Aspect + AspectIdentifier>(&self) -> Result<Arc<A>> {
|
||||
self.0
|
||||
.get(&A::ID)
|
||||
// .cloned doesn't work for some reason
|
||||
.map(|v| v.clone())
|
||||
.map(|a| a.as_any())
|
||||
.and_then(|a| Arc::downcast(a).ok())
|
||||
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
|
||||
}
|
||||
}
|
||||
impl Drop for Aspects {
|
||||
fn drop(&mut self) {
|
||||
// why would this be needed? do drop impls not run otherwise?
|
||||
self.0.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,105 @@
|
||||
use super::spatial::Spatial;
|
||||
use super::{Message, Node};
|
||||
use crate::core::client::Client;
|
||||
use crate::core::registry::Registry;
|
||||
use color_eyre::eyre::Result;
|
||||
use super::{Aspect, AspectIdentifier, Node};
|
||||
use crate::bail;
|
||||
use crate::core::client::{CLIENTS, Client};
|
||||
use crate::core::client_state::ClientStateParsed;
|
||||
use crate::core::error::Result;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
use crate::session::connection_env;
|
||||
use glam::Mat4;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
static ROOT_REGISTRY: Registry<Root> = Registry::new();
|
||||
stardust_xr_server_codegen::codegen_root_protocol!();
|
||||
|
||||
pub struct Root {
|
||||
node: Arc<Node>,
|
||||
send_frame_event: AtomicBool,
|
||||
connect_instant: Instant,
|
||||
}
|
||||
impl Root {
|
||||
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
|
||||
let node = Node::create(client, "", "", false);
|
||||
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
|
||||
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
|
||||
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
|
||||
let node = Node::from_id(client, 0, false);
|
||||
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,
|
||||
);
|
||||
|
||||
Ok(ROOT_REGISTRY.add(Root {
|
||||
node,
|
||||
send_frame_event: AtomicBool::from(false),
|
||||
}))
|
||||
let _ = Spatial::add_to(&node, None, transform, false);
|
||||
let root_aspect = node.add_aspect(Root {
|
||||
node: node.clone(),
|
||||
connect_instant: Instant::now(),
|
||||
});
|
||||
Ok(root_aspect)
|
||||
}
|
||||
|
||||
fn subscribe_frame_flex(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
_message: Message,
|
||||
) -> Result<()> {
|
||||
calling_client
|
||||
.root
|
||||
.get()
|
||||
.unwrap()
|
||||
.send_frame_event
|
||||
.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub fn send_frame_events(delta: f64) {
|
||||
if let Ok(data) = serialize((delta, 0.0)) {
|
||||
for root in ROOT_REGISTRY.get_valid_contents() {
|
||||
if root.send_frame_event.load(Ordering::Relaxed) {
|
||||
let _ = root.node.send_remote_signal("frame", data.clone());
|
||||
}
|
||||
}
|
||||
for client in CLIENTS.get_vec() {
|
||||
let Some(root) = client.root.get() else {
|
||||
continue;
|
||||
};
|
||||
let _ = root_client::frame(
|
||||
&root.node,
|
||||
&FrameInfo {
|
||||
delta: delta as f32,
|
||||
elapsed: root.connect_instant.elapsed().as_secs_f32(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_base_prefixes_flex(
|
||||
_node: &Node,
|
||||
pub fn set_transform(&self, transform: Mat4) {
|
||||
let spatial = self.node.get_aspect::<Spatial>().unwrap();
|
||||
spatial.set_spatial_parent(None).unwrap();
|
||||
spatial.set_local_transform(transform);
|
||||
}
|
||||
pub async fn save_state(&self) -> Result<ClientState> {
|
||||
Ok(root_client::save_state(&self.node).await?.0)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Root {
|
||||
impl_aspect_for_root_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Root {
|
||||
impl_aspect_for_root_aspect! {}
|
||||
}
|
||||
impl RootAspect for Root {
|
||||
async fn get_state(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<ClientState> {
|
||||
let Some(state) = calling_client.state.get() else {
|
||||
bail!("Couldn't get state");
|
||||
};
|
||||
Ok(state.clone())
|
||||
}
|
||||
|
||||
#[doc = "Get a hashmap of all the environment variables to connect a given app to the stardust server"]
|
||||
async fn get_connection_environment(
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
) -> Result<stardust_xr::values::Map<String, String>> {
|
||||
Ok(connection_env())
|
||||
}
|
||||
|
||||
#[doc = "Generate a client state token and return it back.\n\n When launching a new client, set the environment variable `STARDUST_STARTUP_TOKEN` to the returned string.\n Make sure the environment variable shows in `/proc/{pid}/environ` as that's the only reliable way to pass the value to the server (suggestions welcome).\n"]
|
||||
async fn generate_state_token(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
state: ClientState,
|
||||
) -> Result<String> {
|
||||
Ok(ClientStateParsed::from_deserialized(&calling_client, state).token())
|
||||
}
|
||||
|
||||
#[doc = "Set initial list of folders to look for namespaced resources in"]
|
||||
fn set_base_prefixes(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
prefixes: Vec<String>,
|
||||
) -> Result<()> {
|
||||
*calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
|
||||
info!(?calling_client, ?prefixes, "Set base prefixes");
|
||||
*calling_client.base_resource_prefixes.lock() =
|
||||
prefixes.into_iter().map(PathBuf::from).collect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc = "Cleanly disconnect from the server"]
|
||||
fn disconnect(_node: Arc<Node>, calling_client: Arc<Client>) -> Result<()> {
|
||||
calling_client.disconnect(Ok(()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Root {
|
||||
fn drop(&mut self) {
|
||||
ROOT_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,70 @@
|
||||
pub mod zone;
|
||||
|
||||
use self::zone::{create_zone_flex, Zone};
|
||||
use super::{Message, Node};
|
||||
use self::zone::Zone;
|
||||
use super::alias::Alias;
|
||||
use super::fields::{Field, FieldTrait};
|
||||
use super::{Aspect, AspectIdentifier};
|
||||
use crate::bail;
|
||||
use crate::core::client::Client;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::registry::Registry;
|
||||
use color_eyre::eyre::{ensure, eyre, Result};
|
||||
use glam::{vec3a, Mat4, Quat};
|
||||
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use glam::{Mat4, Quat, Vec3, vec3a};
|
||||
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 rustc_hash::FxHashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, OnceLock, Weak};
|
||||
use stereokit::{bounds_grow_to_fit_box, Bounds};
|
||||
use tracing::instrument;
|
||||
use std::{f32, ptr};
|
||||
use stereokit_rust::maths::Bounds;
|
||||
|
||||
stardust_xr_server_codegen::codegen_spatial_protocol!();
|
||||
impl Transform {
|
||||
pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||
let position = position
|
||||
.then_some(self.translation)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
||||
let rotation = rotation
|
||||
.then_some(self.rotation)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Quat::IDENTITY.into());
|
||||
let scale = scale
|
||||
.then_some(self.scale)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([1.0; 3]));
|
||||
|
||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Zone {
|
||||
impl_aspect_for_zone_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Zone {
|
||||
impl_aspect_for_zone_aspect! {}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EXPORTED_SPATIALS: Mutex<FxHashMap<u64, Arc<Node>>> = Mutex::new(FxHashMap::default());
|
||||
}
|
||||
|
||||
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
|
||||
|
||||
pub struct Spatial {
|
||||
uid: String,
|
||||
pub(super) node: Weak<Node>,
|
||||
self_ref: Weak<Spatial>,
|
||||
pub node: Weak<Node>,
|
||||
parent: Mutex<Option<Arc<Spatial>>>,
|
||||
old_parent: Mutex<Option<Arc<Spatial>>>,
|
||||
pub(super) transform: Mutex<Mat4>,
|
||||
transform: Mutex<Mat4>,
|
||||
zone: Mutex<Weak<Zone>>,
|
||||
children: Registry<Spatial>,
|
||||
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
|
||||
pub 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!(),
|
||||
Arc::new(Spatial {
|
||||
node,
|
||||
self_ref: self_ref.clone(),
|
||||
parent: Mutex::new(parent),
|
||||
old_parent: Mutex::new(None),
|
||||
transform: Mutex::new(transform),
|
||||
@@ -51,36 +78,23 @@ impl Spatial {
|
||||
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);
|
||||
) -> Arc<Spatial> {
|
||||
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
|
||||
if zoneable {
|
||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||
}
|
||||
let _ = node.spatial.set(spatial.clone());
|
||||
Ok(spatial)
|
||||
if let Some(parent) = parent {
|
||||
parent.children.add_raw(&spatial);
|
||||
}
|
||||
node.add_aspect_raw(spatial.clone());
|
||||
node.add_aspect(SpatialRef);
|
||||
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());
|
||||
@@ -88,39 +102,35 @@ impl Spatial {
|
||||
}
|
||||
|
||||
// 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 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.grown_box(child.get_bounding_box(), 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(),
|
||||
}
|
||||
let parent_transform = self
|
||||
.get_parent()
|
||||
.as_deref()
|
||||
.map(Self::global_transform)
|
||||
.unwrap_or_default();
|
||||
parent_transform * self.local_transform()
|
||||
}
|
||||
#[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>,
|
||||
@@ -142,7 +152,7 @@ impl Spatial {
|
||||
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 {
|
||||
if let Some(pos) = transform.translation {
|
||||
reference_space_pos = pos.into()
|
||||
}
|
||||
if let Some(rot) = transform.rotation {
|
||||
@@ -164,7 +174,6 @@ impl Spatial {
|
||||
);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
|
||||
let mut current_ancestor = spatial;
|
||||
loop {
|
||||
@@ -183,262 +192,138 @@ impl Spatial {
|
||||
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
||||
self.parent.lock().clone()
|
||||
}
|
||||
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) {
|
||||
fn set_parent(self: &Arc<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());
|
||||
new_parent.children.add_raw(self);
|
||||
}
|
||||
|
||||
*self.parent.lock() = new_parent;
|
||||
*self.parent.lock() = new_parent.cloned();
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
||||
pub fn set_spatial_parent(self: &Arc<Self>, parent: Option<&Arc<Spatial>>) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.as_ref()
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
bail!("Setting spatial parent would cause a loop");
|
||||
}
|
||||
self.set_parent(parent);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
||||
pub fn set_spatial_parent_in_place(
|
||||
self: &Arc<Self>,
|
||||
parent: Option<&Arc<Spatial>>,
|
||||
) -> Result<()> {
|
||||
let is_ancestor = parent
|
||||
.as_ref()
|
||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
||||
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||
.unwrap_or(false);
|
||||
if is_ancestor {
|
||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||
bail!("Setting spatial parent would cause a loop");
|
||||
}
|
||||
|
||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||
Some(self),
|
||||
parent.as_deref(),
|
||||
parent.map(AsRef::as_ref),
|
||||
));
|
||||
self.set_parent(parent);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_bounding_box_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
let this_spatial = node
|
||||
.spatial
|
||||
.get()
|
||||
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
|
||||
let relative_spatial_path: Option<&str> = deserialize(message.as_ref())?;
|
||||
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()
|
||||
};
|
||||
|
||||
Ok(serialize((
|
||||
mint::Vector3::from(bounds.center),
|
||||
mint::Vector3::from(bounds.dimensions),
|
||||
))?
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn get_transform_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
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(message.as_ref())?)?;
|
||||
|
||||
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||
Some(this_spatial.as_ref()),
|
||||
Some(relative_spatial.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
Ok(serialize((
|
||||
mint::Vector3::from(position),
|
||||
mint::Quaternion::from(rotation),
|
||||
mint::Vector3::from(scale),
|
||||
))?
|
||||
.into())
|
||||
}
|
||||
pub fn set_transform_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct TransformArgs<'a> {
|
||||
reference_space_path: Option<&'a str>,
|
||||
transform: Transform,
|
||||
}
|
||||
let transform_args: TransformArgs = deserialize(message.as_ref())?;
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
|
||||
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
|
||||
}
|
||||
pub fn set_spatial_parent_in_place_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
|
||||
node.spatial
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_spatial_parent_in_place(Some(parent))?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_zoneable_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let zoneable: bool = deserialize(message.as_ref())?;
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
|
||||
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)?.into())
|
||||
}
|
||||
pub fn field_normal_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
|
||||
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)?.into())
|
||||
}
|
||||
pub fn field_closest_point_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<Message> {
|
||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
|
||||
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)?.into())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub(self) fn zone_distance(&self) -> f32 {
|
||||
self.zone
|
||||
.lock()
|
||||
.upgrade()
|
||||
.and_then(|zone| zone.field.upgrade())
|
||||
.map(|zone| zone.field.clone())
|
||||
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
|
||||
.unwrap_or(f32::MAX)
|
||||
.unwrap_or(f32::NEG_INFINITY)
|
||||
}
|
||||
}
|
||||
impl AspectIdentifier for Spatial {
|
||||
impl_aspect_for_spatial_aspect_id! {}
|
||||
}
|
||||
impl Aspect for Spatial {
|
||||
impl_aspect_for_spatial_aspect! {}
|
||||
}
|
||||
impl SpatialAspect for Spatial {
|
||||
fn set_local_transform(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
transform: Transform,
|
||||
) -> Result<()> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
this_spatial.set_local_transform_components(None, transform);
|
||||
Ok(())
|
||||
}
|
||||
fn set_relative_transform(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
relative_to: Arc<Node>,
|
||||
transform: Transform,
|
||||
) -> Result<()> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
|
||||
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_spatial_parent(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
parent: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
|
||||
this_spatial.set_spatial_parent(Some(&parent))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_spatial_parent_in_place(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
parent: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
|
||||
this_spatial.set_spatial_parent_in_place(Some(&parent))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_zoneable(node: Arc<Node>, _calling_client: Arc<Client>, zoneable: bool) -> Result<()> {
|
||||
let spatial = node.get_aspect::<Spatial>()?;
|
||||
if zoneable {
|
||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||
} else {
|
||||
ZONEABLE_REGISTRY.remove(&spatial);
|
||||
zone::release(&spatial);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// legit gotta find a way to remove old ones, this just keeps the node alive
|
||||
async fn export_spatial(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||
let id = rand::random();
|
||||
EXPORTED_SPATIALS.lock().insert(id, node);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
impl PartialEq for Spatial {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.uid == other.uid
|
||||
self.node.as_ptr() == other.node.as_ptr()
|
||||
}
|
||||
}
|
||||
impl Debug for Spatial {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Spatial")
|
||||
.field("uid", &self.uid)
|
||||
.field("parent", &self.parent)
|
||||
.field("old_parent", &self.old_parent)
|
||||
.field("transform", &self.transform)
|
||||
@@ -447,14 +332,81 @@ impl Debug for Spatial {
|
||||
}
|
||||
impl Drop for Spatial {
|
||||
fn drop(&mut self) {
|
||||
ZONEABLE_REGISTRY.remove(self);
|
||||
zone::release(self);
|
||||
ZONEABLE_REGISTRY.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpatialRef;
|
||||
impl AspectIdentifier for SpatialRef {
|
||||
impl_aspect_for_spatial_ref_aspect_id! {}
|
||||
}
|
||||
impl Aspect for SpatialRef {
|
||||
impl_aspect_for_spatial_ref_aspect! {}
|
||||
}
|
||||
impl SpatialRefAspect for SpatialRef {
|
||||
async fn get_local_bounding_box(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
) -> Result<BoundingBox> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let bounds = this_spatial.get_bounding_box();
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center).into(),
|
||||
size: Vec3::from(bounds.dimensions).into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_relative_bounding_box(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
relative_to: Arc<Node>,
|
||||
) -> Result<BoundingBox> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||
.transform_point3([0.0; 3].into());
|
||||
let mut bounds = Bounds {
|
||||
center: center.into(),
|
||||
dimensions: [0.0; 3].into(),
|
||||
};
|
||||
bounds.grown_box(
|
||||
this_spatial.get_bounding_box(),
|
||||
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
|
||||
);
|
||||
|
||||
Ok(BoundingBox {
|
||||
center: Vec3::from(bounds.center).into(),
|
||||
size: Vec3::from(bounds.dimensions).into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_transform(
|
||||
node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
relative_to: Arc<Node>,
|
||||
) -> Result<Transform> {
|
||||
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||
|
||||
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||
Some(this_spatial.as_ref()),
|
||||
Some(relative_spatial.as_ref()),
|
||||
)
|
||||
.to_scale_rotation_translation();
|
||||
|
||||
Ok(Transform {
|
||||
translation: Some(position.into()),
|
||||
rotation: Some(rotation.into()),
|
||||
scale: Some(scale.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||
let position = position
|
||||
.then_some(transform.position)
|
||||
.then_some(transform.translation)
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
||||
let rotation = rotation
|
||||
@@ -469,53 +421,56 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
|
||||
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>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateSpatialInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
impl InterfaceAspect for Interface {
|
||||
fn create_spatial(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
zoneable: bool,
|
||||
) -> Result<()> {
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = parse_transform(transform, true, true, true);
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
|
||||
Ok(())
|
||||
}
|
||||
fn create_zone(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
id: u64,
|
||||
parent: Arc<Node>,
|
||||
transform: Transform,
|
||||
field: Arc<Node>,
|
||||
) -> Result<()> {
|
||||
let parent = parent.get_aspect::<Spatial>()?;
|
||||
let transform = parse_transform(transform, true, true, false);
|
||||
let field = field.get_aspect::<Field>()?;
|
||||
|
||||
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||
let space = Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||
Zone::add_to(&node, space, field);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn import_spatial_ref(
|
||||
_node: Arc<Node>,
|
||||
calling_client: Arc<Client>,
|
||||
uid: u64,
|
||||
) -> Result<Arc<Node>> {
|
||||
Ok(EXPORTED_SPATIALS
|
||||
.lock()
|
||||
.get(&uid)
|
||||
.map(|s| {
|
||||
Alias::create(
|
||||
s,
|
||||
&calling_client,
|
||||
SPATIAL_REF_ASPECT_ALIAS_INFO.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.ok_or_eyre("Couldn't find spatial with that ID")?)
|
||||
}
|
||||
let info: CreateSpatialInfo = deserialize(message.as_ref())?;
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -1,171 +1,161 @@
|
||||
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY};
|
||||
use super::{
|
||||
SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO, Spatial, ZONEABLE_REGISTRY,
|
||||
ZoneAspect,
|
||||
};
|
||||
use crate::{
|
||||
core::{client::Client, registry::Registry},
|
||||
core::{client::Client, error::Result, registry::Registry},
|
||||
nodes::{
|
||||
alias::{Alias, AliasInfo},
|
||||
fields::{find_field, Field},
|
||||
spatial::{find_spatial_parent, parse_transform},
|
||||
Message, Node,
|
||||
Node,
|
||||
alias::{Alias, AliasList, get_original},
|
||||
fields::{Field, FieldTrait},
|
||||
},
|
||||
};
|
||||
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 Some(node) = zone.spatial.node.upgrade() else {return};
|
||||
let Ok(message) = serialize(&spatial.uid) else {return};
|
||||
let _ = node.send_remote_signal("capture", message);
|
||||
let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
|
||||
if new_distance.abs() > old_distance.abs() {
|
||||
return;
|
||||
}
|
||||
|
||||
release(spatial);
|
||||
*spatial.old_parent.lock() = spatial.get_parent();
|
||||
*spatial.zone.lock() = Arc::downgrade(zone);
|
||||
let Some(zone_node) = zone.spatial.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(spatial_node) = spatial.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Ok(spatial_alias) = Alias::create(
|
||||
&spatial_node,
|
||||
&zone_node.get_client().unwrap(),
|
||||
SPATIAL_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&zone.captured),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let _ = super::zone_client::capture(&zone_node, &spatial_alias);
|
||||
}
|
||||
pub fn release(spatial: &Spatial) {
|
||||
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take());
|
||||
let Some(spatial_node) = spatial.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let spatial = spatial_node.get_aspect::<Spatial>().unwrap();
|
||||
|
||||
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref());
|
||||
let mut spatial_zone = spatial.zone.lock();
|
||||
|
||||
if let Some(spatial_zone) = spatial_zone.upgrade() {
|
||||
let Some(node) = spatial_zone.spatial.node.upgrade() else {return};
|
||||
spatial_zone.captured.remove(spatial);
|
||||
let Ok(message) = serialize(&spatial.uid) else {return};
|
||||
let _ = node.send_remote_signal("release", message);
|
||||
spatial_zone.captured.remove_aspect(spatial.as_ref());
|
||||
let Some(node) = spatial_zone.spatial.node.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let _ = super::zone_client::release(&node, spatial_node.id);
|
||||
}
|
||||
*spatial_zone = Weak::new();
|
||||
}
|
||||
|
||||
pub struct Zone {
|
||||
spatial: Arc<Spatial>,
|
||||
pub field: Weak<Field>,
|
||||
zoneables: Mutex<FxHashMap<String, Arc<Node>>>,
|
||||
captured: Registry<Spatial>,
|
||||
pub field: Arc<Field>,
|
||||
intersecting_spatials: Registry<Spatial>,
|
||||
intersecting: AliasList,
|
||||
captured: AliasList,
|
||||
}
|
||||
impl Zone {
|
||||
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: &Arc<Field>) -> Arc<Zone> {
|
||||
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: Arc<Field>) -> Arc<Zone> {
|
||||
let zone = Arc::new(Zone {
|
||||
spatial,
|
||||
field: Arc::downgrade(field),
|
||||
zoneables: Mutex::new(FxHashMap::default()),
|
||||
captured: Registry::new(),
|
||||
field,
|
||||
intersecting_spatials: Registry::default(),
|
||||
intersecting: AliasList::default(),
|
||||
captured: AliasList::default(),
|
||||
});
|
||||
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());
|
||||
node.add_aspect_raw(zone.clone());
|
||||
zone
|
||||
}
|
||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
let zone = node.zone.get().unwrap();
|
||||
let capture_path: &str = deserialize(message.as_ref())?;
|
||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
||||
capture(&spatial, zone);
|
||||
pub fn update(&self) -> Result<()> {
|
||||
let node = self.spatial.node().unwrap();
|
||||
|
||||
let current_zoneables = Registry::new();
|
||||
for zoneable in ZONEABLE_REGISTRY.get_valid_contents() {
|
||||
// Skip if the zoneable is an ancestor of the zone or the zone itself
|
||||
if zoneable.is_ancestor_of(self.spatial.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let distance = self.field.distance(&zoneable, [0.0; 3].into());
|
||||
if distance > 0.0 {
|
||||
continue;
|
||||
}
|
||||
if distance < zoneable.zone_distance() {
|
||||
continue;
|
||||
}
|
||||
current_zoneables.add_raw(&zoneable);
|
||||
}
|
||||
|
||||
let (added, removed) =
|
||||
Registry::get_changes(&self.intersecting_spatials, ¤t_zoneables);
|
||||
for added in added {
|
||||
let Some(added_node) = added.node() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(alias) = Alias::create(
|
||||
&added_node,
|
||||
&self.spatial.node().unwrap().get_client().unwrap(),
|
||||
SPATIAL_REF_ASPECT_ALIAS_INFO.clone(),
|
||||
Some(&self.intersecting),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let _ = super::zone_client::enter(&node, &alias);
|
||||
}
|
||||
for removed in removed {
|
||||
let Some(removed_node) = removed.node() else {
|
||||
continue;
|
||||
};
|
||||
release(&removed);
|
||||
let _ = super::zone_client::leave(&node, removed_node.id);
|
||||
self.intersecting.remove_aspect(removed.as_ref());
|
||||
}
|
||||
self.intersecting_spatials.set(¤t_zoneables);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn release_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
let capture_path: &str = deserialize(message.as_ref())?;
|
||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
||||
}
|
||||
impl ZoneAspect for Zone {
|
||||
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||
let zone = node.get_aspect::<Zone>()?;
|
||||
let _ = zone.update();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capture(node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> Result<()> {
|
||||
let zone = node.get_aspect::<Zone>()?;
|
||||
let spatial = spatial.get_aspect()?;
|
||||
capture(&spatial, &zone);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(_node: Arc<Node>, _calling_client: Arc<Client>, spatial: Arc<Node>) -> Result<()> {
|
||||
let spatial = spatial.get_aspect()?;
|
||||
release(&spatial);
|
||||
Ok(())
|
||||
}
|
||||
fn update(node: &Node, _calling_client: Arc<Client>, _message: Message) -> 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() {
|
||||
for captured in self
|
||||
.captured
|
||||
.get_aliases()
|
||||
.into_iter()
|
||||
.filter_map(|n| get_original(n, false))
|
||||
.filter_map(|n| n.get_aspect::<Spatial>().ok())
|
||||
{
|
||||
release(&captured);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
#[derive(Deserialize)]
|
||||
struct CreateZoneInfo<'a> {
|
||||
name: &'a str,
|
||||
parent_path: &'a str,
|
||||
transform: Transform,
|
||||
field_path: &'a str,
|
||||
}
|
||||
let info: CreateZoneInfo = deserialize(message.as_ref())?;
|
||||
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,155 +0,0 @@
|
||||
#[cfg(feature = "xwayland")]
|
||||
use crate::wayland::xwayland::DISPLAY;
|
||||
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
|
||||
|
||||
use super::{
|
||||
items::{ItemAcceptor, TypeInfo},
|
||||
spatial::find_spatial,
|
||||
Message, Node,
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref STARTUP_SETTINGS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
|
||||
}
|
||||
|
||||
#[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>) {
|
||||
let _ = node
|
||||
.startup_settings
|
||||
.set(Mutex::new(StartupSettings::default()));
|
||||
}
|
||||
|
||||
fn set_root_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
|
||||
let spatial = find_spatial(
|
||||
&calling_client,
|
||||
"Root spatial",
|
||||
deserialize(message.as_ref())?,
|
||||
)?;
|
||||
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_automatic_acceptor_flex(
|
||||
node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let acceptor_node =
|
||||
calling_client.get_node("Item acceptor", deserialize(message.as_ref())?)?;
|
||||
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>,
|
||||
_message: Message,
|
||||
) -> Result<Message> {
|
||||
let id = nanoid::nanoid!();
|
||||
let data = serialize(&id)?;
|
||||
STARTUP_SETTINGS
|
||||
.lock()
|
||||
.insert(id, node.startup_settings.get().unwrap().lock().clone());
|
||||
Ok(data.into())
|
||||
}
|
||||
}
|
||||
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>) -> Result<()> {
|
||||
let node = Node::create(client, "", "startup", false);
|
||||
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(
|
||||
_node: &Node,
|
||||
calling_client: Arc<Client>,
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
let node = Node::create(
|
||||
&calling_client,
|
||||
"/startup/settings",
|
||||
deserialize(message.as_ref())?,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph()?;
|
||||
StartupSettings::add_to(&node);
|
||||
|
||||
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(
|
||||
"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>,
|
||||
_message: Message,
|
||||
) -> Result<Message> {
|
||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
||||
var_env_insert!(env, STARDUST_INSTANCE);
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
var_env_insert!(env, WAYLAND_DISPLAY);
|
||||
#[cfg(feature = "xwayland")]
|
||||
var_env_insert!(env, 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)?.into())
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,19 +1,23 @@
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
input::{pointer::Pointer, InputMethod, InputType},
|
||||
Node, OwnedNode,
|
||||
fields::{FieldTrait, Ray},
|
||||
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputMethod, Pointer},
|
||||
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 glam::{Mat4, vec3};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
use stereokit::StereoKitMultiThread;
|
||||
use tracing::instrument;
|
||||
use stereokit_rust::system::Input;
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct EyeDatamap {
|
||||
eye: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct KeyboardEvent {
|
||||
@@ -24,33 +28,79 @@ pub struct KeyboardEvent {
|
||||
}
|
||||
|
||||
pub struct EyePointer {
|
||||
node: OwnedNode,
|
||||
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();
|
||||
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||
let pointer = InputMethod::add_to(
|
||||
&node.0,
|
||||
InputDataType::Pointer(Pointer::default()),
|
||||
Datamap::from_typed(EyeDatamap::default())?,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(EyePointer { spatial, pointer })
|
||||
Ok(EyePointer {
|
||||
node,
|
||||
spatial,
|
||||
pointer,
|
||||
})
|
||||
}
|
||||
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
|
||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
||||
let ray = sk.input_eyes();
|
||||
pub fn update(&self) {
|
||||
let ray = Input::get_eyes();
|
||||
self.spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
ray.orientation,
|
||||
ray.position,
|
||||
ray.orientation.into(),
|
||||
ray.position.into(),
|
||||
));
|
||||
{
|
||||
// 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();
|
||||
*self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
||||
}
|
||||
|
||||
// send input to all the input handlers that are the closest to the ray as possible
|
||||
let rx = INPUT_HANDLER_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
// filter out all the disabled handlers
|
||||
.filter(|handler| {
|
||||
let Some(node) = handler.spatial.node() else {
|
||||
return false;
|
||||
};
|
||||
node.enabled()
|
||||
})
|
||||
// ray march to all the enabled handlers' fields
|
||||
.map(|handler| {
|
||||
let result = handler.field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: self.spatial.clone(),
|
||||
});
|
||||
(vec![handler], result)
|
||||
})
|
||||
// make sure the field isn't at the pointer origin and that it's being hit
|
||||
.filter(|(_, result)| result.deepest_point_distance > 0.01 && result.min_distance < 0.0)
|
||||
// .inspect(|(_, result)| {
|
||||
// dbg!(result);
|
||||
// })
|
||||
// now collect all handlers that are same distance if they're the closest
|
||||
.reduce(|(mut handlers_a, result_a), (handlers_b, result_b)| {
|
||||
if (result_a.deepest_point_distance - result_b.deepest_point_distance).abs() < 0.001
|
||||
{
|
||||
// distance is basically the same
|
||||
handlers_a.extend(handlers_b);
|
||||
(handlers_a, result_a)
|
||||
} else if result_a.deepest_point_distance < result_b.deepest_point_distance {
|
||||
(handlers_a, result_a)
|
||||
} else {
|
||||
(handlers_b, result_b)
|
||||
}
|
||||
})
|
||||
.map(|(rx, _)| rx)
|
||||
.unwrap_or_default();
|
||||
self.pointer.set_handler_order(rx.iter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,92 @@ pub mod eye_pointer;
|
||||
pub mod mouse_pointer;
|
||||
pub mod sk_controller;
|
||||
pub mod sk_hand;
|
||||
|
||||
use crate::nodes::{
|
||||
fields::{Field, FieldTrait, Ray},
|
||||
input::{INPUT_HANDLER_REGISTRY, InputDataTrait, InputDataType, InputHandler, InputMethod},
|
||||
spatial::Spatial,
|
||||
};
|
||||
use glam::vec3;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CaptureManager {
|
||||
pub capture: Weak<InputHandler>,
|
||||
}
|
||||
impl CaptureManager {
|
||||
pub fn update_capture(&mut self, method: &InputMethod) {
|
||||
if let Some(capture) = &self.capture.upgrade() {
|
||||
if !method
|
||||
.capture_attempts
|
||||
.get_valid_contents()
|
||||
.contains(capture)
|
||||
{
|
||||
self.capture = Weak::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn set_new_capture(
|
||||
&mut self,
|
||||
method: &InputMethod,
|
||||
distance_calculator: DistanceCalculator,
|
||||
) {
|
||||
if self.capture.upgrade().is_none() {
|
||||
self.capture = find_closest_capture(method, distance_calculator);
|
||||
}
|
||||
}
|
||||
pub fn apply_capture(&self, method: &InputMethod) {
|
||||
method.captures.clear();
|
||||
if let Some(capture) = &self.capture.upgrade() {
|
||||
method.set_handler_order([capture].into_iter());
|
||||
method.captures.add_raw(capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DistanceCalculator = fn(&Arc<Spatial>, &InputDataType, &Field) -> Option<f32>;
|
||||
|
||||
pub fn find_closest_capture(
|
||||
method: &InputMethod,
|
||||
distance_calculator: DistanceCalculator,
|
||||
) -> Weak<InputHandler> {
|
||||
method
|
||||
.capture_attempts
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter_map(|h| {
|
||||
distance_calculator(&method.spatial, &method.data.lock(), &h.field)
|
||||
.map(|dist| (h.clone(), dist))
|
||||
})
|
||||
.min_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap())
|
||||
.map(|(handler, _)| Arc::downgrade(&handler))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// sorts them greatest to least distance (so you can pop off the closest ones easily)
|
||||
pub fn get_sorted_handlers(
|
||||
method: &InputMethod,
|
||||
distance_calculator: DistanceCalculator,
|
||||
) -> Vec<(Arc<InputHandler>, f32)> {
|
||||
let mut handlers = INPUT_HANDLER_REGISTRY
|
||||
.get_valid_contents()
|
||||
.into_iter()
|
||||
.filter(|handler| handler.spatial.node().is_some_and(|node| node.enabled()))
|
||||
.filter(|handler| {
|
||||
handler
|
||||
.field
|
||||
.spatial
|
||||
.node()
|
||||
.is_some_and(|node| node.enabled())
|
||||
})
|
||||
.filter_map(|handler| {
|
||||
distance_calculator(&method.spatial, &method.data.lock(), &handler.field)
|
||||
.map(|distance| (handler, distance))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
handlers.sort_by(|(_, dist_a), (_, dist_b)| dist_a.partial_cmp(dist_b).unwrap());
|
||||
handlers
|
||||
}
|
||||
|
||||
@@ -1,150 +1,357 @@
|
||||
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
|
||||
use crate::{
|
||||
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY},
|
||||
fields::Ray,
|
||||
input::{pointer::Pointer, InputMethod, InputType},
|
||||
Node, OwnedNode,
|
||||
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
|
||||
input::{InputDataType, InputMethod, Pointer},
|
||||
items::panel::KEYMAPS,
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{vec2, vec3, Mat4, Vec2, Vec3};
|
||||
use nanoid::nanoid;
|
||||
use glam::{Mat4, Vec3, vec3};
|
||||
use mint::Vector2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::schemas::flex::flexbuffers;
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
|
||||
use tracing::instrument;
|
||||
use slotmap::{DefaultKey, Key as SlotKey};
|
||||
use stardust_xr::{
|
||||
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
|
||||
values::Datamap,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::system::{Input, Key};
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::time::{Duration, timeout};
|
||||
use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
|
||||
use zbus::{Connection, names::OwnedInterfaceName};
|
||||
|
||||
const SK_KEYMAP: &str = include_str!("sk.kmp");
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
struct MouseDatamap {
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct MouseEvent {
|
||||
select: f32,
|
||||
middle: f32,
|
||||
context: f32,
|
||||
grab: f32,
|
||||
scroll: Vec2,
|
||||
scroll_continuous: Vector2<f32>,
|
||||
scroll_discrete: Vector2<f32>,
|
||||
raw_input_events: Vec<u32>,
|
||||
}
|
||||
impl Default for MouseEvent {
|
||||
fn default() -> Self {
|
||||
MouseEvent {
|
||||
select: 0.0,
|
||||
middle: 0.0,
|
||||
context: 0.0,
|
||||
grab: 0.0,
|
||||
scroll_continuous: [0.0; 2].into(),
|
||||
scroll_discrete: [0.0; 2].into(),
|
||||
raw_input_events: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct KeyboardEvent {
|
||||
pub keyboard: String,
|
||||
pub keymap: Option<String>,
|
||||
pub keys_up: Option<Vec<u32>>,
|
||||
pub keys_down: Option<Vec<u32>>,
|
||||
#[zbus::proxy(
|
||||
interface = "org.stardustxr.XKBv1",
|
||||
default_service = "org.stardustxr.XKBv1"
|
||||
)]
|
||||
trait KeyboardHandler {
|
||||
async fn keymap(&self, keymap_id: u64) -> zbus::Result<()>;
|
||||
async fn key_state(&self, key: u32, pressed: bool) -> zbus::Result<()>;
|
||||
async fn reset(&self) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct MousePointer {
|
||||
node: Arc<Node>,
|
||||
node: OwnedNode,
|
||||
keymap: DefaultKey,
|
||||
spatial: Arc<Spatial>,
|
||||
pointer: Arc<InputMethod>,
|
||||
datamap: TypedDatamap<MouseDatamap>,
|
||||
keyboard_sender: Arc<PulseSender>,
|
||||
capture_manager: CaptureManager,
|
||||
mouse_datamap: MouseEvent,
|
||||
}
|
||||
impl MousePointer {
|
||||
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 node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||
let pointer = InputMethod::add_to(
|
||||
&node.0,
|
||||
InputDataType::Pointer(Pointer::default()),
|
||||
Datamap::from_typed(MouseEvent::default())?,
|
||||
)?;
|
||||
|
||||
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();
|
||||
let context = Context::new(0).unwrap();
|
||||
let keymap = KEYMAPS.lock().insert(
|
||||
Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
||||
.unwrap()
|
||||
.get_as_string(KeymapFormat::TextV1)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Ok(MousePointer {
|
||||
node,
|
||||
spatial,
|
||||
pointer,
|
||||
datamap: Default::default(),
|
||||
keyboard_sender,
|
||||
capture_manager: CaptureManager::default(),
|
||||
mouse_datamap: Default::default(),
|
||||
keymap,
|
||||
})
|
||||
}
|
||||
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
|
||||
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
|
||||
let mouse = sk.input_mouse();
|
||||
pub fn update(&mut self, dbus_connection: &Connection, object_registry: &ObjectRegistry) {
|
||||
let mouse = Input::get_mouse();
|
||||
|
||||
let ray = ray_from_mouse(mouse.pos).unwrap();
|
||||
let ray = mouse.get_ray();
|
||||
self.spatial.set_local_transform(
|
||||
Mat4::look_to_rh(
|
||||
Vec3::from(ray.pos),
|
||||
Vec3::from(ray.dir),
|
||||
Vec3::from(ray.position),
|
||||
Vec3::from(ray.direction),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
)
|
||||
.inverse(),
|
||||
);
|
||||
{
|
||||
// Set pointer input datamap
|
||||
self.datamap.select = if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
self.mouse_datamap = MouseEvent {
|
||||
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
||||
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
||||
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
||||
grab: (Input::key(Key::MouseRight).is_active()
|
||||
|| (Input::key(Key::Backtick).is_active()
|
||||
&& Input::key(Key::Shift).is_active())) as u32 as f32, // Was Mouse 5
|
||||
scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
||||
raw_input_events: vec![],
|
||||
};
|
||||
self.datamap.grab = if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0f32
|
||||
};
|
||||
self.datamap.scroll = vec2(0.0, mouse.scroll_change / 120.0);
|
||||
*self.pointer.datamap.lock() = self.datamap.to_datamap().ok();
|
||||
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
||||
}
|
||||
self.send_keyboard_input(sk);
|
||||
self.target_pointer_input();
|
||||
self.send_keyboard_input(dbus_connection, object_registry);
|
||||
}
|
||||
fn target_pointer_input(&mut self) {
|
||||
let distance_calculator: DistanceCalculator = |space, data, field| {
|
||||
let result = field.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: space.clone(),
|
||||
});
|
||||
let valid =
|
||||
result.deepest_point_distance > 0.0 && result.min_distance.is_sign_negative();
|
||||
valid.then_some(result.deepest_point_distance)
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.pointer);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.pointer, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.pointer);
|
||||
|
||||
if self.capture_manager.capture.upgrade().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
||||
let first_distance = handlers
|
||||
.first()
|
||||
.map(|(_, distance)| *distance)
|
||||
.unwrap_or(f32::NEG_INFINITY);
|
||||
|
||||
self.pointer.set_handler_order(
|
||||
handlers
|
||||
.iter()
|
||||
.filter(|(handler, distance)| (distance - first_distance).abs() <= 0.001)
|
||||
.map(|(handler, _)| handler),
|
||||
);
|
||||
}
|
||||
|
||||
fn send_keyboard_input(&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);
|
||||
pub fn send_keyboard_input(
|
||||
&mut self,
|
||||
dbus_connection: &Connection,
|
||||
object_registry: &ObjectRegistry,
|
||||
) {
|
||||
let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
|
||||
|
||||
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);
|
||||
// Spawn async task to handle keyboard input
|
||||
tokio::spawn({
|
||||
let keyboard_handlers = keyboard_handlers.clone();
|
||||
let spatial = self.spatial.clone();
|
||||
let keymap_id = self.keymap.data().as_ffi();
|
||||
let dbus_connection = dbus_connection.clone();
|
||||
|
||||
async move {
|
||||
let mut closest_handler = None;
|
||||
let mut closest_distance = f32::MAX;
|
||||
|
||||
let mut join_set = JoinSet::new();
|
||||
for handler in &keyboard_handlers {
|
||||
let handler = handler.clone();
|
||||
let dbus_connection = dbus_connection.clone();
|
||||
join_set.spawn(async move {
|
||||
timeout(Duration::from_millis(1), async {
|
||||
let field_ref = handler
|
||||
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
|
||||
.await
|
||||
.ok()?;
|
||||
let uid = field_ref.uid().await.ok()?;
|
||||
Some((handler, uid))
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
});
|
||||
}
|
||||
while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
|
||||
let exported_fields = EXPORTED_FIELDS.lock();
|
||||
let Some(field_ref_node) = exported_fields.get(&field_ref_id) else {
|
||||
println!("didn't find a thing :(");
|
||||
continue;
|
||||
};
|
||||
// println!("still sendin stuff :)");
|
||||
let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
|
||||
continue;
|
||||
};
|
||||
drop(exported_fields);
|
||||
|
||||
let result = field_ref.ray_march(Ray {
|
||||
origin: vec3(0.0, 0.0, 0.0),
|
||||
direction: vec3(0.0, 0.0, -1.0),
|
||||
space: spatial.clone(),
|
||||
});
|
||||
|
||||
if result.deepest_point_distance > 0.0
|
||||
&& result.min_distance < 0.05
|
||||
&& result.deepest_point_distance < closest_distance
|
||||
{
|
||||
closest_distance = result.deepest_point_distance;
|
||||
closest_handler = Some(handler);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(handler) = closest_handler else {
|
||||
return;
|
||||
};
|
||||
let Ok(keyboard_handler) = handler
|
||||
.to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Register keymap first
|
||||
let _ = keyboard_handler.keymap(keymap_id).await;
|
||||
|
||||
// Send key states
|
||||
for i in 8_u32..254 {
|
||||
let key = unsafe { std::mem::transmute::<u32, stereokit_rust::system::Key>(i) };
|
||||
let Some(mapped_key) = map_key(key) else {
|
||||
continue;
|
||||
};
|
||||
let input_state = Input::key(key);
|
||||
if input_state.is_just_active() {
|
||||
let _ = keyboard_handler.key_state(mapped_key + 8, true).await;
|
||||
} else if input_state.is_just_inactive() {
|
||||
let _ = keyboard_handler.key_state(mapped_key + 8, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn map_key(key: Key) -> Option<u32> {
|
||||
match key {
|
||||
Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
|
||||
Key::Tab => Some(input_event_codes::KEY_TAB!()),
|
||||
Key::Return => Some(input_event_codes::KEY_ENTER!()),
|
||||
Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
|
||||
Key::Ctrl => Some(input_event_codes::KEY_LEFTCTRL!()),
|
||||
Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
|
||||
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
|
||||
Key::Esc => Some(input_event_codes::KEY_ESC!()),
|
||||
Key::Space => Some(input_event_codes::KEY_SPACE!()),
|
||||
Key::End => Some(input_event_codes::KEY_END!()),
|
||||
Key::Home => Some(input_event_codes::KEY_HOME!()),
|
||||
Key::Left => Some(input_event_codes::KEY_LEFT!()),
|
||||
Key::Right => Some(input_event_codes::KEY_RIGHT!()),
|
||||
Key::Up => Some(input_event_codes::KEY_UP!()),
|
||||
Key::Down => Some(input_event_codes::KEY_DOWN!()),
|
||||
Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
|
||||
Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
|
||||
Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
|
||||
Key::KeyInsert => Some(input_event_codes::KEY_INSERT!()),
|
||||
Key::Del => Some(input_event_codes::KEY_DELETE!()),
|
||||
Key::Key0 => Some(input_event_codes::KEY_0!()),
|
||||
Key::Key1 => Some(input_event_codes::KEY_1!()),
|
||||
Key::Key2 => Some(input_event_codes::KEY_2!()),
|
||||
Key::Key3 => Some(input_event_codes::KEY_3!()),
|
||||
Key::Key4 => Some(input_event_codes::KEY_4!()),
|
||||
Key::Key5 => Some(input_event_codes::KEY_5!()),
|
||||
Key::Key6 => Some(input_event_codes::KEY_6!()),
|
||||
Key::Key7 => Some(input_event_codes::KEY_7!()),
|
||||
Key::Key8 => Some(input_event_codes::KEY_8!()),
|
||||
Key::Key9 => Some(input_event_codes::KEY_9!()),
|
||||
Key::A => Some(input_event_codes::KEY_A!()),
|
||||
Key::B => Some(input_event_codes::KEY_B!()),
|
||||
Key::C => Some(input_event_codes::KEY_C!()),
|
||||
Key::D => Some(input_event_codes::KEY_D!()),
|
||||
Key::E => Some(input_event_codes::KEY_E!()),
|
||||
Key::F => Some(input_event_codes::KEY_F!()),
|
||||
Key::G => Some(input_event_codes::KEY_G!()),
|
||||
Key::H => Some(input_event_codes::KEY_H!()),
|
||||
Key::I => Some(input_event_codes::KEY_I!()),
|
||||
Key::J => Some(input_event_codes::KEY_J!()),
|
||||
Key::K => Some(input_event_codes::KEY_K!()),
|
||||
Key::L => Some(input_event_codes::KEY_L!()),
|
||||
Key::M => Some(input_event_codes::KEY_M!()),
|
||||
Key::N => Some(input_event_codes::KEY_N!()),
|
||||
Key::O => Some(input_event_codes::KEY_O!()),
|
||||
Key::P => Some(input_event_codes::KEY_P!()),
|
||||
Key::Q => Some(input_event_codes::KEY_Q!()),
|
||||
Key::R => Some(input_event_codes::KEY_R!()),
|
||||
Key::S => Some(input_event_codes::KEY_S!()),
|
||||
Key::T => Some(input_event_codes::KEY_T!()),
|
||||
Key::U => Some(input_event_codes::KEY_U!()),
|
||||
Key::V => Some(input_event_codes::KEY_V!()),
|
||||
Key::W => Some(input_event_codes::KEY_W!()),
|
||||
Key::X => Some(input_event_codes::KEY_X!()),
|
||||
Key::Y => Some(input_event_codes::KEY_Y!()),
|
||||
Key::Z => Some(input_event_codes::KEY_Z!()),
|
||||
Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
|
||||
Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
|
||||
Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
|
||||
Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
|
||||
Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
|
||||
Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
|
||||
Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
|
||||
Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
|
||||
Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
|
||||
Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
|
||||
Key::F1 => Some(input_event_codes::KEY_F1!()),
|
||||
Key::F2 => Some(input_event_codes::KEY_F2!()),
|
||||
Key::F3 => Some(input_event_codes::KEY_F3!()),
|
||||
Key::F4 => Some(input_event_codes::KEY_F4!()),
|
||||
Key::F5 => Some(input_event_codes::KEY_F5!()),
|
||||
// Key::F6 => Some(input_event_codes::KEY_F6!()),
|
||||
// Key::F7 => Some(input_event_codes::KEY_F7!()),
|
||||
// Key::F8 => Some(input_event_codes::KEY_F8!()),
|
||||
Key::F9 => Some(input_event_codes::KEY_F9!()),
|
||||
Key::F10 => Some(input_event_codes::KEY_F10!()),
|
||||
Key::F11 => Some(input_event_codes::KEY_F11!()),
|
||||
Key::F12 => Some(input_event_codes::KEY_F12!()),
|
||||
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
|
||||
Key::Period => Some(input_event_codes::KEY_DOT!()),
|
||||
Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
|
||||
Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
|
||||
Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
|
||||
Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
|
||||
Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
|
||||
Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
|
||||
Key::Minus => Some(input_event_codes::KEY_MINUS!()),
|
||||
Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
|
||||
Key::Backtick => Some(input_event_codes::KEY_GRAVE!()),
|
||||
Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
|
||||
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
|
||||
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
|
||||
Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
|
||||
Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
|
||||
Key::Decimal => Some(input_event_codes::KEY_DOT!()),
|
||||
Key::Divide => Some(input_event_codes::KEY_SLASH!()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
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,76 +1,139 @@
|
||||
use super::{CaptureManager, get_sorted_handlers};
|
||||
use crate::{
|
||||
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
input::{tip::Tip, InputMethod, InputType},
|
||||
Node, OwnedNode,
|
||||
fields::{Field, FieldTrait},
|
||||
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
objects::{ObjectHandle, SpatialRef, Tracked},
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::{Mat4, Vec2};
|
||||
use nanoid::nanoid;
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::values::Transform;
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
use stereokit::{
|
||||
ButtonState, Color128, Handed, Model, RenderLayer, StereoKitDraw, StereoKitMultiThread,
|
||||
use stereokit_rust::{
|
||||
material::Material,
|
||||
model::Model,
|
||||
sk::MainThreadToken,
|
||||
system::{Handed, Input},
|
||||
util::Color128,
|
||||
};
|
||||
use tracing::instrument;
|
||||
use zbus::Connection;
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
struct ControllerDatamap {
|
||||
select: f32,
|
||||
middle: f32,
|
||||
context: f32,
|
||||
grab: f32,
|
||||
scroll: Vec2,
|
||||
}
|
||||
|
||||
pub struct SkController {
|
||||
_node: Arc<Node>,
|
||||
object_handle: ObjectHandle<SpatialRef>,
|
||||
input: Arc<InputMethod>,
|
||||
model: Model,
|
||||
handed: Handed,
|
||||
datamap: TypedDatamap<ControllerDatamap>,
|
||||
model: Model,
|
||||
material: Material,
|
||||
capture_manager: CaptureManager,
|
||||
datamap: ControllerDatamap,
|
||||
tracked: ObjectHandle<Tracked>,
|
||||
}
|
||||
impl SkController {
|
||||
pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> {
|
||||
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
||||
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
|
||||
let model = sk.model_create_mem("cursor", include_bytes!("cursor.glb"), None)?;
|
||||
let tip = InputType::Tip(Tip::default());
|
||||
let input = InputMethod::add_to(&_node, tip, None)?;
|
||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||
Input::set_controller_model(handed, Some(Model::new()));
|
||||
let path = "/org/stardustxr/Controller/".to_string()
|
||||
+ match handed {
|
||||
Handed::Left => "left",
|
||||
_ => "right",
|
||||
};
|
||||
let (spatial, object_handle) = SpatialRef::create(connection, &path);
|
||||
let tracked = Tracked::new(connection, &path);
|
||||
let model = Model::copy(&Model::from_memory(
|
||||
"cursor.glb",
|
||||
include_bytes!("cursor.glb"),
|
||||
None,
|
||||
)?);
|
||||
let model_nodes = model.get_nodes();
|
||||
let mut model_node = model_nodes.visuals().next().unwrap();
|
||||
let material = Material::copy(&model_node.get_material().unwrap());
|
||||
model_node.material(&material);
|
||||
let tip = InputDataType::Tip(Tip::default());
|
||||
let input = InputMethod::add_to(
|
||||
&spatial.node().unwrap(),
|
||||
tip,
|
||||
Datamap::from_typed(ControllerDatamap::default())?,
|
||||
)?;
|
||||
Ok(SkController {
|
||||
_node,
|
||||
object_handle,
|
||||
input,
|
||||
handed,
|
||||
model,
|
||||
material,
|
||||
capture_manager: CaptureManager::default(),
|
||||
datamap: Default::default(),
|
||||
tracked,
|
||||
})
|
||||
}
|
||||
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
|
||||
pub fn update(&mut self, sk: &impl StereoKitDraw) {
|
||||
let controller = sk.input_controller(self.handed);
|
||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
|
||||
if *self.input.enabled.lock() {
|
||||
sk.model_draw(
|
||||
&self.model,
|
||||
Mat4::from_rotation_translation(
|
||||
controller.aim.orientation,
|
||||
controller.aim.position,
|
||||
),
|
||||
Color128::default(),
|
||||
RenderLayer::all(),
|
||||
pub fn update(&mut self, token: &MainThreadToken) {
|
||||
let controller = Input::controller(self.handed);
|
||||
let input_node = self.input.spatial.node().unwrap();
|
||||
input_node.set_enabled(controller.tracked.is_active());
|
||||
let enabled = input_node.enabled();
|
||||
tokio::spawn({
|
||||
// this is suboptimal since it probably allocates a fresh string every frame
|
||||
let handle = self.tracked.clone();
|
||||
async move {
|
||||
handle.set_tracked(enabled).await;
|
||||
}
|
||||
});
|
||||
if enabled {
|
||||
let world_transform = Mat4::from_rotation_translation(
|
||||
controller.aim.orientation.into(),
|
||||
controller.aim.position.into(),
|
||||
);
|
||||
self.input.spatial.set_local_transform_components(
|
||||
self.material
|
||||
.color_tint(if self.capture_manager.capture.upgrade().is_none() {
|
||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||
});
|
||||
self.model.draw(
|
||||
token,
|
||||
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
|
||||
None,
|
||||
None,
|
||||
Transform::from_position_rotation(
|
||||
controller.aim.position,
|
||||
controller.aim.orientation,
|
||||
),
|
||||
);
|
||||
self.input.spatial.set_local_transform(world_transform);
|
||||
}
|
||||
self.datamap.select = controller.trigger;
|
||||
self.datamap.grab = controller.grip;
|
||||
self.datamap.scroll = controller.stick;
|
||||
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
|
||||
|
||||
self.datamap = ControllerDatamap {
|
||||
select: controller.trigger,
|
||||
middle: controller.stick_click.is_active() as u32 as f32,
|
||||
context: controller.is_x2_pressed() as u32 as f32,
|
||||
grab: controller.grip,
|
||||
scroll: controller.stick.into(),
|
||||
};
|
||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
||||
|
||||
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
|
||||
Some(field.distance(space, [0.0; 3].into()).abs())
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.input);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.input, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.input);
|
||||
|
||||
if self.capture_manager.capture.upgrade().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||
self.input
|
||||
.set_handler_order(sorted_handlers.iter().map(|(handler, _)| handler));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
use crate::{
|
||||
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
|
||||
nodes::{
|
||||
input::{hand::Hand, InputMethod, InputType},
|
||||
spatial::Spatial,
|
||||
Node,
|
||||
},
|
||||
use crate::core::client::INTERNAL_CLIENT;
|
||||
use crate::nodes::OwnedNode;
|
||||
use crate::nodes::fields::{Field, FieldTrait};
|
||||
use crate::nodes::input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler};
|
||||
use crate::nodes::{
|
||||
Node,
|
||||
input::{Hand, InputMethod, Joint},
|
||||
spatial::Spatial,
|
||||
};
|
||||
use crate::objects::{ObjectHandle, SpatialRef, Tracked};
|
||||
use color_eyre::eyre::Result;
|
||||
use glam::Mat4;
|
||||
use nanoid::nanoid;
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stardust_xr::schemas::flat::{Hand as FlatHand, Joint};
|
||||
use stardust_xr::values::Datamap;
|
||||
use std::sync::Arc;
|
||||
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
|
||||
use tracing::instrument;
|
||||
use stereokit_rust::material::Material;
|
||||
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
|
||||
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
|
||||
use stereokit_rust::util::Color128;
|
||||
use zbus::Connection;
|
||||
|
||||
use super::{CaptureManager, get_sorted_handlers};
|
||||
|
||||
fn convert_joint(joint: HandJoint) -> Joint {
|
||||
Joint {
|
||||
position: joint.position.into(),
|
||||
rotation: joint.orientation.into(),
|
||||
position: Vec3::from(joint.position).into(),
|
||||
rotation: Quat::from(joint.orientation).into(),
|
||||
radius: joint.radius,
|
||||
distance: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,49 +37,84 @@ struct HandDatamap {
|
||||
}
|
||||
|
||||
pub struct SkHand {
|
||||
_node: Arc<Node>,
|
||||
input: Arc<InputMethod>,
|
||||
_node: OwnedNode,
|
||||
palm_spatial: Arc<Spatial>,
|
||||
palm_object: ObjectHandle<SpatialRef>,
|
||||
handed: Handed,
|
||||
datamap: TypedDatamap<HandDatamap>,
|
||||
input: Arc<InputMethod>,
|
||||
capture_manager: CaptureManager,
|
||||
datamap: HandDatamap,
|
||||
tracked: ObjectHandle<Tracked>,
|
||||
}
|
||||
impl SkHand {
|
||||
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)?;
|
||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||
let (palm_spatial, palm_object) = SpatialRef::create(
|
||||
connection,
|
||||
&("/org/stardustxr/Hand/".to_string()
|
||||
+ match handed {
|
||||
Handed::Left => "left",
|
||||
_ => "right",
|
||||
} + "/palm"),
|
||||
);
|
||||
let tracked = Tracked::new(
|
||||
connection,
|
||||
&("/org/stardustxr/Hand/".to_string()
|
||||
+ match handed {
|
||||
Handed::Left => "left",
|
||||
_ => "right",
|
||||
}),
|
||||
);
|
||||
let _node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||
Spatial::add_to(&_node.0, None, Mat4::IDENTITY, false);
|
||||
let hand = InputDataType::Hand(Hand {
|
||||
right: handed == Handed::Right,
|
||||
..Default::default()
|
||||
});
|
||||
let datamap = Datamap::from_typed(HandDatamap::default())?;
|
||||
let input = InputMethod::add_to(&_node.0, hand, datamap)?;
|
||||
Input::hand_visible(handed, true);
|
||||
|
||||
Ok(SkHand {
|
||||
_node,
|
||||
input,
|
||||
palm_spatial,
|
||||
palm_object,
|
||||
handed,
|
||||
input,
|
||||
tracked,
|
||||
capture_manager: CaptureManager::default(),
|
||||
datamap: Default::default(),
|
||||
})
|
||||
}
|
||||
#[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.input.specialization.lock() {
|
||||
let controller = sk.input_controller(self.handed);
|
||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE)
|
||||
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE);
|
||||
sk.input_hand_visible(self.handed, *self.input.enabled.lock());
|
||||
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]);
|
||||
hand.base.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
|
||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken, material: &mut Material) {
|
||||
let sk_hand = Input::hand(self.handed);
|
||||
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
|
||||
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
|
||||
let input_node = self.input.spatial.node().unwrap();
|
||||
input_node.set_enabled(
|
||||
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
|
||||
&& sk_hand.tracked.is_active(),
|
||||
);
|
||||
let enabled = input_node.enabled();
|
||||
tokio::spawn({
|
||||
// this is suboptimal since it probably allocates a fresh string every frame
|
||||
let handle = self.tracked.clone();
|
||||
async move {
|
||||
handle.set_tracked(enabled).await;
|
||||
}
|
||||
});
|
||||
if enabled {
|
||||
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
||||
hand.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
|
||||
hand.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
|
||||
hand.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
|
||||
|
||||
for (finger, sk_finger) in [
|
||||
(&mut hand.base.index, sk_hand.fingers[1]),
|
||||
(&mut hand.base.middle, sk_hand.fingers[2]),
|
||||
(&mut hand.base.ring, sk_hand.fingers[3]),
|
||||
(&mut hand.base.little, sk_hand.fingers[4]),
|
||||
for (finger, mut sk_finger) in [
|
||||
(&mut hand.index, sk_hand.fingers[1]),
|
||||
(&mut hand.middle, sk_hand.fingers[2]),
|
||||
(&mut hand.ring, sk_hand.fingers[3]),
|
||||
(&mut hand.little, sk_hand.fingers[4]),
|
||||
] {
|
||||
sk_finger[4].radius = 0.0;
|
||||
finger.tip = convert_joint(sk_finger[4]);
|
||||
finger.distal = convert_joint(sk_finger[3]);
|
||||
finger.intermediate = convert_joint(sk_finger[2]);
|
||||
@@ -80,21 +122,77 @@ impl SkHand {
|
||||
finger.metacarpal = convert_joint(sk_finger[0]);
|
||||
}
|
||||
|
||||
hand.base.palm.position = sk_hand.palm.position.into();
|
||||
hand.base.palm.rotation = sk_hand.palm.orientation.into();
|
||||
hand.base.palm.radius =
|
||||
hand.palm.position = Vec3::from(sk_hand.palm.position).into();
|
||||
hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
|
||||
hand.palm.radius =
|
||||
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
||||
|
||||
hand.base.wrist.position = sk_hand.wrist.position.into();
|
||||
hand.base.wrist.rotation = sk_hand.wrist.orientation.into();
|
||||
hand.base.wrist.radius =
|
||||
self.palm_spatial
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
hand.palm.rotation.into(),
|
||||
hand.palm.position.into(),
|
||||
));
|
||||
|
||||
hand.wrist.position = Vec3::from(sk_hand.wrist.position).into();
|
||||
hand.wrist.rotation = Quat::from(sk_hand.wrist.orientation).into();
|
||||
hand.wrist.radius =
|
||||
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
|
||||
|
||||
hand.base.elbow = None;
|
||||
hand.elbow = None;
|
||||
|
||||
let hand_color = if self.capture_manager.capture.upgrade().is_none() {
|
||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||
};
|
||||
material.color_tint(hand_color);
|
||||
}
|
||||
}
|
||||
self.datamap.pinch_strength = sk_hand.pinch_activation;
|
||||
self.datamap.grab_strength = sk_hand.grip_activation;
|
||||
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
|
||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
||||
|
||||
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
|
||||
let InputDataType::Hand(hand) = data else {
|
||||
return None;
|
||||
};
|
||||
let thumb_tip_distance = field.distance(space, hand.thumb.tip.position.into());
|
||||
let index_tip_distance = field.distance(space, hand.index.tip.position.into());
|
||||
let middle_tip_distance = field.distance(space, hand.middle.tip.position.into());
|
||||
let ring_tip_distance = field.distance(space, hand.ring.tip.position.into());
|
||||
|
||||
Some(
|
||||
(thumb_tip_distance * 0.3)
|
||||
+ (index_tip_distance * 0.4)
|
||||
+ (middle_tip_distance * 0.15)
|
||||
+ (ring_tip_distance * 0.15),
|
||||
)
|
||||
};
|
||||
|
||||
self.capture_manager.update_capture(&self.input);
|
||||
self.capture_manager
|
||||
.set_new_capture(&self.input, distance_calculator);
|
||||
self.capture_manager.apply_capture(&self.input);
|
||||
|
||||
if self.capture_manager.capture.upgrade().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||
self.input
|
||||
.set_handler_order(sorted_handlers.iter().map(|(handler, _)| handler));
|
||||
}
|
||||
}
|
||||
impl Drop for SkHand {
|
||||
fn drop(&mut self) {
|
||||
Input::hand_visible(self.handed, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn joint_to_line_point(joint: &Joint, color: Color128) -> LinePoint {
|
||||
LinePoint {
|
||||
pt: Vec3::from(joint.position).into(),
|
||||
thickness: joint.radius * 2.0,
|
||||
color: color.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,342 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{
|
||||
core::client::INTERNAL_CLIENT,
|
||||
nodes::{
|
||||
Node, OwnedNode,
|
||||
fields::{EXPORTED_FIELDS, Field, Shape},
|
||||
spatial::{EXPORTED_SPATIALS, Spatial},
|
||||
},
|
||||
};
|
||||
use glam::{Mat4, vec3};
|
||||
use input::{
|
||||
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
||||
sk_hand::SkHand,
|
||||
};
|
||||
use play_space::PlaySpaceBounds;
|
||||
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
};
|
||||
use stereokit_rust::{
|
||||
material::Material,
|
||||
sk::{DisplayMode, MainThreadToken, Sk},
|
||||
system::{Handed, Input, Key, World},
|
||||
util::Device,
|
||||
};
|
||||
use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath};
|
||||
|
||||
pub mod input;
|
||||
pub mod play_space;
|
||||
|
||||
enum Inputs {
|
||||
XR {
|
||||
controller_left: SkController,
|
||||
controller_right: SkController,
|
||||
hand_left: SkHand,
|
||||
hand_right: SkHand,
|
||||
eye_pointer: Option<EyePointer>,
|
||||
},
|
||||
MousePointer(MousePointer),
|
||||
// Controllers((SkController, SkController)),
|
||||
Hands {
|
||||
left: SkHand,
|
||||
right: SkHand,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct ServerObjects {
|
||||
connection: Connection,
|
||||
hmd: (Arc<Spatial>, ObjectHandle<SpatialRef>),
|
||||
play_space: Option<(Arc<Spatial>, ObjectHandle<SpatialRef>)>,
|
||||
hand_materials: [Material; 2],
|
||||
inputs: Inputs,
|
||||
disable_controllers: bool,
|
||||
disable_hands: bool,
|
||||
}
|
||||
impl ServerObjects {
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
sk: &Sk,
|
||||
hand_materials: [Material; 2],
|
||||
disable_controllers: bool,
|
||||
disable_hands: bool,
|
||||
) -> ServerObjects {
|
||||
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
|
||||
|
||||
let play_space = Some(SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
||||
if play_space.is_some() {
|
||||
let dbus_connection = connection.clone();
|
||||
tokio::task::spawn(async move {
|
||||
PlaySpaceBounds::create(&dbus_connection).await;
|
||||
dbus_connection
|
||||
.request_name("org.stardustxr.PlaySpace")
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
tokio::task::spawn({
|
||||
let connection = connection.clone();
|
||||
async move {
|
||||
connection
|
||||
.request_name("org.stardustxr.Controllers")
|
||||
.await
|
||||
.unwrap();
|
||||
connection
|
||||
.request_name("org.stardustxr.Hands")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
let inputs = if sk.get_active_display_mode() == DisplayMode::MixedReality {
|
||||
Inputs::XR {
|
||||
controller_left: SkController::new(&connection, Handed::Left).unwrap(),
|
||||
controller_right: SkController::new(&connection, Handed::Right).unwrap(),
|
||||
hand_left: SkHand::new(&connection, Handed::Left).unwrap(),
|
||||
hand_right: SkHand::new(&connection, Handed::Right).unwrap(),
|
||||
eye_pointer: Device::has_eye_gaze()
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap(),
|
||||
}
|
||||
} else {
|
||||
Inputs::MousePointer(MousePointer::new().unwrap())
|
||||
};
|
||||
|
||||
ServerObjects {
|
||||
connection,
|
||||
hmd,
|
||||
play_space,
|
||||
hand_materials,
|
||||
inputs,
|
||||
disable_controllers,
|
||||
disable_hands,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
sk: &Sk,
|
||||
token: &MainThreadToken,
|
||||
dbus_connection: &Connection,
|
||||
object_registry: &ObjectRegistry,
|
||||
) {
|
||||
let hmd_pose = Input::get_head();
|
||||
self.hmd
|
||||
.0
|
||||
.set_local_transform(Mat4::from_scale_rotation_translation(
|
||||
vec3(1.0, 1.0, 1.0),
|
||||
hmd_pose.orientation.into(),
|
||||
hmd_pose.position.into(),
|
||||
));
|
||||
|
||||
if let Some(play_space) = self.play_space.as_ref() {
|
||||
let pose = World::get_bounds_pose();
|
||||
play_space
|
||||
.0
|
||||
.set_local_transform(Mat4::from_rotation_translation(
|
||||
pose.orientation.into(),
|
||||
pose.position.into(),
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if sk.get_active_display_mode() != DisplayMode::MixedReality {
|
||||
if Input::key(Key::F6).is_just_inactive() {
|
||||
self.inputs = Inputs::MousePointer(MousePointer::new().unwrap());
|
||||
}
|
||||
// if Input::key(Key::F7).is_just_inactive() {
|
||||
// self.inputs = Inputs::Controllers((
|
||||
// SkController::new(Handed::Left).unwrap(),
|
||||
// SkController::new(Handed::Right).unwrap(),
|
||||
// ));
|
||||
// }
|
||||
// if Input::key(Key::F8).is_just_inactive() {
|
||||
// self.inputs = Inputs::Hands {
|
||||
// left: SkHand::new(&self.connection, Handed::Left).unwrap(),
|
||||
// right: SkHand::new(&self.connection, Handed::Right).unwrap(),
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
match &mut self.inputs {
|
||||
Inputs::XR {
|
||||
controller_left,
|
||||
controller_right,
|
||||
hand_left,
|
||||
hand_right,
|
||||
eye_pointer,
|
||||
} => {
|
||||
if !self.disable_controllers {
|
||||
controller_left.update(token);
|
||||
controller_right.update(token);
|
||||
}
|
||||
Input::hand_visible(Handed::Left, !self.disable_hands);
|
||||
Input::hand_visible(Handed::Right, !self.disable_hands);
|
||||
if !self.disable_hands {
|
||||
hand_left.update(sk, token, &mut self.hand_materials[0]);
|
||||
hand_right.update(sk, token, &mut self.hand_materials[1]);
|
||||
}
|
||||
if let Some(eye_pointer) = eye_pointer {
|
||||
eye_pointer.update();
|
||||
}
|
||||
}
|
||||
Inputs::MousePointer(mouse_pointer) => {
|
||||
mouse_pointer.update(dbus_connection, object_registry)
|
||||
}
|
||||
// Inputs::Controllers((left, right)) => {
|
||||
// left.update(token);
|
||||
// right.update(token);
|
||||
// }
|
||||
Inputs::Hands { left, right } => {
|
||||
left.update(sk, token, &mut self.hand_materials[0]);
|
||||
right.update(sk, token, &mut self.hand_materials[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
|
||||
|
||||
impl<I: Interface> Clone for ObjectHandle<I> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), self.1.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
impl<I: Interface> Drop for ObjectHandle<I> {
|
||||
fn drop(&mut self) {
|
||||
let connection = self.0.clone();
|
||||
let object_path = self.1.clone();
|
||||
tokio::task::spawn(async move {
|
||||
connection.object_server().remove::<I, _>(object_path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpatialRef(u64, OwnedNode);
|
||||
impl SpatialRef {
|
||||
pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<SpatialRef>) {
|
||||
let node = OwnedNode(Arc::new(Node::generate(&INTERNAL_CLIENT, false)));
|
||||
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||
let uid: u64 = rand::random();
|
||||
EXPORTED_SPATIALS.lock().insert(uid, node.0.clone());
|
||||
|
||||
tokio::task::spawn({
|
||||
let connection = connection.clone();
|
||||
let path = path.to_string();
|
||||
async move {
|
||||
connection
|
||||
.object_server()
|
||||
.at(path, Self(uid, node))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
(
|
||||
spatial,
|
||||
ObjectHandle(
|
||||
connection.clone(),
|
||||
OwnedObjectPath::try_from(path.to_string()).unwrap(),
|
||||
PhantomData,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
#[interface(name = "org.stardustxr.SpatialRef")]
|
||||
impl SpatialRef {
|
||||
#[zbus(property)]
|
||||
fn uid(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tracked(bool);
|
||||
impl Tracked {
|
||||
pub fn new(connection: &Connection, path: &str) -> ObjectHandle<Tracked> {
|
||||
tokio::task::spawn({
|
||||
let connection = connection.clone();
|
||||
let path = path.to_string();
|
||||
async move {
|
||||
connection
|
||||
.object_server()
|
||||
.at(path, Self(false))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
ObjectHandle(
|
||||
connection.clone(),
|
||||
OwnedObjectPath::try_from(path.to_string()).unwrap(),
|
||||
PhantomData,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl ObjectHandle<Tracked> {
|
||||
pub async fn set_tracked(&self, is_tracked: bool) -> zbus::Result<()> {
|
||||
let tracked_ref = self
|
||||
.0
|
||||
.object_server()
|
||||
.interface::<_, Tracked>(self.1.as_ref())
|
||||
.await?;
|
||||
let mut tracked = tracked_ref.get_mut().await;
|
||||
if tracked.0 != is_tracked {
|
||||
tracked.0 = is_tracked;
|
||||
tracked
|
||||
.is_tracked_changed(tracked_ref.signal_emitter())
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[interface(name = "org.stardustxr.Tracked")]
|
||||
impl Tracked {
|
||||
#[zbus(property)]
|
||||
fn is_tracked(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldRef(u64, OwnedNode);
|
||||
impl FieldRef {
|
||||
pub fn create(
|
||||
connection: &Connection,
|
||||
path: &str,
|
||||
shape: Shape,
|
||||
) -> (Arc<Field>, ObjectHandle<FieldRef>) {
|
||||
let node = OwnedNode(Arc::new(Node::generate(&INTERNAL_CLIENT, false)));
|
||||
Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||
let field = Field::add_to(&node.0, shape).unwrap();
|
||||
let uid: u64 = rand::random();
|
||||
EXPORTED_FIELDS.lock().insert(uid, node.0.clone());
|
||||
|
||||
tokio::task::spawn({
|
||||
let connection = connection.clone();
|
||||
let path = path.to_string();
|
||||
async move {
|
||||
connection
|
||||
.object_server()
|
||||
.at(path, Self(uid, node))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
(
|
||||
field,
|
||||
ObjectHandle(
|
||||
connection.clone(),
|
||||
OwnedObjectPath::try_from(path.to_string()).unwrap(),
|
||||
PhantomData,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
#[interface(name = "org.stardustxr.FieldRef")]
|
||||
impl FieldRef {
|
||||
#[zbus(property)]
|
||||
fn uid(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +1,33 @@
|
||||
use std::sync::Arc;
|
||||
use stereokit_rust::system::World;
|
||||
use zbus::{Connection, ObjectServer, interface};
|
||||
|
||||
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>,
|
||||
pub struct PlaySpaceBounds;
|
||||
impl PlaySpaceBounds {
|
||||
pub async fn create(connection: &Connection) {
|
||||
connection
|
||||
.object_server()
|
||||
.at("/org/stardustxr/PlaySpace", Self)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
impl Default for PlaySpaceMap {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
play_space: (),
|
||||
size: [0.0; 2].into(),
|
||||
#[interface(name = "org.stardustxr.PlaySpace")]
|
||||
impl PlaySpaceBounds {
|
||||
#[zbus(property)]
|
||||
fn bounds(&self) -> Vec<(f64, f64)> {
|
||||
if (World::has_bounds()
|
||||
&& World::get_bounds_size().x != 0.0
|
||||
&& World::get_bounds_size().y != 0.0)
|
||||
{
|
||||
let bounds = World::get_bounds_size();
|
||||
vec![
|
||||
((bounds.x).into(), (bounds.y).into()),
|
||||
((bounds.x).into(), (-bounds.y).into()),
|
||||
((-bounds.x).into(), (-bounds.y).into()),
|
||||
((-bounds.x).into(), (bounds.y).into()),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,76 +0,0 @@
|
||||
use super::Object;
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Action {
|
||||
// _info: InstanceInfo,
|
||||
_localized_name: String,
|
||||
suggested_bindings: Mutex<FxHashMap<String, String>>,
|
||||
}
|
||||
impl Action {
|
||||
pub fn create_action_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Object::ActionSet(action_set) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
|
||||
bail!("Object not an instance")
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateActionInfo {
|
||||
name: String,
|
||||
localized_name: String,
|
||||
}
|
||||
let info: CreateActionInfo = dbg!(deserialize(data)?);
|
||||
|
||||
let node = Node::create(
|
||||
&node.get_client().unwrap(),
|
||||
node.get_path(),
|
||||
&info.name,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph();
|
||||
node.add_local_signal("suggest_binding", Self::suggest_binding_flex);
|
||||
|
||||
let action = Arc::new(Action {
|
||||
_localized_name: info.localized_name,
|
||||
suggested_bindings: Mutex::new(FxHashMap::default()),
|
||||
});
|
||||
action_set
|
||||
.actions
|
||||
.lock()
|
||||
.insert(info.name, Arc::downgrade(&action));
|
||||
node.openxr_object.set(Object::Action(action)).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn suggest_binding_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Object::Action(action) = node.get_aspect("OpenXR interface", "Action", |n| &n.openxr_object)? else {
|
||||
bail!("Object not an action")
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SuggestBindingArgs {
|
||||
interaction_profile: String,
|
||||
binding: String,
|
||||
}
|
||||
let args: SuggestBindingArgs = dbg!(deserialize(data)?);
|
||||
action
|
||||
.suggested_bindings
|
||||
.lock()
|
||||
.insert(args.interaction_profile, args.binding);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use super::{action::Action, Object};
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActionSet {
|
||||
// _info: InstanceInfo,
|
||||
_localized_name: String,
|
||||
_priority: u32,
|
||||
pub actions: Mutex<FxHashMap<String, Weak<Action>>>,
|
||||
}
|
||||
impl ActionSet {
|
||||
pub fn create_action_set_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
|
||||
bail!("Object not an instance")
|
||||
};
|
||||
let Some(instance) = instance.get() else { bail!("Instance not initialized") };
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateActionSetInfo {
|
||||
name: String,
|
||||
localized_name: String,
|
||||
priority: u32,
|
||||
}
|
||||
let info: CreateActionSetInfo = deserialize(data)?;
|
||||
|
||||
let node = Node::create(
|
||||
&node.get_client().unwrap(),
|
||||
"/openxr/action_set",
|
||||
&info.name,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph();
|
||||
node.add_local_signal("create_action", Action::create_action_flex);
|
||||
|
||||
let action_set = Arc::new(ActionSet {
|
||||
_localized_name: info.localized_name,
|
||||
_priority: info.priority,
|
||||
actions: Mutex::new(FxHashMap::default()),
|
||||
});
|
||||
instance
|
||||
.action_sets
|
||||
.lock()
|
||||
.insert(info.name, Arc::downgrade(&action_set));
|
||||
node.openxr_object
|
||||
.set(Object::ActionSet(action_set))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use super::{action_set::ActionSet, Object};
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use color_eyre::eyre::{bail, eyre, Result};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct InstanceInfo {
|
||||
_app_info: ApplicationInfo,
|
||||
_extension_names: Vec<String>,
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ApplicationInfo {
|
||||
_app_name: String,
|
||||
_app_version: u32,
|
||||
_engine_name: String,
|
||||
_engine_version: u32,
|
||||
_api_version: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Instance {
|
||||
_info: InstanceInfo,
|
||||
pub action_sets: Mutex<FxHashMap<String, Weak<ActionSet>>>,
|
||||
}
|
||||
impl Instance {
|
||||
pub fn setup_instance_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
|
||||
bail!("Object not an instance")
|
||||
};
|
||||
let instance_info = Instance {
|
||||
_info: deserialize(data)?,
|
||||
action_sets: Mutex::new(FxHashMap::default()),
|
||||
};
|
||||
dbg!(&instance_info);
|
||||
instance
|
||||
.set(Arc::new(instance_info))
|
||||
.map_err(|_| eyre!("Instance already set up"))
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
mod action;
|
||||
mod action_set;
|
||||
mod instance;
|
||||
mod session;
|
||||
mod system;
|
||||
|
||||
use self::{
|
||||
action::Action, action_set::ActionSet, instance::Instance, session::Session, system::System,
|
||||
};
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Object {
|
||||
Instance(OnceCell<Arc<Instance>>),
|
||||
System(System),
|
||||
Session(Session),
|
||||
ActionSet(Arc<ActionSet>),
|
||||
Action(Arc<Action>),
|
||||
}
|
||||
|
||||
pub fn create_interface(client: &Arc<Client>) {
|
||||
let node = Node::create(client, "", "openxr", false);
|
||||
node.add_local_signal("setup_instance", Instance::setup_instance_flex);
|
||||
node.add_local_method("get_system", System::get_system_flex);
|
||||
node.add_local_signal("create_action_set", ActionSet::create_action_set_flex);
|
||||
|
||||
node.openxr_object
|
||||
.set(Object::Instance(OnceCell::new()))
|
||||
.unwrap();
|
||||
|
||||
node.add_to_scenegraph();
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use stardust_xr::schemas::flex::deserialize;
|
||||
|
||||
use super::Object;
|
||||
use crate::{core::client::Client, nodes::Node};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Session {
|
||||
// _info: InstanceInfo,
|
||||
}
|
||||
impl Session {
|
||||
pub fn create_session_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
let Object::System(_system) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
|
||||
bail!("Object not a system")
|
||||
};
|
||||
let node = Node::create(
|
||||
&node.get_client().unwrap(),
|
||||
node.get_path(),
|
||||
deserialize(data)?,
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph();
|
||||
let session = Session {};
|
||||
node.openxr_object.set(Object::Session(session)).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
use super::{session::Session, Object};
|
||||
use crate::{core::client::Client, nodes::Node, SK_INFO};
|
||||
use color_eyre::eyre::{bail, eyre, Result};
|
||||
use serde::Serialize;
|
||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum System {
|
||||
Handheld,
|
||||
HeadMounted,
|
||||
}
|
||||
impl System {
|
||||
pub fn from_raw(raw: u32) -> Option<Self> {
|
||||
match raw {
|
||||
1 => Some(System::Handheld),
|
||||
2 => Some(System::HeadMounted),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_system_flex(
|
||||
node: &Node,
|
||||
_calling_client: Arc<Client>,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
// let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
|
||||
// bail!("Object not an instance")
|
||||
// };
|
||||
let system_type: u32 = deserialize(data)?;
|
||||
let system = System::from_raw(system_type).ok_or_else(|| eyre!("No system exists!"))?;
|
||||
let node = Node::create(
|
||||
&node.get_client().unwrap(),
|
||||
node.get_path(),
|
||||
&format!("system{}", system_type),
|
||||
true,
|
||||
)
|
||||
.add_to_scenegraph();
|
||||
node.add_local_method("views", System::views_flex);
|
||||
node.add_local_signal("create_session", Session::create_session_flex);
|
||||
node.openxr_object.set(Object::System(system)).unwrap();
|
||||
|
||||
Ok(serialize(system_type)?)
|
||||
}
|
||||
|
||||
fn views_flex(_node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
|
||||
let view_configuration_type: u64 = deserialize(data)?;
|
||||
let view_count: u32 = match view_configuration_type {
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
1000037000 => 4,
|
||||
1000054000 => 1,
|
||||
_ => bail!("Invalid view config type"),
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct View {
|
||||
recommended_image_rect_width: u32,
|
||||
max_image_rect_width: u32,
|
||||
recommended_image_rect_height: u32,
|
||||
max_image_rect_height: u32,
|
||||
}
|
||||
let sk_info = SK_INFO.get().unwrap();
|
||||
|
||||
Ok(serialize(
|
||||
(0..view_count)
|
||||
.map(|_| View {
|
||||
recommended_image_rect_width: sk_info.display_width,
|
||||
max_image_rect_width: sk_info.display_width,
|
||||
recommended_image_rect_height: sk_info.display_height,
|
||||
max_image_rect_height: sk_info.display_height,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
110
src/session.rs
Normal file
110
src/session.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::core::client::CLIENTS;
|
||||
use crate::core::client_state::ClientStateParsed;
|
||||
#[cfg(feature = "wayland")]
|
||||
use crate::wayland::WAYLAND_DISPLAY;
|
||||
use crate::{CliArgs, STARDUST_INSTANCE};
|
||||
use directories::ProjectDirs;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::Duration;
|
||||
use tokio::task::LocalSet;
|
||||
use tracing::info;
|
||||
|
||||
pub async fn save_session(project_dirs: &ProjectDirs) {
|
||||
let session_id = nanoid::nanoid!();
|
||||
let state_dir = project_dirs.state_dir().unwrap();
|
||||
let session_dir = state_dir.join(&session_id);
|
||||
std::fs::create_dir_all(&session_dir).unwrap();
|
||||
let _ = std::fs::remove_dir_all(state_dir.join("latest"));
|
||||
std::os::unix::fs::symlink(&session_dir, state_dir.join("latest")).unwrap();
|
||||
|
||||
let local_set = LocalSet::new();
|
||||
for client in CLIENTS.get_vec() {
|
||||
let session_dir = session_dir.clone();
|
||||
local_set.spawn_local(async move {
|
||||
tokio::select! {
|
||||
biased;
|
||||
s = client.save_state() => {if let Some(s) = s { s.to_file(&session_dir) }},
|
||||
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
local_set.await;
|
||||
info!("Session ID for restore is {session_id}");
|
||||
}
|
||||
|
||||
pub fn launch_start(cli_args: &CliArgs, project_dirs: &ProjectDirs) -> Vec<Child> {
|
||||
match (&cli_args.restore, &cli_args.startup_script) {
|
||||
(Some(session_id), _) => restore_session(
|
||||
&project_dirs.state_dir().unwrap().join(session_id),
|
||||
cli_args.debug_launched_clients,
|
||||
),
|
||||
(None, Some(startup_script)) => run_script(
|
||||
&startup_script.clone().canonicalize().unwrap_or_default(),
|
||||
cli_args.debug_launched_clients,
|
||||
),
|
||||
(None, None) => run_script(
|
||||
&project_dirs.config_dir().join("startup"),
|
||||
cli_args.debug_launched_clients,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_session(session_dir: &Path, debug_launched_clients: bool) -> Vec<Child> {
|
||||
let Ok(clients) = session_dir.read_dir() else {
|
||||
return Vec::new();
|
||||
};
|
||||
clients
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|c| ClientStateParsed::from_file(&c.path()))
|
||||
.filter_map(ClientStateParsed::launch_command)
|
||||
.filter_map(|c| run_client(c, debug_launched_clients))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run_script(script_path: &Path, debug_launched_clients: bool) -> Vec<Child> {
|
||||
let _ = std::fs::set_permissions(script_path, std::fs::Permissions::from_mode(0o755));
|
||||
let startup_command = Command::new(script_path);
|
||||
run_client(startup_command, debug_launched_clients)
|
||||
.map(|c| vec![c])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn run_client(mut command: Command, debug_launched_clients: bool) -> Option<Child> {
|
||||
command.stdin(Stdio::null());
|
||||
if !debug_launched_clients {
|
||||
command.stdout(Stdio::null());
|
||||
command.stderr(Stdio::null());
|
||||
}
|
||||
for (var, value) in connection_env() {
|
||||
command.env(var, value);
|
||||
}
|
||||
let child = command.spawn().ok()?;
|
||||
Some(child)
|
||||
}
|
||||
|
||||
pub fn connection_env() -> FxHashMap<String, String> {
|
||||
macro_rules! var_env_insert {
|
||||
($env:ident, $name:ident) => {
|
||||
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
|
||||
};
|
||||
}
|
||||
|
||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
||||
var_env_insert!(env, STARDUST_INSTANCE);
|
||||
|
||||
if let Some(flat_wayland_display) = std::env::var_os("WAYLAND_DISPLAY") {
|
||||
env.insert(
|
||||
"FLAT_WAYLAND_DISPLAY".to_string(),
|
||||
flat_wayland_display.to_string_lossy().into_owned(),
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
var_env_insert!(env, WAYLAND_DISPLAY);
|
||||
env.insert("XDG_SESSION_TYPE".to_string(), "wayland".to_string());
|
||||
}
|
||||
env
|
||||
}
|
||||
@@ -1,16 +1,27 @@
|
||||
use crate::wayland::surface::CoreSurface;
|
||||
|
||||
use super::state::{ClientState, WaylandState};
|
||||
use portable_atomic::{AtomicU32, Ordering};
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::XWaylandClientData;
|
||||
use super::{
|
||||
state::{ClientState, WaylandState},
|
||||
utils::{ChildInfoExt, WlSurfaceExt},
|
||||
xdg_shell::surface_panel_item,
|
||||
};
|
||||
use crate::{
|
||||
nodes::items::panel::{ChildInfo, Geometry, SurfaceId},
|
||||
wayland::surface::CoreSurface,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use rand::Rng;
|
||||
use smithay::{
|
||||
backend::renderer::utils::{RendererSurfaceStateUserData, on_commit_buffer_handler},
|
||||
delegate_compositor,
|
||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
|
||||
wayland::compositor::{self, CompositorClientState, CompositorHandler, CompositorState},
|
||||
desktop::PopupKind,
|
||||
reexports::wayland_server::{Client, protocol::wl_surface::WlSurface},
|
||||
wayland::compositor::{
|
||||
CompositorClientState, CompositorHandler, CompositorState, add_post_commit_hook,
|
||||
},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
pub struct ConfiguredSurface;
|
||||
|
||||
impl CompositorHandler for WaylandState {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
@@ -19,35 +30,121 @@ impl CompositorHandler for WaylandState {
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
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);
|
||||
on_commit_buffer_handler::<WaylandState>(surface);
|
||||
|
||||
if let Some(toplevel) = self
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|s| s.wl_surface() == surface)
|
||||
{
|
||||
if !toplevel.is_initial_configure_sent() {
|
||||
debug!("Sending initial configure for toplevel surface");
|
||||
toplevel.send_configure();
|
||||
surface.insert_data(ConfiguredSurface);
|
||||
}
|
||||
}
|
||||
|
||||
self.popup_manager.commit(surface);
|
||||
if let Some(PopupKind::Xdg(popup)) = self.popup_manager.find_popup(surface) {
|
||||
if surface.insert_data(ConfiguredSurface) {
|
||||
debug!("Configuring popup surface");
|
||||
let _ = popup.send_configure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
if let Some(client_state) = client.get_data::<ClientState>() {
|
||||
&client_state.compositor_state
|
||||
} else {
|
||||
#[cfg(feature = "xwayland")]
|
||||
if let Some(xwayland_client_data) = client.get_data::<XWaylandClientData>() {
|
||||
return &xwayland_client_data.compositor_state;
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
|
||||
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
|
||||
let id = rand::thread_rng().gen_range(0..u64::MAX);
|
||||
surface.insert_data(SurfaceId::Child(id));
|
||||
CoreSurface::add_to(surface);
|
||||
let Some(parent_surface_id) = parent.get_data::<SurfaceId>() else {
|
||||
warn!("Parent surface has no SurfaceId");
|
||||
return;
|
||||
};
|
||||
surface.insert_data(Mutex::new(ChildInfo {
|
||||
id,
|
||||
parent: parent_surface_id,
|
||||
geometry: Geometry {
|
||||
origin: [0; 2].into(),
|
||||
size: [256; 2].into(),
|
||||
},
|
||||
z_order: 1,
|
||||
receives_input: false,
|
||||
}));
|
||||
|
||||
let Some(panel_item) = surface_panel_item(parent) else {
|
||||
warn!("Parent has no panel item");
|
||||
return;
|
||||
};
|
||||
let panel_item_weak = Arc::downgrade(&panel_item);
|
||||
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
|
||||
if surface_panel_item(surf).is_some() {
|
||||
return;
|
||||
}
|
||||
unimplemented!()
|
||||
debug!("Linking surface to panel item");
|
||||
surf.insert_data(panel_item_weak.clone());
|
||||
|
||||
let Some(panel_item) = surface_panel_item(surf) else {
|
||||
warn!("Failed to link surface to panel item");
|
||||
return;
|
||||
};
|
||||
|
||||
surf.with_child_info(|_info| {
|
||||
panel_item.backend.reposition_child(surf);
|
||||
});
|
||||
|
||||
debug!("Adding new child to panel item");
|
||||
panel_item.backend.new_child(surf);
|
||||
});
|
||||
|
||||
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
|
||||
let Some(view) = surf
|
||||
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|s| s.lock().ok()?.view())
|
||||
.flatten()
|
||||
else {
|
||||
debug!("No view data for surface");
|
||||
return;
|
||||
};
|
||||
let mut changed = false;
|
||||
surf.with_child_info(|info| {
|
||||
if info.geometry.origin.x != view.offset.x
|
||||
&& info.geometry.origin.y != view.offset.y
|
||||
{
|
||||
changed = true;
|
||||
debug!("Surface position changed");
|
||||
}
|
||||
if info.geometry.size.x != view.dst.w as u32
|
||||
&& info.geometry.size.y != view.dst.h as u32
|
||||
{
|
||||
changed = true;
|
||||
debug!("Surface size changed");
|
||||
}
|
||||
info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into();
|
||||
});
|
||||
|
||||
let Some(panel_item) = surface_panel_item(surf) else {
|
||||
return;
|
||||
};
|
||||
if changed {
|
||||
debug!("Repositioning child due to geometry change");
|
||||
panel_item.backend.reposition_child(surf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn destroyed(&mut self, surface: &WlSurface) {
|
||||
let Some(panel_item) = surface_panel_item(surface) else {
|
||||
return;
|
||||
};
|
||||
if surface.get_child_info().is_some() {
|
||||
debug!("Dropping destroyed child surface");
|
||||
panel_item.backend.drop_child(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use smithay::reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
protocol::{
|
||||
wl_data_device::{
|
||||
Request::{Release, SetSelection, StartDrag},
|
||||
@@ -13,7 +14,6 @@ use smithay::reexports::wayland_server::{
|
||||
WlDataSource,
|
||||
},
|
||||
},
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
};
|
||||
|
||||
use super::state::WaylandState;
|
||||
|
||||
@@ -13,8 +13,8 @@ use smithay::{
|
||||
Mode as KdeMode, OrgKdeKwinServerDecoration,
|
||||
},
|
||||
wayland_server::{
|
||||
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle,
|
||||
GlobalDispatch, New, Resource, WEnum, Weak,
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak,
|
||||
protocol::wl_surface::WlSurface,
|
||||
},
|
||||
},
|
||||
wayland::shell::{self, kde::decoration::KdeDecorationHandler},
|
||||
@@ -90,7 +90,8 @@ impl KdeDecorationHandler for WaylandState {
|
||||
decoration: &OrgKdeKwinServerDecoration,
|
||||
mode: WEnum<KdeMode>,
|
||||
) {
|
||||
decoration.mode(mode.into_result().unwrap());
|
||||
let Ok(mode) = mode.into_result() else { return };
|
||||
decoration.mode(mode);
|
||||
}
|
||||
}
|
||||
delegate_kde_decoration!(WaylandState);
|
||||
|
||||
144
src/wayland/drm.rs
Normal file
144
src/wayland/drm.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// Re-export only the actual code, and then only use this re-export
|
||||
// The `generated` module below is just some boilerplate to properly isolate stuff
|
||||
// and avoid exposing internal details.
|
||||
//
|
||||
// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
|
||||
pub use generated::wl_drm;
|
||||
|
||||
#[allow(non_upper_case_globals, non_camel_case_types)]
|
||||
mod generated {
|
||||
use smithay::reexports::wayland_server::{self, protocol::*};
|
||||
|
||||
pub mod __interfaces {
|
||||
use smithay::reexports::wayland_server::protocol::__interfaces::*;
|
||||
wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml");
|
||||
}
|
||||
use self::__interfaces::*;
|
||||
|
||||
wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml");
|
||||
}
|
||||
|
||||
use super::state::WaylandState;
|
||||
use smithay::{
|
||||
backend::allocator::{
|
||||
Fourcc, Modifier,
|
||||
dmabuf::{Dmabuf, DmabufFlags},
|
||||
},
|
||||
reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
},
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
impl GlobalDispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
|
||||
fn bind(
|
||||
state: &mut WaylandState,
|
||||
_dh: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<wl_drm::WlDrm>,
|
||||
_global_data: &(),
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
let drm_instance = data_init.init(resource, ());
|
||||
|
||||
drm_instance.device("/dev/dri/renderD128".to_string());
|
||||
if drm_instance.version() >= 2 {
|
||||
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
|
||||
}
|
||||
for format in state.drm_formats.iter() {
|
||||
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
|
||||
drm_instance.format(converted as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_view(_client: Client, _global_dataa: &()) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
|
||||
fn request(
|
||||
state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
drm: &wl_drm::WlDrm,
|
||||
request: wl_drm::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
wl_drm::Request::Authenticate { .. } => drm.authenticated(),
|
||||
wl_drm::Request::CreateBuffer { .. } => drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
String::from("Flink handles are unsupported, use PRIME"),
|
||||
),
|
||||
wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
String::from("Flink handles are unsupported, use PRIME"),
|
||||
),
|
||||
wl_drm::Request::CreatePrimeBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
..
|
||||
} => {
|
||||
let format = match Fourcc::try_from(format) {
|
||||
Ok(format) => {
|
||||
if !state.drm_formats.contains(&format) {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format not advertised by wl_drm"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
format
|
||||
}
|
||||
Err(_) => {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format unknown / not advertised by wl_drm"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if width < 1 || height < 1 {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("width or height not positive"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut dma = Dmabuf::builder(
|
||||
(width, height),
|
||||
format,
|
||||
Modifier::Invalid,
|
||||
DmabufFlags::empty(),
|
||||
);
|
||||
dma.add_plane(name, 0, offset0 as u32, stride0 as u32);
|
||||
match dma.build() {
|
||||
Some(dmabuf) => {
|
||||
state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap();
|
||||
data_init.init(id, dmabuf);
|
||||
}
|
||||
None => {
|
||||
// Buffer import failed. The protocol documentation heavily implies killing the
|
||||
// client is the right thing to do here.
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidName,
|
||||
"dmabuf global was destroyed on server",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,42 +2,44 @@ mod compositor;
|
||||
mod data_device;
|
||||
mod decoration;
|
||||
mod seat;
|
||||
mod shaders;
|
||||
mod state;
|
||||
mod surface;
|
||||
// mod xdg_activation;
|
||||
mod drm;
|
||||
mod utils;
|
||||
mod xdg_shell;
|
||||
#[cfg(feature = "xwayland")]
|
||||
pub mod xwayland;
|
||||
|
||||
use self::{state::WaylandState, surface::CORE_SURFACES};
|
||||
use crate::wayland::seat::SeatData;
|
||||
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 color_eyre::eyre::{Result, ensure};
|
||||
use parking_lot::Mutex;
|
||||
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 smithay::{
|
||||
backend::{
|
||||
allocator::dmabuf::Dmabuf,
|
||||
egl::EGLContext,
|
||||
renderer::{ImportDma, Renderer, gles::GlesRenderer},
|
||||
},
|
||||
output::Output,
|
||||
reexports::wayland_server::{Display, DisplayHandle, ListeningSocket},
|
||||
wayland::dmabuf,
|
||||
};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
os::unix::{net::UnixListener, prelude::FromRawFd},
|
||||
sync::Arc,
|
||||
ffi::{OsStr, c_void},
|
||||
os::fd::AsFd,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use stereokit as sk;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use stereokit_rust::system::{Backend, BackendGraphics};
|
||||
use tokio::{
|
||||
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
|
||||
io::unix::AsyncFd,
|
||||
sync::{
|
||||
Notify,
|
||||
mpsc::{self, UnboundedReceiver},
|
||||
},
|
||||
task::AbortHandle,
|
||||
};
|
||||
use tracing::{debug, debug_span, info, instrument};
|
||||
use tracing::{debug_span, info, instrument};
|
||||
|
||||
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
|
||||
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
|
||||
pub static WAYLAND_DISPLAY: OnceLock<String> = OnceLock::new();
|
||||
|
||||
struct EGLRawHandles {
|
||||
display: *const c_void,
|
||||
@@ -46,31 +48,26 @@ struct EGLRawHandles {
|
||||
}
|
||||
fn get_sk_egl() -> Result<EGLRawHandles> {
|
||||
ensure!(
|
||||
unsafe { sk::sys::backend_graphics_get() }
|
||||
== sk::sys::backend_graphics__backend_graphics_opengles_egl,
|
||||
Backend::graphics() == BackendGraphics::OpenGLESEGL,
|
||||
"StereoKit is not running using EGL!"
|
||||
);
|
||||
|
||||
Ok(unsafe {
|
||||
EGLRawHandles {
|
||||
display: sk::sys::backend_opengl_egl_get_display() as *const c_void,
|
||||
config: sk::sys::backend_opengl_egl_get_config() as *const c_void,
|
||||
context: sk::sys::backend_opengl_egl_get_context() as *const c_void,
|
||||
display: stereokit_rust::system::backend_opengl_egl_get_display() as *const c_void,
|
||||
config: stereokit_rust::system::backend_opengl_egl_get_config() as *const c_void,
|
||||
context: stereokit_rust::system::backend_opengl_egl_get_context() as *const c_void,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static GLOBAL_DESTROY_QUEUE: OnceCell<mpsc::Sender<GlobalId>> = OnceCell::new();
|
||||
|
||||
pub struct Wayland {
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
pub socket_name: String,
|
||||
join_handle: JoinHandle<Result<()>>,
|
||||
flush_notify: Arc<Notify>,
|
||||
client_listener: AbortHandle,
|
||||
client_dispatcher: AbortHandle,
|
||||
renderer: GlesRenderer,
|
||||
dmabuf_rx: UnboundedReceiver<Dmabuf>,
|
||||
wayland_state: Arc<Mutex<WaylandState>>,
|
||||
#[cfg(feature = "xwayland")]
|
||||
pub xwayland_state: xwayland::XWaylandState,
|
||||
output: Output,
|
||||
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
|
||||
}
|
||||
impl Wayland {
|
||||
pub fn new() -> Result<Self> {
|
||||
@@ -87,117 +84,126 @@ impl Wayland {
|
||||
let display_handle = display.handle();
|
||||
|
||||
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
|
||||
let display = Arc::new(Mutex::new(display));
|
||||
#[cfg(feature = "xwayland")]
|
||||
let xwayland_state = xwayland::XWaylandState::create(&display_handle).unwrap();
|
||||
let wayland_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 wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx);
|
||||
let output = wayland_state.lock().output.clone();
|
||||
|
||||
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");
|
||||
let socket_name = socket
|
||||
.socket_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.map(ToString::to_string);
|
||||
if let Some(socket_name) = &socket_name {
|
||||
let _ = WAYLAND_DISPLAY.set(socket_name.clone());
|
||||
}
|
||||
info!(socket_name, "Wayland active");
|
||||
|
||||
let join_handle = Wayland::start_loop(
|
||||
display.clone(),
|
||||
socket,
|
||||
wayland_state.clone(),
|
||||
global_destroy_queue,
|
||||
)?;
|
||||
let flush_notify = Arc::new(Notify::new());
|
||||
let client_listener = task::new(
|
||||
|| "Wayland client listener loop",
|
||||
Wayland::client_listener_loop(display_handle, socket, wayland_state.clone()),
|
||||
)?
|
||||
.abort_handle();
|
||||
let client_dispatcher = task::new(
|
||||
|| "Wayland dispatch client loop",
|
||||
Wayland::dispatch_client_loop(display, flush_notify.clone(), wayland_state),
|
||||
)?
|
||||
.abort_handle();
|
||||
|
||||
Ok(Wayland {
|
||||
display,
|
||||
socket_name,
|
||||
join_handle,
|
||||
flush_notify,
|
||||
client_listener,
|
||||
client_dispatcher,
|
||||
renderer,
|
||||
output,
|
||||
dmabuf_rx,
|
||||
wayland_state,
|
||||
#[cfg(feature = "xwayland")]
|
||||
xwayland_state,
|
||||
})
|
||||
}
|
||||
|
||||
fn start_loop(
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
async fn client_listener_loop(
|
||||
mut display_handle: DisplayHandle,
|
||||
socket: ListeningSocket,
|
||||
state: Arc<Mutex<WaylandState>>,
|
||||
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
|
||||
) -> Result<JoinHandle<Result<()>>> {
|
||||
let listen_async =
|
||||
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
|
||||
) -> Result<()> {
|
||||
let async_fd = AsyncFd::new(socket.as_fd())?;
|
||||
loop {
|
||||
let mut guard = async_fd.readable().await?;
|
||||
let Ok(Some(stream)) = socket.accept() else {
|
||||
guard.clear_ready();
|
||||
continue;
|
||||
};
|
||||
|
||||
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?;
|
||||
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
|
||||
let stream = tokio::net::UnixStream::from_std(stream)?;
|
||||
let pid = stream.peer_cred().ok().and_then(|c| c.pid());
|
||||
|
||||
let dh1 = display.lock().handle();
|
||||
let mut dh2 = dh1.clone();
|
||||
// New client connected
|
||||
let client_state = Arc::new(ClientState {
|
||||
pid,
|
||||
id: OnceLock::new(),
|
||||
compositor_state: Default::default(),
|
||||
seat: state.lock().seat.clone(),
|
||||
});
|
||||
let _client = display_handle.insert_client(stream.into_std()?, client_state.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
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_state = Arc::new(ClientState {
|
||||
compositor_state: Default::default(),
|
||||
display: Arc::downgrade(&display),
|
||||
seat: SeatData::new(&dh1)
|
||||
});
|
||||
let client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
|
||||
client_state.seat.client.set(client.id()).unwrap();
|
||||
}
|
||||
e = dispatch_poll_listener.readable() => { // Dispatch
|
||||
let mut guard = e?;
|
||||
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();
|
||||
}
|
||||
async fn dispatch_client_loop(
|
||||
mut display: Display<WaylandState>,
|
||||
flush_notify: Arc<Notify>,
|
||||
state: Arc<Mutex<WaylandState>>,
|
||||
) -> std::io::Result<()> {
|
||||
loop {
|
||||
let poll_fd = display.backend().poll_fd();
|
||||
let async_fd = AsyncFd::new(poll_fd)?;
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = async_fd.readable() => {
|
||||
drop(async_fd);
|
||||
let _span = debug_span!("Dispatch wayland event");
|
||||
let _span = _span.enter();
|
||||
let _ = display.dispatch_clients(&mut *state.lock());
|
||||
let _ = display.flush_clients();
|
||||
}
|
||||
_ = flush_notify.notified() => {
|
||||
drop(async_fd);
|
||||
let _ = display.flush_clients();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "Wayland frame", skip(self))]
|
||||
pub fn update(&mut self) {
|
||||
while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
|
||||
if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
|
||||
if let Some(notifier) = notifier {
|
||||
notifier.failed();
|
||||
}
|
||||
}
|
||||
})?)
|
||||
}
|
||||
|
||||
#[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() {
|
||||
core_surface.process(sk, &mut self.renderer);
|
||||
core_surface.process(&mut self.renderer);
|
||||
}
|
||||
let _ = self.renderer.cleanup_texture_cache();
|
||||
|
||||
self.display.lock().flush_clients().unwrap();
|
||||
self.flush_notify.notify_waiters();
|
||||
}
|
||||
|
||||
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
|
||||
let state = self.wayland_state.lock();
|
||||
|
||||
pub fn frame_event(&self) {
|
||||
for core_surface in CORE_SURFACES.get_valid_contents() {
|
||||
core_surface.frame(sk, state.output.clone());
|
||||
core_surface.frame(self.output.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_context_current(&self) {
|
||||
unsafe {
|
||||
self.renderer.egl_context().make_current().unwrap();
|
||||
let _ = self.renderer.egl_context().make_current();
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for Wayland {
|
||||
fn drop(&mut self) {
|
||||
self.join_handle.abort();
|
||||
self.client_listener.abort();
|
||||
self.client_dispatcher.abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,553 +1,313 @@
|
||||
use super::{
|
||||
state::{ClientState, WaylandState},
|
||||
surface::CoreSurface,
|
||||
GLOBAL_DESTROY_QUEUE, SERIAL_COUNTER,
|
||||
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
|
||||
use crate::{
|
||||
core::task,
|
||||
nodes::items::panel::{Backend, Geometry, KEYMAPS, PanelItem},
|
||||
};
|
||||
use crate::core::task;
|
||||
use color_eyre::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 rustc_hash::FxHashMap;
|
||||
use slotmap::KeyData;
|
||||
use smithay::{
|
||||
input::keyboard::{KeymapFile, ModifiersState},
|
||||
reexports::wayland_server::{
|
||||
backend::{ClientId, GlobalId, ObjectId},
|
||||
protocol::{
|
||||
wl_keyboard::{self, KeyState, WlKeyboard},
|
||||
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, Weak as WlWeak,
|
||||
backend::input::{AxisRelativeDirection, ButtonState, KeyState},
|
||||
delegate_seat,
|
||||
input::{
|
||||
Seat, SeatHandler,
|
||||
keyboard::{FilterResult, LedState},
|
||||
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, CursorImageSurfaceData, MotionEvent},
|
||||
touch::{self, DownEvent, UpEvent},
|
||||
},
|
||||
reexports::wayland_server::{Resource, Weak as WlWeak, protocol::wl_surface::WlSurface},
|
||||
utils::SERIAL_COUNTER,
|
||||
wayland::compositor,
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tokio::sync::watch;
|
||||
use tracing::{debug, warn};
|
||||
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
|
||||
|
||||
pub struct KeyboardInfo {
|
||||
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),
|
||||
mods: ModifiersState::default(),
|
||||
keys: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
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,
|
||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
||||
};
|
||||
let xkb_key_state = match state {
|
||||
0 => xkb::KeyDirection::Up,
|
||||
1 => xkb::KeyDirection::Down,
|
||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
||||
};
|
||||
let state_components = self.state.update_key(key + 8, xkb_key_state);
|
||||
if state_components != 0 {
|
||||
self.mods.update_with(&self.state);
|
||||
keyboard.modifiers(
|
||||
0,
|
||||
self.mods.serialized.depressed,
|
||||
self.mods.serialized.latched,
|
||||
self.mods.serialized.locked,
|
||||
0,
|
||||
);
|
||||
}
|
||||
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 {}
|
||||
impl SeatHandler for WaylandState {
|
||||
type PointerFocus = WlSurface;
|
||||
type KeyboardFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion(Vector2<f32>),
|
||||
Button {
|
||||
button: u32,
|
||||
state: u32,
|
||||
},
|
||||
Scroll {
|
||||
axis_continuous: Option<Vector2<f32>>,
|
||||
axis_discrete: Option<Vector2<f32>>,
|
||||
},
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum KeyboardEvent {
|
||||
Keymap,
|
||||
Key { key: u32, state: u32 },
|
||||
}
|
||||
|
||||
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
struct SurfaceInfo {
|
||||
wl_surface: WlWeak<WlSurface>,
|
||||
cursor_sender: watch::Sender<Option<CursorInfo>>,
|
||||
pointer_queue: VecDeque<PointerEvent>,
|
||||
pointer_latest_event: Instant,
|
||||
keyboard_queue: VecDeque<KeyboardEvent>,
|
||||
keyboard_info: Option<KeyboardInfo>,
|
||||
}
|
||||
impl SurfaceInfo {
|
||||
fn new(wl_surface: &WlSurface, cursor_sender: watch::Sender<Option<CursorInfo>>) -> Self {
|
||||
SurfaceInfo {
|
||||
wl_surface: wl_surface.downgrade(),
|
||||
cursor_sender,
|
||||
pointer_queue: VecDeque::new(),
|
||||
pointer_latest_event: Instant::now(),
|
||||
keyboard_queue: VecDeque::new(),
|
||||
keyboard_info: None,
|
||||
}
|
||||
fn seat_state(&mut self) -> &mut smithay::input::SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
fn flush(&self) {
|
||||
if let Some(client) = self.wl_surface.upgrade().ok().and_then(|s| s.client()) {
|
||||
if let Some(client_data) = client.get_data::<ClientState>() {
|
||||
client_data.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool {
|
||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
||||
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
|
||||
let Some(focus_size) = core_surface.size() else { return false; };
|
||||
|
||||
if !self.pointer_queue.is_empty() {
|
||||
self.pointer_latest_event = Instant::now();
|
||||
}
|
||||
while let Some(event) = self.pointer_queue.pop_front() {
|
||||
match (locked, event) {
|
||||
(false, PointerEvent::Motion(pos)) => {
|
||||
pointer.enter(
|
||||
SERIAL_COUNTER.inc(),
|
||||
&focus,
|
||||
(pos.x as f64).clamp(0.0, focus_size.x as f64),
|
||||
(pos.y as f64).clamp(0.0, focus_size.y as f64),
|
||||
);
|
||||
locked = true;
|
||||
}
|
||||
(true, PointerEvent::Motion(pos)) => {
|
||||
pointer.motion(
|
||||
0,
|
||||
(pos.x as f64).clamp(0.0, focus_size.x as f64),
|
||||
(pos.y as f64).clamp(0.0, focus_size.y as f64),
|
||||
);
|
||||
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
|
||||
pointer.frame();
|
||||
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&Self::KeyboardFocus>) {}
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
||||
self.seat.cursor_info_tx.send_modify(|c| match image {
|
||||
CursorImageStatus::Hidden => c.surface = None,
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
CoreSurface::add_to(&surface);
|
||||
compositor::with_states(&surface, |data| {
|
||||
if let Some(cursor_attributes) = data.data_map.get::<CursorImageSurfaceData>() {
|
||||
let hotspot = cursor_attributes.lock().unwrap().hotspot;
|
||||
c.hotspot_x = hotspot.x;
|
||||
c.hotspot_y = hotspot.y;
|
||||
}
|
||||
}
|
||||
(true, PointerEvent::Button { button, state }) => {
|
||||
pointer.button(
|
||||
0,
|
||||
0,
|
||||
button,
|
||||
match state {
|
||||
0 => ButtonState::Released,
|
||||
1 => ButtonState::Pressed,
|
||||
_ => continue,
|
||||
},
|
||||
);
|
||||
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
|
||||
pointer.frame();
|
||||
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
||||
core_surface.set_material_offset(1);
|
||||
}
|
||||
}
|
||||
(
|
||||
true,
|
||||
PointerEvent::Scroll {
|
||||
axis_continuous,
|
||||
axis_discrete,
|
||||
},
|
||||
) => {
|
||||
if let Some(axis_continuous) = axis_continuous {
|
||||
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
|
||||
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64);
|
||||
}
|
||||
if pointer.version() >= wl_pointer::EVT_AXIS_DISCRETE_SINCE {
|
||||
if let Some(axis_discrete) = axis_discrete {
|
||||
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
|
||||
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32);
|
||||
}
|
||||
}
|
||||
if pointer.version() >= wl_pointer::EVT_AXIS_STOP_SINCE
|
||||
&& axis_discrete.is_none()
|
||||
&& axis_continuous.is_none()
|
||||
{
|
||||
pointer.axis_stop(0, Axis::HorizontalScroll);
|
||||
pointer.axis_stop(0, Axis::VerticalScroll);
|
||||
}
|
||||
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
|
||||
pointer.frame();
|
||||
}
|
||||
}
|
||||
(locked, event) => {
|
||||
warn!(locked, ?event, "Invalid pointer event!");
|
||||
}
|
||||
});
|
||||
c.surface = Some(surface.downgrade())
|
||||
}
|
||||
}
|
||||
if self.pointer_latest_event.elapsed() > POINTER_EVENT_TIMEOUT {
|
||||
pointer.leave(SERIAL_COUNTER.inc(), &focus);
|
||||
locked = false;
|
||||
}
|
||||
self.flush();
|
||||
|
||||
locked
|
||||
}
|
||||
fn handle_keyboard_events(&mut self, keyboard: &WlKeyboard, mut locked: bool) -> bool {
|
||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
||||
let Some(info) = self.keyboard_info.as_mut() else { return true; };
|
||||
|
||||
if !locked {
|
||||
keyboard.enter(0, &focus, vec![]);
|
||||
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
|
||||
keyboard.repeat_info(0, 0);
|
||||
}
|
||||
locked = info.keymap.send(keyboard).is_ok();
|
||||
}
|
||||
while let Some(event) = self.keyboard_queue.pop_front() {
|
||||
debug!(locked, ?event, "Process keyboard event");
|
||||
match (locked, event) {
|
||||
(true, KeyboardEvent::Keymap) => {
|
||||
let _ = info.keymap.send(keyboard);
|
||||
}
|
||||
(true, KeyboardEvent::Key { key, state }) => {
|
||||
if let Ok(key_count) = info.process(key, state, keyboard) {
|
||||
if key_count == 0 {
|
||||
keyboard.leave(SERIAL_COUNTER.inc(), &focus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(locked, event) => {
|
||||
warn!(locked, ?event, "Invalid keyboard event!");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.flush();
|
||||
locked
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SeatData {
|
||||
pub client: OnceCell<ClientId>,
|
||||
global_id: OnceCell<GlobalId>,
|
||||
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
|
||||
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
|
||||
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
|
||||
touch: OnceCell<WlTouch>,
|
||||
}
|
||||
impl SeatData {
|
||||
pub fn new(dh: &DisplayHandle) -> Arc<Self> {
|
||||
let seat_data = Arc::new(SeatData {
|
||||
client: OnceCell::new(),
|
||||
global_id: OnceCell::new(),
|
||||
surfaces: Mutex::new(FxHashMap::default()),
|
||||
pointer: OnceCell::new(),
|
||||
keyboard: OnceCell::new(),
|
||||
touch: OnceCell::new(),
|
||||
});
|
||||
|
||||
seat_data
|
||||
.global_id
|
||||
.set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone()))
|
||||
.unwrap();
|
||||
|
||||
seat_data
|
||||
}
|
||||
|
||||
pub fn set_keymap_str(&self, keymap: &str, surfaces: Vec<WlSurface>) -> Result<()> {
|
||||
let context = xkb::Context::new(0);
|
||||
let keymap =
|
||||
Keymap::new_from_string(&context, keymap.to_string(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
|
||||
.ok_or_else(|| eyre!("Keymap is not valid"))?;
|
||||
self.set_keymap(&keymap, surfaces);
|
||||
Ok(())
|
||||
}
|
||||
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));
|
||||
|
||||
if *focus.lock() == surface.id() {
|
||||
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
||||
surface_info.pointer_queue.push_back(event);
|
||||
drop(surfaces);
|
||||
self.handle_pointer_events();
|
||||
}
|
||||
pub fn keyboard_event(&self, surface: &WlSurface, event: KeyboardEvent) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
||||
surface_info.keyboard_queue.push_back(event);
|
||||
drop(surfaces);
|
||||
self.handle_keyboard_events();
|
||||
}
|
||||
|
||||
fn handle_pointer_events(&self) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some((pointer, pointer_focus)) = self.pointer.get() else {return};
|
||||
let mut pointer_focus = pointer_focus.lock();
|
||||
|
||||
loop {
|
||||
let locked = !pointer_focus.is_null();
|
||||
// Pick a pointer to focus on if there is none
|
||||
if pointer_focus.is_null() {
|
||||
*pointer_focus = surfaces
|
||||
.iter()
|
||||
.filter(|(_k, v)| !v.pointer_queue.is_empty())
|
||||
.map(|(k, _v)| k)
|
||||
.choose(&mut thread_rng())
|
||||
.cloned()
|
||||
.unwrap_or(ObjectId::null());
|
||||
}
|
||||
if pointer_focus.is_null() {
|
||||
// If there's still none, guess we're done with pointer events for the time being
|
||||
break;
|
||||
}
|
||||
let Some(surface_info) = surfaces.get_mut(&pointer_focus) else {break};
|
||||
if surface_info.handle_pointer_events(pointer, locked) {
|
||||
// We haven't gotten to a point where we can switch the focus
|
||||
break;
|
||||
} else {
|
||||
*pointer_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_keyboard_events(&self) {
|
||||
let mut surfaces = self.surfaces.lock();
|
||||
let Some((keyboard, keyboard_focus)) = self.keyboard.get() else {return};
|
||||
let mut keyboard_focus = keyboard_focus.lock();
|
||||
loop {
|
||||
let locked = !keyboard_focus.is_null();
|
||||
// Pick a keyboard to focus on if there is none
|
||||
if keyboard_focus.is_null() {
|
||||
*keyboard_focus = surfaces
|
||||
.iter()
|
||||
.filter(|(_k, v)| v.keyboard_info.is_some())
|
||||
.filter(|(_k, v)| !v.keyboard_queue.is_empty())
|
||||
.map(|(k, _v)| k)
|
||||
.choose(&mut thread_rng())
|
||||
.cloned()
|
||||
.unwrap_or(ObjectId::null());
|
||||
}
|
||||
// If there's still none, guess we're done with keyboard events for the time being
|
||||
let Some(surface_info) = surfaces.get_mut(&keyboard_focus) else {break};
|
||||
if surface_info.handle_keyboard_events(keyboard, locked) {
|
||||
// We haven't gotten to a point where we can switch the focus
|
||||
break;
|
||||
} else {
|
||||
*keyboard_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_surface(&self, surface: &WlSurface) -> watch::Receiver<Option<CursorInfo>> {
|
||||
let (tx, rx) = watch::channel(None);
|
||||
self.surfaces
|
||||
.lock()
|
||||
.insert(surface.id(), SurfaceInfo::new(surface, tx));
|
||||
|
||||
rx
|
||||
}
|
||||
pub fn drop_surface(&self, surface: &WlSurface) {
|
||||
self.surfaces.lock().remove(&surface.id());
|
||||
if let Some((_, pointer_focus)) = self.pointer.get() {
|
||||
let mut pointer_focus = pointer_focus.lock();
|
||||
if *pointer_focus == surface.id() {
|
||||
*pointer_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
if let Some((_, keyboard_focus)) = self.keyboard.get() {
|
||||
let mut keyboard_focus = keyboard_focus.lock();
|
||||
if *keyboard_focus == surface.id() {
|
||||
*keyboard_focus = ObjectId::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for SeatData {
|
||||
fn drop(&mut self) {
|
||||
let id = self.global_id.take().unwrap();
|
||||
let _ = task::new(|| "global destroy queue garbage collection", async move {
|
||||
GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
fn led_state_changed(&mut self, _seat: &Seat<Self>, _led_state: LedState) {}
|
||||
}
|
||||
delegate_seat!(WaylandState);
|
||||
|
||||
pub fn handle_cursor<B: Backend>(
|
||||
panel_item: &Arc<PanelItem<B>>,
|
||||
mut cursor: watch::Receiver<CursorInfo>,
|
||||
) {
|
||||
let panel_item_weak = Arc::downgrade(panel_item);
|
||||
let _ = task::new(|| "cursor handler", async move {
|
||||
while cursor.changed().await.is_ok() {
|
||||
let Some(panel_item) = panel_item_weak.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
let cursor_info = cursor.borrow();
|
||||
panel_item.set_cursor(cursor_info.cursor_data());
|
||||
}
|
||||
});
|
||||
}
|
||||
pub struct CursorInfo {
|
||||
pub surface: WlWeak<WlSurface>,
|
||||
pub surface: Option<WlWeak<WlSurface>>,
|
||||
pub hotspot_x: i32,
|
||||
pub hotspot_y: i32,
|
||||
}
|
||||
impl CursorInfo {
|
||||
pub fn cursor_data(&self) -> Option<(Vector2<u32>, Vector2<i32>)> {
|
||||
let cursor_size = CoreSurface::from_wl_surface(&self.surface.upgrade().ok()?)?.size()?;
|
||||
Some((cursor_size, [self.hotspot_x, self.hotspot_y].into()))
|
||||
pub fn cursor_data(&self) -> Option<Geometry> {
|
||||
let cursor_size = self.surface.as_ref()?.upgrade().ok()?.get_size()?;
|
||||
Some(Geometry {
|
||||
origin: [self.hotspot_x, self.hotspot_y].into(),
|
||||
size: cursor_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn bind(
|
||||
_state: &mut WaylandState,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<WlSeat>,
|
||||
data: &Arc<SeatData>,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
let resource = data_init.init(resource, data.clone());
|
||||
|
||||
if resource.version() >= EVT_NAME_SINCE {
|
||||
resource.name(nanoid!());
|
||||
pub struct SeatWrapper {
|
||||
wayland_state: Weak<Mutex<WaylandState>>,
|
||||
cursor_info_tx: watch::Sender<CursorInfo>,
|
||||
pub cursor_info_rx: watch::Receiver<CursorInfo>,
|
||||
seat: Seat<WaylandState>,
|
||||
touches: Mutex<FxHashMap<u32, WlWeak<WlSurface>>>,
|
||||
}
|
||||
impl SeatWrapper {
|
||||
pub fn new(wayland_state: Weak<Mutex<WaylandState>>, seat: Seat<WaylandState>) -> Self {
|
||||
let (cursor_info_tx, cursor_info_rx) = watch::channel(CursorInfo {
|
||||
surface: None,
|
||||
hotspot_x: 0,
|
||||
hotspot_y: 0,
|
||||
});
|
||||
SeatWrapper {
|
||||
wayland_state,
|
||||
cursor_info_tx,
|
||||
cursor_info_rx,
|
||||
seat,
|
||||
touches: Mutex::new(FxHashMap::default()),
|
||||
}
|
||||
|
||||
resource.capabilities(Capability::Pointer | Capability::Keyboard);
|
||||
}
|
||||
|
||||
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
|
||||
let Some(seat_client) = data.client.get().cloned() else {return false};
|
||||
client.id() == seat_client
|
||||
pub fn unfocus_internal_state(&self, surface: &WlSurface) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
self.unfocus(surface, &mut state.lock());
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlSeat,
|
||||
request: wl_seat::Request,
|
||||
data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
wl_seat::Request::GetPointer { id } => {
|
||||
let pointer = data_init.init(id, data.clone());
|
||||
let _ = data.pointer.set((pointer, Mutex::new(ObjectId::null())));
|
||||
pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) {
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
if pointer.current_focus() == Some(surface.clone()) {
|
||||
pointer.motion(
|
||||
state,
|
||||
None,
|
||||
&MotionEvent {
|
||||
location: (0.0, 0.0).into(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
let keyboard = self.seat.get_keyboard().unwrap();
|
||||
if keyboard.current_focus() == Some(surface.clone()) {
|
||||
keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial());
|
||||
}
|
||||
for (id, touch_surface) in self.touches.lock().iter() {
|
||||
if touch_surface.id() == surface.id() {
|
||||
self.touch_up(*id);
|
||||
}
|
||||
wl_seat::Request::GetKeyboard { id } => {
|
||||
let keyboard = data_init.init(id, data.clone());
|
||||
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
|
||||
keyboard.repeat_info(0, 0);
|
||||
}
|
||||
let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null())));
|
||||
}
|
||||
wl_seat::Request::GetTouch { id } => {
|
||||
let _ = data.touch.set(data_init.init(id, data.clone()));
|
||||
}
|
||||
wl_seat::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlPointer,
|
||||
request: wl_pointer::Request,
|
||||
seat_data: &Arc<SeatData>,
|
||||
dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
pub fn pointer_motion(&self, surface: WlSurface, position: Vector2<f32>) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut state = state.lock();
|
||||
let Some(pointer) = self.seat.get_pointer() else {
|
||||
return;
|
||||
};
|
||||
pointer.motion(
|
||||
&mut state,
|
||||
Some((surface, (0.0, 0.0).into())),
|
||||
&MotionEvent {
|
||||
location: (position.x as f64, position.y as f64).into(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
},
|
||||
);
|
||||
pointer.frame(&mut state);
|
||||
}
|
||||
pub fn pointer_button(&self, button: u32, pressed: bool) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut state = state.lock();
|
||||
let Some(pointer) = self.seat.get_pointer() else {
|
||||
return;
|
||||
};
|
||||
pointer.button(
|
||||
&mut state,
|
||||
&ButtonEvent {
|
||||
button,
|
||||
state: if pressed {
|
||||
ButtonState::Pressed
|
||||
} else {
|
||||
ButtonState::Released
|
||||
},
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
},
|
||||
);
|
||||
pointer.frame(&mut state);
|
||||
}
|
||||
pub fn pointer_scroll(
|
||||
&self,
|
||||
scroll_distance: Option<Vector2<f32>>,
|
||||
scroll_steps: Option<Vector2<f32>>,
|
||||
) {
|
||||
match request {
|
||||
wl_pointer::Request::SetCursor {
|
||||
serial: _,
|
||||
surface,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => {
|
||||
if let Some(surface) = surface.as_ref() {
|
||||
CoreSurface::add_to(dh.clone(), surface, || (), |_| ());
|
||||
compositor::with_states(surface, |data| {
|
||||
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
||||
core_surface.set_material_offset(1);
|
||||
}
|
||||
})
|
||||
}
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut state = state.lock();
|
||||
let Some(pointer) = self.seat.get_pointer() else {
|
||||
return;
|
||||
};
|
||||
pointer.axis(
|
||||
&mut state,
|
||||
AxisFrame {
|
||||
source: None,
|
||||
relative_direction: (
|
||||
AxisRelativeDirection::Identical,
|
||||
AxisRelativeDirection::Identical,
|
||||
),
|
||||
time: 0,
|
||||
axis: scroll_distance
|
||||
.map(|d| (d.x as f64, d.y as f64))
|
||||
.unwrap_or((0.0, 0.0)),
|
||||
v120: scroll_steps.map(|d| ((d.x * 120.0) as i32, (d.y * 120.0) as i32)),
|
||||
stop: (false, false),
|
||||
},
|
||||
);
|
||||
pointer.frame(&mut state);
|
||||
}
|
||||
|
||||
let Some((_, focus)) = seat_data.pointer.get() else {return};
|
||||
let focus = focus.lock();
|
||||
let surfaces = seat_data.surfaces.lock();
|
||||
let Some(surface_info) = surfaces.get(&focus) else {return};
|
||||
let cursor_info = surface.map(|surface| CursorInfo {
|
||||
surface: surface.downgrade(),
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
});
|
||||
let _ = surface_info.cursor_sender.send_replace(cursor_info);
|
||||
}
|
||||
wl_pointer::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlKeyboard, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlKeyboard,
|
||||
request: <WlKeyboard as Resource>::Request,
|
||||
_data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
wl_keyboard::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlTouch, Arc<SeatData>, WaylandState> for WaylandState {
|
||||
fn request(
|
||||
_state: &mut WaylandState,
|
||||
_client: &Client,
|
||||
_resource: &WlTouch,
|
||||
request: <WlTouch as Resource>::Request,
|
||||
_data: &Arc<SeatData>,
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, WaylandState>,
|
||||
) {
|
||||
match request {
|
||||
wl_touch::Request::Release => (),
|
||||
_ => unreachable!(),
|
||||
pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(keyboard) = self.seat.get_keyboard() else {
|
||||
return;
|
||||
};
|
||||
let keymaps = KEYMAPS.lock();
|
||||
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
keyboard.set_focus(
|
||||
&mut state.lock(),
|
||||
Some(surface),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
);
|
||||
if keyboard
|
||||
.set_keymap_from_string(&mut state.lock(), keymap)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
keyboard.input(
|
||||
&mut state.lock(),
|
||||
key.into(),
|
||||
if pressed {
|
||||
KeyState::Pressed
|
||||
} else {
|
||||
KeyState::Released
|
||||
},
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
0,
|
||||
|_, _, _| FilterResult::Forward::<()>,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(touch) = self.seat.get_touch() else {
|
||||
return;
|
||||
};
|
||||
touch.down(
|
||||
&mut state.lock(),
|
||||
Some((surface, (0.0, 0.0).into())),
|
||||
&DownEvent {
|
||||
slot: Some(id).into(),
|
||||
location: (position.x as f64, position.y as f64).into(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
},
|
||||
);
|
||||
touch.frame(&mut state.lock());
|
||||
}
|
||||
pub fn touch_move(&self, id: u32, position: Vector2<f32>) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(surface) = self.touches.lock().get(&id).and_then(|c| c.upgrade().ok()) else {
|
||||
return;
|
||||
};
|
||||
let Some(touch) = self.seat.get_touch() else {
|
||||
return;
|
||||
};
|
||||
touch.motion(
|
||||
&mut state.lock(),
|
||||
Some((surface, (0.0, 0.0).into())),
|
||||
&touch::MotionEvent {
|
||||
slot: Some(id).into(),
|
||||
location: (position.x as f64, position.y as f64).into(),
|
||||
time: 0,
|
||||
},
|
||||
);
|
||||
touch.frame(&mut state.lock());
|
||||
}
|
||||
pub fn touch_up(&self, id: u32) {
|
||||
let Some(state) = self.wayland_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(touch) = self.seat.get_touch() else {
|
||||
return;
|
||||
};
|
||||
touch.up(
|
||||
&mut state.lock(),
|
||||
&UpEvent {
|
||||
slot: Some(id).into(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
},
|
||||
);
|
||||
touch.frame(&mut state.lock());
|
||||
}
|
||||
pub fn reset_input(&self) {
|
||||
for id in self.touches.lock().keys() {
|
||||
self.touch_up(*id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
// Basic gamma correction shader
|
||||
// pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
|
||||
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
|
||||
|
||||
// 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");
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,75 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#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,23 +1,29 @@
|
||||
use crate::wayland::seat::SeatData;
|
||||
use super::seat::SeatWrapper;
|
||||
use crate::wayland::drm::wl_drm::WlDrm;
|
||||
use parking_lot::Mutex;
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::dmabuf::Dmabuf,
|
||||
allocator::{Fourcc, dmabuf::Dmabuf},
|
||||
egl::EGLDevice,
|
||||
renderer::{gles::GlesRenderer, ImportDma},
|
||||
renderer::gles::GlesRenderer,
|
||||
},
|
||||
delegate_dmabuf, delegate_output, delegate_shm,
|
||||
delegate_dmabuf, delegate_output, delegate_shm, delegate_viewporter,
|
||||
desktop::PopupManager,
|
||||
input::{SeatState, keyboard::XkbConfig},
|
||||
output::{Mode, Output, Scale, Subpixel},
|
||||
reexports::{
|
||||
wayland_protocols::xdg::{
|
||||
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
|
||||
shell::server::xdg_wm_base::XdgWmBase,
|
||||
shell::server::xdg_toplevel::WmCapabilities,
|
||||
},
|
||||
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
|
||||
wayland_server::{
|
||||
DisplayHandle,
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
|
||||
Display, DisplayHandle,
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
|
||||
wl_output::WlOutput,
|
||||
},
|
||||
},
|
||||
},
|
||||
utils::{Size, Transform},
|
||||
@@ -26,30 +32,30 @@ use smithay::{
|
||||
compositor::{CompositorClientState, CompositorState},
|
||||
dmabuf::{
|
||||
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
|
||||
ImportError,
|
||||
},
|
||||
shell::kde::decoration::KdeDecorationState,
|
||||
output::OutputHandler,
|
||||
shell::{
|
||||
kde::decoration::KdeDecorationState,
|
||||
xdg::{WmCapabilitySet, XdgShellState},
|
||||
},
|
||||
shm::{ShmHandler, ShmState},
|
||||
viewporter::ViewporterState,
|
||||
},
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub struct ClientState {
|
||||
pub pid: Option<i32>,
|
||||
pub id: OnceLock<ClientId>,
|
||||
pub compositor_state: CompositorClientState,
|
||||
pub display: Weak<Mutex<Display<WaylandState>>>,
|
||||
pub seat: Arc<SeatData>,
|
||||
}
|
||||
impl ClientState {
|
||||
pub fn flush(&self) {
|
||||
let Some(display) = self.display.upgrade() else {return};
|
||||
let _ = display.lock().flush_clients();
|
||||
}
|
||||
pub seat: Arc<SeatWrapper>,
|
||||
}
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, client_id: ClientId) {
|
||||
info!("Wayland client {:?} connected", client_id);
|
||||
let _ = self.id.set(client_id);
|
||||
}
|
||||
|
||||
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
|
||||
@@ -61,25 +67,26 @@ impl ClientData for ClientState {
|
||||
}
|
||||
|
||||
pub struct WaylandState {
|
||||
pub weak_ref: Weak<Mutex<WaylandState>>,
|
||||
pub display: Arc<Mutex<Display<WaylandState>>>,
|
||||
pub display_handle: DisplayHandle,
|
||||
|
||||
pub compositor_state: CompositorState,
|
||||
// pub xdg_activation_state: XdgActivationState,
|
||||
pub kde_decoration_state: KdeDecorationState,
|
||||
pub shm_state: ShmState,
|
||||
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
||||
pub _viewporter_state: ViewporterState,
|
||||
pub drm_formats: Vec<Fourcc>,
|
||||
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
|
||||
pub seat_state: SeatState<Self>,
|
||||
pub seat: Arc<SeatWrapper>,
|
||||
pub xdg_shell: XdgShellState,
|
||||
pub popup_manager: PopupManager,
|
||||
pub output: Output,
|
||||
}
|
||||
|
||||
impl WaylandState {
|
||||
pub fn new(
|
||||
display: Arc<Mutex<Display<WaylandState>>>,
|
||||
display_handle: DisplayHandle,
|
||||
renderer: &GlesRenderer,
|
||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
||||
dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
|
||||
@@ -88,16 +95,18 @@ impl WaylandState {
|
||||
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_formats = renderer
|
||||
.egl_context()
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect();
|
||||
|
||||
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(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
|
||||
.build()
|
||||
.ok(),
|
||||
Ok(None) => {
|
||||
warn!("failed to query render node, dmabuf will use v3");
|
||||
None
|
||||
@@ -117,13 +126,18 @@ impl WaylandState {
|
||||
);
|
||||
(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.create_global::<WaylandState>(&display_handle, dmabuf_formats.clone());
|
||||
(dmabuf_state, dmabuf_global, None)
|
||||
};
|
||||
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat = seat_state.new_wl_seat(&display_handle, "seat0");
|
||||
seat.add_pointer();
|
||||
seat.add_keyboard(XkbConfig::default(), 200, 25).unwrap();
|
||||
seat.add_touch();
|
||||
|
||||
let output = Output::new(
|
||||
"1x".to_owned(),
|
||||
smithay::output::PhysicalProperties {
|
||||
@@ -135,7 +149,7 @@ impl WaylandState {
|
||||
);
|
||||
let _output_global = output.create_global::<Self>(&display_handle);
|
||||
let mode = Mode {
|
||||
size: (2048, 2048).into(),
|
||||
size: (1024, 1024).into(),
|
||||
refresh: 60000,
|
||||
};
|
||||
output.change_current_state(
|
||||
@@ -145,25 +159,37 @@ impl WaylandState {
|
||||
None,
|
||||
);
|
||||
output.set_preferred(mode);
|
||||
|
||||
let mut xdg_shell = XdgShellState::new::<Self>(&display_handle);
|
||||
let _viewporter_state = ViewporterState::new::<Self>(&display_handle);
|
||||
let popup_manager = PopupManager::default();
|
||||
let mut capabilities = WmCapabilitySet::default();
|
||||
capabilities.set(WmCapabilities::Maximize);
|
||||
capabilities.set(WmCapabilities::Fullscreen);
|
||||
capabilities.unset(WmCapabilities::Minimize);
|
||||
capabilities.unset(WmCapabilities::WindowMenu);
|
||||
xdg_shell.replace_capabilities(capabilities);
|
||||
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
|
||||
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
|
||||
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
|
||||
display_handle.create_global::<Self, WlDrm, _>(2, ());
|
||||
|
||||
info!("Init Wayland compositor");
|
||||
|
||||
Arc::new_cyclic(|weak| {
|
||||
Mutex::new(WaylandState {
|
||||
weak_ref: weak.clone(),
|
||||
display,
|
||||
display_handle,
|
||||
|
||||
compositor_state,
|
||||
// xdg_activation_state,
|
||||
kde_decoration_state,
|
||||
shm_state,
|
||||
drm_formats,
|
||||
dmabuf_state,
|
||||
dmabuf_tx,
|
||||
seat_state,
|
||||
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
|
||||
xdg_shell,
|
||||
popup_manager,
|
||||
output,
|
||||
_viewporter_state,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -190,10 +216,15 @@ impl DmabufHandler for WaylandState {
|
||||
&mut self,
|
||||
_global: &DmabufGlobal,
|
||||
dmabuf: Dmabuf,
|
||||
) -> Result<(), dmabuf::ImportError> {
|
||||
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed)
|
||||
notifier: dmabuf::ImportNotifier,
|
||||
) {
|
||||
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap();
|
||||
}
|
||||
}
|
||||
impl OutputHandler for WaylandState {
|
||||
fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {}
|
||||
}
|
||||
delegate_dmabuf!(WaylandState);
|
||||
delegate_shm!(WaylandState);
|
||||
delegate_output!(WaylandState);
|
||||
delegate_viewporter!(WaylandState);
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
|
||||
use super::utils::WlSurfaceExt;
|
||||
use crate::{
|
||||
core::{delta::Delta, destroy_queue, registry::Registry},
|
||||
nodes::drawable::model::ModelPart,
|
||||
nodes::{
|
||||
drawable::{
|
||||
model::{MaterialWrapper, ModelPart},
|
||||
shaders::PANEL_SHADER_BYTES,
|
||||
},
|
||||
items::camera::TexWrapper,
|
||||
},
|
||||
};
|
||||
use mint::Vector2;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use send_wrapper::SendWrapper;
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
gles::{GlesRenderer, GlesTexture},
|
||||
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
|
||||
Renderer, Texture,
|
||||
gles::{GlesRenderer, GlesTexture},
|
||||
utils::{RendererSurfaceStateUserData, import_surface_tree},
|
||||
},
|
||||
desktop::utils::send_frames_surface_tree,
|
||||
output::Output,
|
||||
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, DisplayHandle, Resource},
|
||||
wayland::compositor::{self, SurfaceData},
|
||||
reexports::wayland_server::{self, Resource, protocol::wl_surface::WlSurface},
|
||||
};
|
||||
use std::{ffi::c_void, sync::Arc, time::Duration};
|
||||
use stereokit::{
|
||||
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
|
||||
Transparency,
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
sync::{Arc, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
use stereokit_rust::{
|
||||
material::{Material, Transparency},
|
||||
shader::Shader,
|
||||
tex::{Tex, TexAddress, TexFormat, TexSample, TexType},
|
||||
util::Time,
|
||||
};
|
||||
|
||||
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
|
||||
|
||||
pub struct CoreSurfaceData {
|
||||
wl_tex: Option<SendWrapper<GlesTexture>>,
|
||||
pub size: Vector2<u32>,
|
||||
}
|
||||
impl Drop for CoreSurfaceData {
|
||||
fn drop(&mut self) {
|
||||
@@ -37,141 +45,122 @@ impl Drop for CoreSurfaceData {
|
||||
}
|
||||
|
||||
pub struct CoreSurface {
|
||||
pub dh: DisplayHandle,
|
||||
pub weak_surface: wayland_server::Weak<WlSurface>,
|
||||
mapped_data: Mutex<Option<CoreSurfaceData>>,
|
||||
sk_tex: OnceCell<SendWrapper<Tex>>,
|
||||
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
|
||||
sk_tex: OnceLock<Mutex<TexWrapper>>,
|
||||
sk_mat: OnceLock<Mutex<MaterialWrapper>>,
|
||||
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 add_to(
|
||||
dh: DisplayHandle,
|
||||
surface: &WlSurface,
|
||||
on_mapped: impl Fn() + Send + Sync + 'static,
|
||||
on_commit: impl Fn(u32) + Send + Sync + 'static,
|
||||
) {
|
||||
compositor::with_states(surface, |data| {
|
||||
data.data_map.insert_if_missing_threadsafe(|| {
|
||||
CORE_SURFACES.add(CoreSurface {
|
||||
dh,
|
||||
weak_surface: surface.downgrade(),
|
||||
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 add_to(surface: &WlSurface) {
|
||||
let core_surface = CORE_SURFACES.add(CoreSurface {
|
||||
weak_surface: surface.downgrade(),
|
||||
mapped_data: Mutex::new(None),
|
||||
sk_tex: OnceLock::new(),
|
||||
sk_mat: OnceLock::new(),
|
||||
material_offset: Mutex::new(Delta::new(0)),
|
||||
pending_material_applications: Registry::new(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn commit(&self, count: u32) {
|
||||
(self.on_commit)(count);
|
||||
surface.insert_data(core_surface);
|
||||
}
|
||||
|
||||
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
|
||||
compositor::with_states(surf, |data| {
|
||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
||||
})
|
||||
surf.get_data()
|
||||
}
|
||||
|
||||
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
|
||||
let Some(wl_surface) = self.wl_surface() else {return};
|
||||
pub fn process(&self, renderer: &mut GlesRenderer) {
|
||||
let Some(wl_surface) = self.wl_surface() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let sk_tex = self.sk_tex.get_or_init(|| {
|
||||
SendWrapper::new(sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32))
|
||||
Mutex::new(TexWrapper(Tex::new(
|
||||
TexType::ImageNomips,
|
||||
TexFormat::RGBA32Linear,
|
||||
nanoid::nanoid!(),
|
||||
)))
|
||||
});
|
||||
self.sk_mat.get_or_init(|| {
|
||||
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES).unwrap();
|
||||
let shader = Shader::from_memory(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 mut mat = Material::new(shader, None);
|
||||
mat.diffuse_tex(&sk_tex.lock().0);
|
||||
mat.transparency(Transparency::Blend);
|
||||
Mutex::new(MaterialWrapper(mat))
|
||||
});
|
||||
|
||||
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
|
||||
on_commit_buffer_handler::<WaylandState>(&wl_surface);
|
||||
// Import all surface buffers into textures
|
||||
if import_surface_tree(renderer, &wl_surface).is_err() {
|
||||
if let Err(err) = import_surface_tree(renderer, &wl_surface) {
|
||||
tracing::error!("Failed to import surface tree for surface {}: {}", wl_surface.id(), err);
|
||||
return;
|
||||
}
|
||||
|
||||
let mapped = compositor::with_states(&wl_surface, |data| {
|
||||
data.data_map
|
||||
.get::<RendererSurfaceStateUserData>()
|
||||
.map(|surface_states| surface_states.borrow().buffer().is_some())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if !mapped {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut mapped_data = self.mapped_data.lock();
|
||||
let just_mapped = mapped_data.is_none();
|
||||
self.with_states(|data| {
|
||||
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,
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
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.update_textures(renderer);
|
||||
self.apply_surface_materials();
|
||||
}
|
||||
|
||||
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
|
||||
let Some(wl_surface) = self.wl_surface() else {return};
|
||||
pub fn update_textures(&self, renderer: &mut GlesRenderer) {
|
||||
let Some(wl_surface) = self.wl_surface() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(smithay_tex) = wl_surface
|
||||
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
|
||||
let locked = surface_states.lock().unwrap();
|
||||
locked.texture::<GlesRenderer>(renderer.id()).cloned()
|
||||
})
|
||||
.flatten()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(sk_tex) = self.sk_tex.get() else {
|
||||
tracing::error!("No sk_tex found for surface");
|
||||
return;
|
||||
};
|
||||
let Some(sk_mat) = self.sk_mat.get() else {
|
||||
tracing::error!("No sk_mat found for surface");
|
||||
return;
|
||||
};
|
||||
sk_tex
|
||||
.lock()
|
||||
.0
|
||||
.set_native_surface(
|
||||
smithay_tex.tex_id() as usize as *mut c_void,
|
||||
TexType::ImageNomips,
|
||||
smithay::backend::renderer::gles::ffi::RGBA8.into(),
|
||||
smithay_tex.width() as i32,
|
||||
smithay_tex.height() as i32,
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.sample_mode(TexSample::Point)
|
||||
.address_mode(TexAddress::Clamp);
|
||||
|
||||
if let Some(material_offset) = self.material_offset.lock().delta() {
|
||||
sk_mat.lock().0.queue_offset(*material_offset as i32);
|
||||
}
|
||||
|
||||
let new_mapped_data = CoreSurfaceData {
|
||||
wl_tex: Some(SendWrapper::new(smithay_tex)),
|
||||
};
|
||||
*self.mapped_data.lock() = Some(new_mapped_data);
|
||||
}
|
||||
|
||||
pub fn frame(&self, 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()),
|
||||
Duration::from_secs_f64(Time::get_total_unscaled()),
|
||||
None,
|
||||
|_, _| Some(output.clone()),
|
||||
);
|
||||
@@ -186,27 +175,18 @@ impl CoreSurface {
|
||||
}
|
||||
|
||||
fn apply_surface_materials(&self) {
|
||||
for model_node in self.pending_material_applications.get_valid_contents() {
|
||||
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
|
||||
if let Some(sk_mat) = self.sk_mat.get() {
|
||||
let sk_mat = sk_mat.lock();
|
||||
for model_node in self.pending_material_applications.get_valid_contents() {
|
||||
model_node.replace_material_now(&sk_mat.0);
|
||||
}
|
||||
self.pending_material_applications.clear();
|
||||
}
|
||||
self.pending_material_applications.clear();
|
||||
}
|
||||
|
||||
pub fn wl_surface(&self) -> Option<WlSurface> {
|
||||
self.weak_surface.upgrade().ok()
|
||||
}
|
||||
|
||||
pub fn with_states<F, T>(&self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&SurfaceData) -> T,
|
||||
{
|
||||
self.wl_surface()
|
||||
.map(|wl_surface| compositor::with_states(&wl_surface, f))
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Option<Vector2<u32>> {
|
||||
self.mapped_data.lock().as_ref().map(|d| d.size)
|
||||
}
|
||||
}
|
||||
impl Drop for CoreSurface {
|
||||
fn drop(&mut self) {
|
||||
|
||||
121
src/wayland/utils.rs
Normal file
121
src/wayland/utils.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use mint::Vector2;
|
||||
use parking_lot::Mutex;
|
||||
use smithay::{
|
||||
backend::renderer::utils::RendererSurfaceStateUserData,
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
wayland::{
|
||||
compositor,
|
||||
shell::xdg::{SurfaceCachedState, XdgToplevelSurfaceData},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::nodes::items::panel::{ChildInfo, Geometry, ToplevelInfo};
|
||||
|
||||
use super::xdg_shell::surface_panel_item;
|
||||
pub trait WlSurfaceExt {
|
||||
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool;
|
||||
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T>;
|
||||
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O>;
|
||||
fn get_current_surface_state(&self) -> SurfaceCachedState;
|
||||
fn get_pending_surface_state(&self) -> SurfaceCachedState;
|
||||
fn get_size(&self) -> Option<Vector2<u32>>;
|
||||
fn get_geometry(&self) -> Option<Geometry>;
|
||||
}
|
||||
impl WlSurfaceExt for WlSurface {
|
||||
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool {
|
||||
compositor::with_states(self, |d| {
|
||||
d.data_map.insert_if_missing_threadsafe(move || data)
|
||||
})
|
||||
}
|
||||
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T> {
|
||||
compositor::with_states(self, |d| d.data_map.get::<T>().cloned())
|
||||
}
|
||||
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O> {
|
||||
compositor::with_states(self, |d| Some((f)(d.data_map.get::<T>()?)))
|
||||
}
|
||||
fn get_current_surface_state(&self) -> SurfaceCachedState {
|
||||
compositor::with_states(self, |states| {
|
||||
*states.cached_state.get::<SurfaceCachedState>().current()
|
||||
})
|
||||
}
|
||||
fn get_pending_surface_state(&self) -> SurfaceCachedState {
|
||||
compositor::with_states(self, |states| {
|
||||
*states.cached_state.get::<SurfaceCachedState>().pending()
|
||||
})
|
||||
}
|
||||
fn get_size(&self) -> Option<Vector2<u32>> {
|
||||
self.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
|
||||
surface_states.lock().unwrap().surface_size()
|
||||
})
|
||||
.flatten()
|
||||
.map(|size| Vector2::from([size.w as u32, size.h as u32]))
|
||||
}
|
||||
fn get_geometry(&self) -> Option<Geometry> {
|
||||
self.get_current_surface_state().geometry.map(|r| r.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToplevelInfoExt {
|
||||
fn get_toplevel_info(&self) -> Option<ToplevelInfo>;
|
||||
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O>;
|
||||
|
||||
fn get_parent(&self) -> Option<u64>;
|
||||
fn get_app_id(&self) -> Option<String>;
|
||||
fn get_title(&self) -> Option<String>;
|
||||
fn min_size(&self) -> Option<Vector2<u32>>;
|
||||
fn max_size(&self) -> Option<Vector2<u32>>;
|
||||
}
|
||||
impl ToplevelInfoExt for WlSurface {
|
||||
fn get_toplevel_info(&self) -> Option<ToplevelInfo> {
|
||||
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|c| c.lock().clone())
|
||||
}
|
||||
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O> {
|
||||
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|r| (f)(&mut r.lock()))
|
||||
}
|
||||
|
||||
fn get_parent(&self) -> Option<u64> {
|
||||
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().unwrap().parent.clone())
|
||||
.flatten()
|
||||
.and_then(|p| surface_panel_item(&p))
|
||||
.and_then(|p| p.node.upgrade())
|
||||
.map(|p| p.get_id())
|
||||
}
|
||||
fn get_app_id(&self) -> Option<String> {
|
||||
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.app_id.clone())
|
||||
.flatten()
|
||||
}
|
||||
fn get_title(&self) -> Option<String> {
|
||||
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.title.clone())
|
||||
.flatten()
|
||||
}
|
||||
fn min_size(&self) -> Option<Vector2<u32>> {
|
||||
let state = self.get_pending_surface_state();
|
||||
let size = state.min_size;
|
||||
if size.w == 0 && size.h == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Vector2::from([size.w as u32, size.h as u32]))
|
||||
}
|
||||
}
|
||||
fn max_size(&self) -> Option<Vector2<u32>> {
|
||||
let state = self.get_pending_surface_state();
|
||||
let size = state.max_size;
|
||||
if size.w == 0 && size.h == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Vector2::from([size.w as u32, size.h as u32]))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub trait ChildInfoExt {
|
||||
fn get_child_info(&self) -> Option<ChildInfo>;
|
||||
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O>;
|
||||
}
|
||||
impl ChildInfoExt for WlSurface {
|
||||
fn get_child_info(&self) -> Option<ChildInfo> {
|
||||
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|c| c.lock().clone())
|
||||
}
|
||||
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O> {
|
||||
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|r| (f)(&mut r.lock()))
|
||||
}
|
||||
}
|
||||
189
src/wayland/wayland-drm.xml
Normal file
189
src/wayland/wayland-drm.xml
Normal file
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="drm">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2008-2011 Kristian Høgsberg
|
||||
Copyright © 2010-2011 Intel Corporation
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that\n the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<!-- drm support. This object is created by the server and published
|
||||
using the display's global event. -->
|
||||
<interface name="wl_drm" version="2">
|
||||
<enum name="error">
|
||||
<entry name="authenticate_fail" value="0" />
|
||||
<entry name="invalid_format" value="1" />
|
||||
<entry name="invalid_name" value="2" />
|
||||
</enum>
|
||||
|
||||
<enum name="format">
|
||||
<!-- The drm format codes match the #defines in drm_fourcc.h.
|
||||
The formats actually supported by the compositor will be
|
||||
reported by the format event. New codes must not be added,
|
||||
unless directly taken from drm_fourcc.h. -->
|
||||
<entry name="c8" value="0x20203843" />
|
||||
<entry name="rgb332" value="0x38424752" />
|
||||
<entry name="bgr233" value="0x38524742" />
|
||||
<entry name="xrgb4444" value="0x32315258" />
|
||||
<entry name="xbgr4444" value="0x32314258" />
|
||||
<entry name="rgbx4444" value="0x32315852" />
|
||||
<entry name="bgrx4444" value="0x32315842" />
|
||||
<entry name="argb4444" value="0x32315241" />
|
||||
<entry name="abgr4444" value="0x32314241" />
|
||||
<entry name="rgba4444" value="0x32314152" />
|
||||
<entry name="bgra4444" value="0x32314142" />
|
||||
<entry name="xrgb1555" value="0x35315258" />
|
||||
<entry name="xbgr1555" value="0x35314258" />
|
||||
<entry name="rgbx5551" value="0x35315852" />
|
||||
<entry name="bgrx5551" value="0x35315842" />
|
||||
<entry name="argb1555" value="0x35315241" />
|
||||
<entry name="abgr1555" value="0x35314241" />
|
||||
<entry name="rgba5551" value="0x35314152" />
|
||||
<entry name="bgra5551" value="0x35314142" />
|
||||
<entry name="rgb565" value="0x36314752" />
|
||||
<entry name="bgr565" value="0x36314742" />
|
||||
<entry name="rgb888" value="0x34324752" />
|
||||
<entry name="bgr888" value="0x34324742" />
|
||||
<entry name="xrgb8888" value="0x34325258" />
|
||||
<entry name="xbgr8888" value="0x34324258" />
|
||||
<entry name="rgbx8888" value="0x34325852" />
|
||||
<entry name="bgrx8888" value="0x34325842" />
|
||||
<entry name="argb8888" value="0x34325241" />
|
||||
<entry name="abgr8888" value="0x34324241" />
|
||||
<entry name="rgba8888" value="0x34324152" />
|
||||
<entry name="bgra8888" value="0x34324142" />
|
||||
<entry name="xrgb2101010" value="0x30335258" />
|
||||
<entry name="xbgr2101010" value="0x30334258" />
|
||||
<entry name="rgbx1010102" value="0x30335852" />
|
||||
<entry name="bgrx1010102" value="0x30335842" />
|
||||
<entry name="argb2101010" value="0x30335241" />
|
||||
<entry name="abgr2101010" value="0x30334241" />
|
||||
<entry name="rgba1010102" value="0x30334152" />
|
||||
<entry name="bgra1010102" value="0x30334142" />
|
||||
<entry name="yuyv" value="0x56595559" />
|
||||
<entry name="yvyu" value="0x55595659" />
|
||||
<entry name="uyvy" value="0x59565955" />
|
||||
<entry name="vyuy" value="0x59555956" />
|
||||
<entry name="ayuv" value="0x56555941" />
|
||||
<entry name="xyuv8888" value="0x56555958" />
|
||||
<entry name="nv12" value="0x3231564e" />
|
||||
<entry name="nv21" value="0x3132564e" />
|
||||
<entry name="nv16" value="0x3631564e" />
|
||||
<entry name="nv61" value="0x3136564e" />
|
||||
<entry name="yuv410" value="0x39565559" />
|
||||
<entry name="yvu410" value="0x39555659" />
|
||||
<entry name="yuv411" value="0x31315559" />
|
||||
<entry name="yvu411" value="0x31315659" />
|
||||
<entry name="yuv420" value="0x32315559" />
|
||||
<entry name="yvu420" value="0x32315659" />
|
||||
<entry name="yuv422" value="0x36315559" />
|
||||
<entry name="yvu422" value="0x36315659" />
|
||||
<entry name="yuv444" value="0x34325559" />
|
||||
<entry name="yvu444" value="0x34325659" />
|
||||
<entry name="abgr16f" value="0x48344241" />
|
||||
<entry name="xbgr16f" value="0x48344258" />
|
||||
</enum>
|
||||
|
||||
<!-- Call this request with the magic received from drmGetMagic().
|
||||
It will be passed on to the drmAuthMagic() or
|
||||
DRIAuthConnection() call. This authentication must be
|
||||
completed before create_buffer could be used. -->
|
||||
<request name="authenticate">
|
||||
<arg name="id" type="uint" />
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer" />
|
||||
<arg name="name" type="uint" />
|
||||
<arg name="width" type="int" />
|
||||
<arg name="height" type="int" />
|
||||
<arg name="stride" type="uint" />
|
||||
<arg name="format" type="uint" />
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_planar_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer" />
|
||||
<arg name="name" type="uint" />
|
||||
<arg name="width" type="int" />
|
||||
<arg name="height" type="int" />
|
||||
<arg name="format" type="uint" />
|
||||
<arg name="offset0" type="int" />
|
||||
<arg name="stride0" type="int" />
|
||||
<arg name="offset1" type="int" />
|
||||
<arg name="stride1" type="int" />
|
||||
<arg name="offset2" type="int" />
|
||||
<arg name="stride2" type="int" />
|
||||
</request>
|
||||
|
||||
<!-- Notification of the path of the drm device which is used by
|
||||
the server. The client should use this device for creating
|
||||
local buffers. Only buffers created from this device should
|
||||
be be passed to the server using this drm object's
|
||||
create_buffer request. -->
|
||||
<event name="device">
|
||||
<arg name="name" type="string" />
|
||||
</event>
|
||||
|
||||
<event name="format">
|
||||
<arg name="format" type="uint" />
|
||||
</event>
|
||||
|
||||
<!-- Raised if the authenticate request succeeded -->
|
||||
<event name="authenticated" />
|
||||
|
||||
<enum name="capability" since="2">
|
||||
<description summary="wl_drm capability bitmask">
|
||||
Bitmask of capabilities.
|
||||
</description>
|
||||
<entry name="prime" value="1" summary="wl_drm prime available" />
|
||||
</enum>
|
||||
|
||||
<event name="capabilities">
|
||||
<arg name="value" type="uint" />
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
|
||||
buffers. Pass 0 for offset and stride for unused planes. -->
|
||||
<request name="create_prime_buffer" since="2">
|
||||
<arg name="id" type="new_id" interface="wl_buffer" />
|
||||
<arg name="name" type="fd" />
|
||||
<arg name="width" type="int" />
|
||||
<arg name="height" type="int" />
|
||||
<arg name="format" type="uint" />
|
||||
<arg name="offset0" type="int" />
|
||||
<arg name="stride0" type="int" />
|
||||
<arg name="offset1" type="int" />
|
||||
<arg name="stride1" type="int" />
|
||||
<arg name="offset2" type="int" />
|
||||
<arg name="stride2" type="int" />
|
||||
</request>
|
||||
|
||||
</interface>
|
||||
|
||||
</protocol>
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user