Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,4 +11,6 @@
|
|||||||
*result*
|
*result*
|
||||||
|
|
||||||
/libs/
|
/libs/
|
||||||
*.AppImage
|
*.AppImage
|
||||||
|
*.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",
|
||||||
|
# ]
|
||||||
2927
Cargo.lock
generated
2927
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
131
Cargo.toml
131
Cargo.toml
@@ -1,92 +1,117 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.75"
|
||||||
name = "stardust-xr-server"
|
name = "stardust-xr-server"
|
||||||
version = "0.42.1"
|
version = "0.45.0"
|
||||||
authors = ["Nova King <technobaboo@proton.me>"]
|
authors = ["Nova King <technobaboo@proton.me>"]
|
||||||
description = "Stardust XR reference display server"
|
description = "Stardust XR reference display server"
|
||||||
license = "GPLv2"
|
license = "GPLv2"
|
||||||
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
repository = "https://github.com/StardustXR/stardust-xr-server/"
|
||||||
homepage = "https://stardustxr.org"
|
homepage = "https://stardustxr.org"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["codegen"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "stardust-xr-server"
|
name = "stardust-xr-server"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wayland"]
|
default = ["wayland"]
|
||||||
wayland = ["dep:smithay", "dep:xkbcommon"]
|
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
|
||||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
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]
|
[package.metadata.appimage]
|
||||||
auto_link = true
|
auto_link = true
|
||||||
auto_link_exclude_list = [
|
auto_link_exclude_list = [
|
||||||
"libc*",
|
"libc*",
|
||||||
"libdl*",
|
"libdl*",
|
||||||
"libpthread*",
|
"libpthread*",
|
||||||
"ld-linux*",
|
"ld-linux*",
|
||||||
"libGL*",
|
"libGL*",
|
||||||
"libEGL*",
|
"libEGL*",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
debug = true
|
||||||
|
strip = "none"
|
||||||
|
debug-assertions = true
|
||||||
|
overflow-checks = true
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
opt-level = 3
|
||||||
lto = true
|
debug = "line-tables-only"
|
||||||
|
strip = "none"
|
||||||
|
debug-assertions = true
|
||||||
|
overflow-checks = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = { version = "0.6.2", default-features = false }
|
# small utility thingys
|
||||||
clap = { version = "4.2.4", features = ["derive"] }
|
once_cell = "1.19.0"
|
||||||
dashmap = "5.4.0"
|
|
||||||
glam = { version = "0.23.0", features = ["mint"] }
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
mint = "0.5.9"
|
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
once_cell = "1.17.1"
|
lazy_static = "1.5.0"
|
||||||
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.11.4"
|
|
||||||
directories = "5.0.0"
|
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
|
||||||
tracing = "0.1.37"
|
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
|
||||||
global_counter = "0.2.2"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
atty = "0.2.14"
|
rustc-hash = "2.0.0"
|
||||||
|
portable-atomic = { version = "1.7.0", features = ["float", "std"] }
|
||||||
|
send_wrapper = "0.6.0"
|
||||||
|
slotmap = "1.0.7"
|
||||||
|
global_counter = "=0.2.2"
|
||||||
|
parking_lot = "0.12.3"
|
||||||
|
|
||||||
[dependencies.stereokit]
|
# rust errors/logging
|
||||||
default-features = false
|
color-eyre = { version = "0.6.3", default-features = false }
|
||||||
features = ["linux-egl"]
|
clap = { version = "4.5.13", features = ["derive"] }
|
||||||
version = "0.16.7"
|
console-subscriber = { version = "0.4.0", optional = true }
|
||||||
|
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.28.0", features = ["mint", "serde"] }
|
||||||
|
mint = "0.5.9"
|
||||||
|
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
|
||||||
|
prisma = "0.1.1"
|
||||||
|
|
||||||
|
# linux stuffs
|
||||||
|
libc = "0.2.155"
|
||||||
|
nix = "0.29.0"
|
||||||
|
input-event-codes = "6.2.0"
|
||||||
|
zbus = { version = "4.4.0", default-features = false, features = ["tokio"] }
|
||||||
|
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 }
|
||||||
|
|
||||||
[dependencies.smithay]
|
[dependencies.smithay]
|
||||||
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
|
# git = "https://github.com/technobaboo/smithay.git"
|
||||||
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
|
# git = "https://github.com/colinmarc/smithay.git"
|
||||||
|
git = "https://github.com/smithay/smithay.git"
|
||||||
# path = "../smithay"
|
# path = "../smithay"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
|
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
|
||||||
version = "*"
|
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.console-subscriber]
|
|
||||||
version = "0.1.8"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.tracing-chrome]
|
[dependencies.stereokit-rust]
|
||||||
version = "0.7.1"
|
# path = "../StereoKit-rust"
|
||||||
optional = true
|
# git = "https://github.com/mvvvv/StereoKit-rust.git"
|
||||||
|
git = "https://github.com/technobaboo/StereoKit-rust.git"
|
||||||
|
features = ["no-event-loop"]
|
||||||
|
default-features = false
|
||||||
|
|
||||||
# [patch.crates-io.stereokit]
|
[dependencies.stardust-xr]
|
||||||
# path = "../stereokit-rs"
|
git = "https://github.com/StardustXR/core.git"
|
||||||
# [patch.crates-io.stereokit-sys]
|
branch = "dev"
|
||||||
# path = "../stereokit-sys"
|
|
||||||
# [patch.crates-io.stardust-xr]
|
[dependencies.stardust-xr-server-codegen]
|
||||||
# path = "../core/core"
|
path = "codegen"
|
||||||
# [patch.crates-io.stardust-xr-schemas]
|
|
||||||
# path = "../core/schemas"
|
|
||||||
|
|||||||
18
codegen/Cargo.toml
Normal file
18
codegen/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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"
|
||||||
|
|
||||||
|
[dependencies.stardust-xr-schemas]
|
||||||
|
git = "https://github.com/StardustXR/core.git"
|
||||||
|
branch = "dev"
|
||||||
617
codegen/src/lib.rs
Normal file
617
codegen/src/lib.rs
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
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_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
codegen_protocol(DATA_PROTOCOL)
|
||||||
|
}
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
codegen_protocol(AUDIO_PROTOCOL)
|
||||||
|
}
|
||||||
|
#[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(),
|
||||||
|
description: protocol.description.clone(),
|
||||||
|
inherits: vec![],
|
||||||
|
members: p.members,
|
||||||
|
});
|
||||||
|
quote!(#node_id #aspect)
|
||||||
|
})
|
||||||
|
.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(untagged)]
|
||||||
|
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(generate_member)
|
||||||
|
.reduce(fold_tokens)
|
||||||
|
.map(|t| {
|
||||||
|
// TODO: properly import all dependencies
|
||||||
|
quote! {
|
||||||
|
pub mod #client_mod_name {
|
||||||
|
use super::*;
|
||||||
|
#t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let aspect_trait_name = Ident::new(
|
||||||
|
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let opcodes = aspect
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.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(generate_member)
|
||||||
|
.reduce(fold_tokens)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let add_node_members = aspect
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter(|m| m.side == Side::Server)
|
||||||
|
.map(generate_handler)
|
||||||
|
.reduce(fold_tokens)
|
||||||
|
.map(|members| {
|
||||||
|
quote! {
|
||||||
|
fn add_node_members(node: &crate::nodes::Node) {
|
||||||
|
#members
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let server_side_members = quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
pub trait #aspect_trait_name {
|
||||||
|
#add_node_members
|
||||||
|
#server_side_members
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote!(#opcodes #alias_info #client_side_members #server_side_members)
|
||||||
|
}
|
||||||
|
|
||||||
|
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! {
|
||||||
|
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(member: &Member) -> TokenStream {
|
||||||
|
let id = 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!(()));
|
||||||
|
|
||||||
|
match (side, _type) {
|
||||||
|
(Side::Client, MemberType::Method) => {
|
||||||
|
quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
|
||||||
|
_node.execute_remote_method_typed(#id, &(#argument_uses), vec![]).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Side::Client, MemberType::Signal) => {
|
||||||
|
quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
|
||||||
|
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
|
||||||
|
_node.send_remote_signal(#id, serialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Side::Server, MemberType::Method) => {
|
||||||
|
quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Side::Server, MemberType::Signal) => {
|
||||||
|
quote! {
|
||||||
|
#[doc = #description]
|
||||||
|
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn generate_handler(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!(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();
|
||||||
|
match member._type {
|
||||||
|
MemberType::Signal => quote! {
|
||||||
|
node.add_local_signal(#opcode, |_node, _calling_client, _message| {
|
||||||
|
#deserialize
|
||||||
|
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
MemberType::Method => quote! {
|
||||||
|
node.add_local_method(#opcode, |_node, _calling_client, _message, _method_response| {
|
||||||
|
_method_response.wrap_async(async move {
|
||||||
|
#deserialize
|
||||||
|
let result = Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?;
|
||||||
|
Ok((#serialize, Vec::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::<_, color_eyre::eyre::Report>(#mapping)).transpose()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
match argument_type {
|
||||||
|
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
|
||||||
|
ArgumentType::Vec(v) => {
|
||||||
|
let mapping = generate_argument_deserialize("a", v, false);
|
||||||
|
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
|
||||||
|
}
|
||||||
|
ArgumentType::Map(v) => {
|
||||||
|
let mapping = generate_argument_deserialize("a", v, false);
|
||||||
|
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||||
|
}
|
||||||
|
_ => quote!(#name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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::<color_eyre::eyre::Result<Vec<_>>>()?)
|
||||||
|
}
|
||||||
|
ArgumentType::Map(v) => {
|
||||||
|
let mapping = generate_argument_serialize("a", v, false);
|
||||||
|
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
|
||||||
|
}
|
||||||
|
_ => quote!(#name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if optional {
|
||||||
|
quote!(Option<#_type>)
|
||||||
|
} else {
|
||||||
|
_type
|
||||||
|
}
|
||||||
|
}
|
||||||
358
flake.lock
generated
358
flake.lock
generated
@@ -1,61 +1,23 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"fenix": {
|
"crane": {
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1683786056,
|
|
||||||
"narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "5816c7bbcc385d2e65877631497df3f7d66b354a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fenix_2": {
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"flatland",
|
"flatland",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
]
|
||||||
"rust-analyzer-src": "rust-analyzer-src_2"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678775037,
|
"lastModified": 1712681629,
|
||||||
"narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=",
|
"narHash": "sha256-bMDXn4AkTXLCpoZbII6pDGoSeSe9gI87jxPsHRXgu/E=",
|
||||||
"owner": "nix-community",
|
"owner": "ipetkov",
|
||||||
"repo": "fenix",
|
"repo": "crane",
|
||||||
"rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583",
|
"rev": "220387ac8e99cbee0ca4c95b621c4bc782b6a235",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "ipetkov",
|
||||||
"repo": "fenix",
|
"repo": "crane",
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1673956053,
|
|
||||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -64,11 +26,32 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678379998,
|
"lastModified": 1722555600,
|
||||||
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
|
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
|
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"hercules-ci-effects",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712014858,
|
||||||
|
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -76,54 +59,17 @@
|
|||||||
"type": "indirect"
|
"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": {
|
"flatland": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix_2",
|
"crane": "crane",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1683766358,
|
"lastModified": 1713050562,
|
||||||
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=",
|
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
|
||||||
"owner": "StardustXR",
|
"owner": "StardustXR",
|
||||||
"repo": "flatland",
|
"repo": "flatland",
|
||||||
"rev": "24613a496841bdf38e5f136608d5295860a75fce",
|
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -132,78 +78,17 @@
|
|||||||
"type": "github"
|
"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": {
|
"hercules-ci-effects": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts_2",
|
||||||
"hercules-ci-agent": "hercules-ci-agent",
|
"nixpkgs": "nixpkgs_2"
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681898675,
|
"lastModified": 1719226092,
|
||||||
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=",
|
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "hercules-ci-effects",
|
"repo": "hercules-ci-effects",
|
||||||
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0",
|
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -212,85 +97,41 @@
|
|||||||
"type": "github"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678703398,
|
"lastModified": 1712791164,
|
||||||
"narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=",
|
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
|
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"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",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1673800717,
|
"lastModified": 1722555339,
|
||||||
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
|
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
|
||||||
"owner": "NixOS",
|
"type": "tarball",
|
||||||
"repo": "nixpkgs",
|
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
||||||
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"type": "tarball",
|
||||||
"ref": "nixos-22.11",
|
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678293141,
|
"lastModified": 1713714899,
|
||||||
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
|
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
|
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -302,101 +143,26 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678891326,
|
"lastModified": 1723991338,
|
||||||
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=",
|
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052",
|
"rev": "8a3354191c0d7144db9756a74755672387b702ba",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_4": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1683408522,
|
|
||||||
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"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": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"flake-parts": "flake-parts",
|
||||||
"flatland": "flatland",
|
"flatland": "flatland",
|
||||||
"hercules-ci-effects": "hercules-ci-effects",
|
"hercules-ci-effects": "hercules-ci-effects",
|
||||||
"nixpkgs": "nixpkgs_4"
|
"nixpkgs": "nixpkgs_3"
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
196
flake.nix
196
flake.nix
@@ -1,132 +1,90 @@
|
|||||||
{
|
{
|
||||||
nixConfig = {
|
nixConfig = {
|
||||||
extra-substituters = [ "https://stardustxr.cachix.org" ];
|
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
|
# Since we do not have a monorepo, we have to fetch Flatland in order to use
|
||||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
|
# it to create VM Tests
|
||||||
|
flatland.url = "github:StardustXR/flatland";
|
||||||
# Since we do not have a monorepo, we have to fetch Flatland in order to use
|
};
|
||||||
# it to create VM Tests
|
outputs =
|
||||||
inputs.flatland.url = "github:StardustXR/flatland";
|
inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }:
|
||||||
|
|
||||||
inputs.fenix.url = github:nix-community/fenix;
|
|
||||||
inputs.fenix.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
|
|
||||||
inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }:
|
|
||||||
let
|
let
|
||||||
name = "server";
|
name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||||
pkgs = system: import nixpkgs {
|
src = builtins.path {
|
||||||
inherit system;
|
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 {
|
in flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
inputsFrom = [ self.packages.${pkgs.system}.default ];
|
imports = [ flake-parts.flakeModules.easyOverlay ];
|
||||||
|
systems = [ "aarch64-linux" "x86_64-linux" "riscv64-linux" ];
|
||||||
# ---- START package specific dev settings ----
|
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
||||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
_module.args.pkgs = import inputs.nixpkgs {
|
||||||
# ---- END package specific dev settings ----
|
inherit system;
|
||||||
};
|
overlays = [ inputs.self.overlays.default ];
|
||||||
package = pkgs:
|
};
|
||||||
let
|
overlayAttrs = config.packages;
|
||||||
toolchain = fenix.packages.${pkgs.system}.minimal.toolchain;
|
packages = let sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
|
||||||
in
|
in {
|
||||||
(pkgs.makeRustPlatform {
|
default = self'.packages.${name};
|
||||||
cargo = toolchain;
|
gnome-graphical-test = self'.checks.gnome-graphical-test;
|
||||||
rustc = toolchain;
|
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
|
||||||
}).buildRustPackage rec {
|
inherit name src sk_gpu;
|
||||||
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
|
};
|
||||||
{
|
apps.default = {
|
||||||
overlays.default = final: prev: {
|
type = "app";
|
||||||
stardust-xr = (prev.stardust-xr or {}) // {
|
program = self'.packages.${name} + "/bin/stardust-xr-server";
|
||||||
${name} = package final;
|
};
|
||||||
|
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 = {
|
||||||
packages."x86_64-linux".default = package (pkgs "x86_64-linux");
|
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||||
packages."aarch64-linux".default = package (pkgs "aarch64-linux");
|
effects = let
|
||||||
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||||
packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test;
|
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
|
||||||
packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test;
|
in { ref, rev, ... }: {
|
||||||
|
gnome-graphical-test = hci-effects.mkEffect {
|
||||||
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; });
|
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
|
||||||
checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; });
|
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
|
||||||
|
effectScript = ''
|
||||||
devShells."x86_64-linux".default = shell (pkgs "x86_64-linux");
|
readSecretString stardustxrDiscord .webhook > .webhook
|
||||||
devShells."aarch64-linux".default = shell (pkgs "aarch64-linux");
|
readSecretString stardustxrIpfs .basicauth > .basicauth
|
||||||
|
set -x
|
||||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
export RESPONSE=$(curl -H @.basicauth -F file=@${
|
||||||
|
self.packages."x86_64-linux".gnome-graphical-test
|
||||||
effects = let
|
}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
|
||||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
|
||||||
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
|
set +x
|
||||||
in { branch, rev, ... }: {
|
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
|
||||||
gnome-graphical-test = hci-effects.mkEffect {
|
${pkgs.discord-sh}/bin/discord.sh \
|
||||||
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
|
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
|
||||||
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
|
--field "Ref;${ref}" \
|
||||||
effectScript = ''
|
--field "Commit ID;${rev}" \
|
||||||
readSecretString stardustxrDiscord .webhook > .webhook
|
--field "Flatland Revision;${flatland.rev}" \
|
||||||
readSecretString stardustxrIpfs .basicauth > .basicauth
|
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
|
||||||
set -x
|
--image "$ADDRESS"
|
||||||
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"
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
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.8.16/sk_gpu.v2024.8.16.zip";
|
||||||
|
sha256 = "sha256-Wk3PZFlWqhrsQ8xG0sQaV2xSasdg2D7TMiPvl/CgtGU=";
|
||||||
|
};
|
||||||
|
in stdenv.mkDerivation rec {
|
||||||
|
name = "sk_gpu";
|
||||||
|
src = sk_gpu_zip;
|
||||||
|
unpackPhase = ''
|
||||||
|
unzip -d $out ${sk_gpu_zip}
|
||||||
|
'';
|
||||||
|
nativeBuildInputs = [ unzip ];
|
||||||
|
}
|
||||||
86
nix/stardust-xr-server.nix
Normal file
86
nix/stardust-xr-server.nix
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{ rustPlatform
|
||||||
|
, src
|
||||||
|
, name
|
||||||
|
, libGL
|
||||||
|
, mesa
|
||||||
|
, xorg
|
||||||
|
, fontconfig
|
||||||
|
, libxkbcommon
|
||||||
|
, libclang
|
||||||
|
|
||||||
|
, cmake
|
||||||
|
, cpm-cmake
|
||||||
|
, pkg-config
|
||||||
|
, llvmPackages
|
||||||
|
, fetchFromGitHub
|
||||||
|
, sk_gpu
|
||||||
|
, libXau
|
||||||
|
|
||||||
|
, libXdmcp
|
||||||
|
, stdenv
|
||||||
|
, lib
|
||||||
|
, openxr-loader
|
||||||
|
}:
|
||||||
|
|
||||||
|
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=";
|
||||||
|
};
|
||||||
|
|
||||||
|
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
|
||||||
|
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
|
||||||
|
DEP_SK_GPU_SOURCE = "${sk_gpu}";
|
||||||
|
|
||||||
|
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 ];
|
||||||
|
buildInputs = [
|
||||||
|
libGL
|
||||||
|
mesa
|
||||||
|
xorg.libX11.dev
|
||||||
|
xorg.libXft
|
||||||
|
xorg.libXfixes
|
||||||
|
fontconfig
|
||||||
|
libxkbcommon
|
||||||
|
libXau
|
||||||
|
libXdmcp
|
||||||
|
openxr-loader
|
||||||
|
];
|
||||||
|
LIBCLANG_PATH = "${libclang.lib}/lib";
|
||||||
|
}
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
use super::scenegraph::Scenegraph;
|
use super::{
|
||||||
|
client_state::{ClientStateParsed, CLIENT_STATES},
|
||||||
|
destroy_queue,
|
||||||
|
scenegraph::Scenegraph,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{registry::OwnedRegistry, task},
|
core::{registry::OwnedRegistry, task},
|
||||||
nodes::{
|
nodes::{
|
||||||
audio, data, drawable, fields, hmd, input, items,
|
audio, data, drawable, fields, input, items,
|
||||||
root::Root,
|
root::{ClientState, Root},
|
||||||
spatial,
|
spatial, Node,
|
||||||
startup::{self, StartupSettings, STARTUP_SETTINGS},
|
|
||||||
Node,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
|
use global_counter::primitive::exact::CounterU32;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use stardust_xr::messenger::{self, MessageSenderHandle};
|
use stardust_xr::messenger::{self, MessageSenderHandle};
|
||||||
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
|
use std::{fmt::Debug, fs, iter::FromIterator, path::PathBuf, sync::Arc};
|
||||||
use tokio::{net::UnixStream, task::JoinHandle};
|
use tokio::{net::UnixStream, task::JoinHandle};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
@@ -31,10 +34,11 @@ lazy_static! {
|
|||||||
disconnect_status: OnceCell::new(),
|
disconnect_status: OnceCell::new(),
|
||||||
|
|
||||||
message_sender_handle: None,
|
message_sender_handle: None,
|
||||||
|
id_counter: CounterU32::new(0),
|
||||||
scenegraph: Default::default(),
|
scenegraph: Default::default(),
|
||||||
root: OnceCell::new(),
|
root: OnceCell::new(),
|
||||||
base_resource_prefixes: Default::default(),
|
base_resource_prefixes: Default::default(),
|
||||||
startup_settings: None,
|
state: OnceCell::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,24 +50,25 @@ pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
|
|||||||
.map(|(k, v)| (k.to_string(), v.to_string())),
|
.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")?;
|
let token = env.get("STARDUST_STARTUP_TOKEN")?;
|
||||||
STARTUP_SETTINGS.lock().get(token).cloned()
|
CLIENT_STATES.lock().get(token).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
pid: Option<i32>,
|
pub pid: Option<i32>,
|
||||||
// env: Option<FxHashMap<String, String>>,
|
// env: Option<FxHashMap<String, String>>,
|
||||||
exe: Option<PathBuf>,
|
exe: Option<PathBuf>,
|
||||||
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||||
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
|
||||||
disconnect_status: OnceCell<Result<()>>,
|
disconnect_status: OnceCell<Result<()>>,
|
||||||
|
|
||||||
|
id_counter: CounterU32,
|
||||||
pub message_sender_handle: Option<MessageSenderHandle>,
|
pub message_sender_handle: Option<MessageSenderHandle>,
|
||||||
pub scenegraph: Arc<Scenegraph>,
|
pub scenegraph: Arc<Scenegraph>,
|
||||||
pub root: OnceCell<Arc<Root>>,
|
pub root: OnceCell<Arc<Root>>,
|
||||||
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
|
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
|
||||||
pub startup_settings: Option<StartupSettings>,
|
pub state: OnceCell<ClientState>,
|
||||||
}
|
}
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
|
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
|
||||||
@@ -80,7 +85,10 @@ impl Client {
|
|||||||
|
|
||||||
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
|
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
|
||||||
let scenegraph = Arc::new(Scenegraph::default());
|
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 client = CLIENTS.add(Client {
|
let client = CLIENTS.add(Client {
|
||||||
pid,
|
pid,
|
||||||
@@ -91,23 +99,25 @@ impl Client {
|
|||||||
flush_join_handle: OnceCell::new(),
|
flush_join_handle: OnceCell::new(),
|
||||||
disconnect_status: OnceCell::new(),
|
disconnect_status: OnceCell::new(),
|
||||||
|
|
||||||
|
id_counter: CounterU32::new(256),
|
||||||
message_sender_handle: Some(messenger_tx.handle()),
|
message_sender_handle: Some(messenger_tx.handle()),
|
||||||
scenegraph: scenegraph.clone(),
|
scenegraph: scenegraph.clone(),
|
||||||
root: OnceCell::new(),
|
root: OnceCell::new(),
|
||||||
base_resource_prefixes: Default::default(),
|
base_resource_prefixes: Default::default(),
|
||||||
startup_settings,
|
state: OnceCell::default(),
|
||||||
});
|
});
|
||||||
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
|
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
|
||||||
let _ = client.root.set(Root::create(&client)?);
|
let _ = client.root.set(Root::create(&client, state.root)?);
|
||||||
hmd::make_alias(&client)?;
|
|
||||||
spatial::create_interface(&client)?;
|
spatial::create_interface(&client)?;
|
||||||
fields::create_interface(&client)?;
|
fields::create_interface(&client)?;
|
||||||
drawable::create_interface(&client)?;
|
drawable::create_interface(&client)?;
|
||||||
audio::create_interface(&client)?;
|
audio::create_interface(&client)?;
|
||||||
data::create_interface(&client)?;
|
data::create_interface(&client)?;
|
||||||
items::create_interface(&client)?;
|
|
||||||
input::create_interface(&client)?;
|
input::create_interface(&client)?;
|
||||||
startup::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
|
let pid_printable = pid
|
||||||
.map(|pid| pid.to_string())
|
.map(|pid| pid.to_string())
|
||||||
@@ -131,11 +141,8 @@ impl Client {
|
|||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
match messenger_rx.dispatch(&*scenegraph).await {
|
if let Err(e) = messenger_rx.dispatch(&*scenegraph).await {
|
||||||
Err(e) => {
|
client.disconnect(Err(e.into()));
|
||||||
client.disconnect(Err(e.into()));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,11 +156,8 @@ impl Client {
|
|||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
match messenger_tx.flush().await {
|
if let Err(e) = messenger_tx.flush().await {
|
||||||
Err(e) => {
|
client.disconnect(Err(e.into()));
|
||||||
client.disconnect(Err(e.into()));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,10 +168,35 @@ impl Client {
|
|||||||
Ok(client)
|
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]
|
#[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
|
self.scenegraph
|
||||||
.get_node(path)
|
.get_node(id)
|
||||||
.ok_or_else(|| eyre!("{} not found", name))
|
.ok_or_else(|| eyre!("{} not found", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +208,22 @@ impl Client {
|
|||||||
if let Some(flush_join_handle) = self.flush_join_handle.get() {
|
if let Some(flush_join_handle) = self.flush_join_handle.get() {
|
||||||
flush_join_handle.abort();
|
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 {
|
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::{get_env, Client};
|
||||||
|
use crate::nodes::{root::ClientState, spatial::Spatial, Node};
|
||||||
|
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('/').last().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,24 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
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: Lazy<(
|
||||||
|
mpsc::UnboundedSender<Anything>,
|
||||||
|
Mutex<mpsc::UnboundedReceiver<Anything>>,
|
||||||
|
)> = Lazy::new(|| {
|
||||||
|
let (tx, rx) = unbounded_channel();
|
||||||
|
(tx, Mutex::new(rx))
|
||||||
|
});
|
||||||
|
|
||||||
pub fn add<T: Any + Sync + Send>(thing: T) {
|
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() {
|
pub fn clear() {
|
||||||
MAIN_DESTROY_QUEUE.lock().clear();
|
while let Ok(thing) = MAIN_DESTROY_QUEUE.1.lock().try_recv() {
|
||||||
|
drop(thing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/core/idl_utils.rs
Normal file
11
src/core/idl_utils.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_interface {
|
||||||
|
($iface:ident) => {
|
||||||
|
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
||||||
|
let node = Node::from_id(client, INTERFACE_NODE_ID, false);
|
||||||
|
<$iface as self::InterfaceAspect>::add_node_members(&node);
|
||||||
|
node.add_to_scenegraph()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod client_state;
|
||||||
pub mod delta;
|
pub mod delta;
|
||||||
pub mod destroy_queue;
|
pub mod destroy_queue;
|
||||||
pub mod eventloop;
|
pub mod idl_utils;
|
||||||
pub mod node_collections;
|
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod scenegraph;
|
pub mod scenegraph;
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ use rustc_hash::FxHashMap;
|
|||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
|
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
|
||||||
|
|
||||||
impl<T: Send + Sync + ?Sized> Registry<T> {
|
impl<T: Send + Sync + ?Sized> Registry<T> {
|
||||||
@@ -12,9 +13,7 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
|||||||
Registry(const_mutex(None))
|
Registry(const_mutex(None))
|
||||||
}
|
}
|
||||||
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
|
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
|
||||||
MutexGuard::map(self.0.lock(), |r| {
|
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
|
||||||
r.get_or_insert_with(|| FxHashMap::default())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn add(&self, t: T) -> Arc<T>
|
pub fn add(&self, t: T) -> Arc<T>
|
||||||
where
|
where
|
||||||
@@ -32,12 +31,38 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
|||||||
self.lock()
|
self.lock()
|
||||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
||||||
}
|
}
|
||||||
|
pub fn get_changes(old: &Registry<T>, new: &Registry<T>) -> (Vec<Arc<T>>, Vec<Arc<T>>) {
|
||||||
|
let old = old.lock();
|
||||||
|
let new = new.lock();
|
||||||
|
|
||||||
|
let mut added = Vec::new();
|
||||||
|
let mut removed = Vec::new();
|
||||||
|
|
||||||
|
for (id, entry) in new.iter() {
|
||||||
|
if let Some(entry) = entry.upgrade() {
|
||||||
|
if !old.contains_key(id) {
|
||||||
|
added.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (id, entry) in old.iter() {
|
||||||
|
if let Some(entry) = entry.upgrade() {
|
||||||
|
if !new.contains_key(id) {
|
||||||
|
removed.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(added, removed)
|
||||||
|
}
|
||||||
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
|
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
|
||||||
self.lock()
|
self.lock()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pair| pair.1.upgrade())
|
.filter_map(|pair| pair.1.upgrade())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
pub fn set(&self, other: &Registry<T>) {
|
||||||
|
self.lock().clone_from(&other.lock());
|
||||||
|
}
|
||||||
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
|
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
@@ -47,6 +72,14 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
|||||||
.filter_map(|pair| pair.1.upgrade())
|
.filter_map(|pair| pair.1.upgrade())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
pub fn retain<F: Fn(&Arc<T>) -> bool>(&self, f: F) {
|
||||||
|
self.lock().retain(|_, v| {
|
||||||
|
let Some(v) = v.upgrade() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
(f)(&v)
|
||||||
|
})
|
||||||
|
}
|
||||||
pub fn remove(&self, t: &T) {
|
pub fn remove(&self, t: &T) {
|
||||||
self.lock()
|
self.lock()
|
||||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
||||||
@@ -54,12 +87,27 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
|
|||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
self.lock().clear();
|
self.lock().clear();
|
||||||
}
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
let registry = self.0.lock();
|
||||||
|
let Some(registry) = &*registry else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if registry.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
registry.values().all(|v| v.strong_count() == 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
|
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(Mutex::new(self.0.lock().clone()))
|
Self(Mutex::new(self.0.lock().clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: Send + Sync + ?Sized> Default for Registry<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
|
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
|
||||||
|
|
||||||
@@ -68,9 +116,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
|
|||||||
OwnedRegistry(const_mutex(None))
|
OwnedRegistry(const_mutex(None))
|
||||||
}
|
}
|
||||||
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
|
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
|
||||||
MutexGuard::map(self.0.lock(), |r| {
|
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
|
||||||
r.get_or_insert_with(|| FxHashMap::default())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn add(&self, t: T) -> Arc<T>
|
pub fn add(&self, t: T) -> Arc<T>
|
||||||
where
|
where
|
||||||
@@ -90,9 +136,12 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
|
|||||||
self.lock()
|
self.lock()
|
||||||
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
|
.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()
|
self.lock()
|
||||||
.remove(&(ptr::addr_of!(*t) as *const () as usize));
|
.remove(&(ptr::addr_of!(*t) as *const () as usize))
|
||||||
}
|
}
|
||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
self.lock().clear();
|
self.lock().clear();
|
||||||
|
|||||||
@@ -1,83 +1,49 @@
|
|||||||
use color_eyre::eyre::eyre;
|
use stardust_xr::values::ResourceID;
|
||||||
use serde::{de::Visitor, Deserialize};
|
use std::{
|
||||||
use std::{ffi::OsStr, path::PathBuf};
|
ffi::OsStr,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
use super::client::Client;
|
||||||
pub enum ResourceID {
|
|
||||||
File(PathBuf),
|
lazy_static::lazy_static! {
|
||||||
Namespaced { namespace: String, path: PathBuf },
|
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 {
|
fn has_extension(path: &Path, extensions: &[&OsStr]) -> bool {
|
||||||
if let Some(path_extension) = path.extension() {
|
if let Some(path_extension) = path.extension() {
|
||||||
extensions.contains(&path_extension)
|
extensions.contains(&path_extension)
|
||||||
} else {
|
} else {
|
||||||
false
|
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,20 +1,24 @@
|
|||||||
use crate::core::client::Client;
|
use crate::nodes::alias::get_original;
|
||||||
use crate::nodes::Node;
|
use crate::nodes::Node;
|
||||||
|
use crate::{core::client::Client, nodes::Message};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use serde::Serialize;
|
||||||
use stardust_xr::scenegraph;
|
use stardust_xr::scenegraph;
|
||||||
use stardust_xr::scenegraph::ScenegraphError;
|
use stardust_xr::scenegraph::ScenegraphError;
|
||||||
|
use stardust_xr::schemas::flex::serialize;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::os::fd::OwnedFd;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use tracing::{debug, debug_span, instrument};
|
use tokio::sync::oneshot;
|
||||||
|
use tracing::{debug, debug_span};
|
||||||
use core::hash::BuildHasherDefault;
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use rustc_hash::FxHasher;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Scenegraph {
|
pub struct Scenegraph {
|
||||||
pub(super) client: OnceCell<Weak<Client>>,
|
pub(super) client: OnceCell<Weak<Client>>,
|
||||||
nodes: DashMap<String, Arc<Node>, BuildHasherDefault<FxHasher>>,
|
nodes: Mutex<FxHashMap<u64, Arc<Node>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scenegraph {
|
impl Scenegraph {
|
||||||
@@ -29,46 +33,105 @@ impl Scenegraph {
|
|||||||
}
|
}
|
||||||
pub fn add_node_raw(&self, node: Arc<Node>) {
|
pub fn add_node_raw(&self, node: Arc<Node>) {
|
||||||
debug!(node = ?&*node, "Add node");
|
debug!(node = ?&*node, "Add node");
|
||||||
let path = node.get_path().to_string();
|
self.nodes.lock().insert(node.get_id(), node);
|
||||||
self.nodes.insert(path, node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
pub fn get_node(&self, node: u64) -> Option<Arc<Node>> {
|
||||||
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
|
let node = self.nodes.lock().get(&node)?.clone();
|
||||||
let mut node = self.nodes.get(path)?.clone();
|
get_original(node, true)
|
||||||
while let Some(alias) = node.alias.get() {
|
|
||||||
node = alias.original.upgrade()?;
|
|
||||||
}
|
|
||||||
Some(node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
|
pub fn remove_node(&self, node: u64) -> Option<Arc<Node>> {
|
||||||
debug!(path, "Remove node");
|
debug!(node, "Remove node");
|
||||||
let (_, node) = self.nodes.remove(path)?;
|
self.nodes.lock().remove(&node)
|
||||||
Some(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() -> color_eyre::eyre::Result<Message>>(self, f: F) {
|
||||||
|
self.send(f().map_err(|e| ScenegraphError::MethodError {
|
||||||
|
error: e.to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
pub fn wrap_async<T: Serialize>(
|
||||||
|
self,
|
||||||
|
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static,
|
||||||
|
) {
|
||||||
|
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn map_method_return<T: Serialize>(
|
||||||
|
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
|
||||||
|
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
|
||||||
|
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
|
||||||
|
error: e.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError {
|
||||||
|
error: format!("Internal: Serialization failed: {e}"),
|
||||||
|
})?;
|
||||||
|
Ok((serialized_value, fds))
|
||||||
|
}
|
||||||
impl scenegraph::Scenegraph for Scenegraph {
|
impl scenegraph::Scenegraph for Scenegraph {
|
||||||
fn send_signal(&self, path: &str, method: &str, data: &[u8]) -> Result<(), ScenegraphError> {
|
fn send_signal(
|
||||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)};
|
&self,
|
||||||
debug_span!("Handle signal", path, method).in_scope(|| {
|
node: u64,
|
||||||
self.get_node(path)
|
method: u64,
|
||||||
|
data: &[u8],
|
||||||
|
fds: Vec<OwnedFd>,
|
||||||
|
) -> Result<(), ScenegraphError> {
|
||||||
|
let Some(client) = self.get_client() else {
|
||||||
|
return Err(ScenegraphError::SignalNotFound);
|
||||||
|
};
|
||||||
|
debug_span!("Handle signal", node, method).in_scope(|| {
|
||||||
|
self.get_node(node)
|
||||||
.ok_or(ScenegraphError::NodeNotFound)?
|
.ok_or(ScenegraphError::NodeNotFound)?
|
||||||
.send_local_signal(client, method, data)
|
.send_local_signal(
|
||||||
|
client,
|
||||||
|
method,
|
||||||
|
Message {
|
||||||
|
data: data.to_vec(),
|
||||||
|
fds,
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn execute_method(
|
fn execute_method(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
node: u64,
|
||||||
method: &str,
|
method: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Result<Vec<u8>, ScenegraphError> {
|
fds: Vec<OwnedFd>,
|
||||||
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)};
|
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
||||||
debug_span!("Handle method", path, method).in_scope(|| {
|
) {
|
||||||
self.get_node(path)
|
let Some(client) = self.get_client() else {
|
||||||
.ok_or(ScenegraphError::NodeNotFound)?
|
let _ = response.send(Err(ScenegraphError::MethodNotFound));
|
||||||
.execute_local_method(client, method, data)
|
return;
|
||||||
})
|
};
|
||||||
|
debug!(node, method, "Handle method");
|
||||||
|
let Some(node) = self.get_node(node) else {
|
||||||
|
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
node.execute_local_method(
|
||||||
|
client,
|
||||||
|
method,
|
||||||
|
Message {
|
||||||
|
data: data.to_vec(),
|
||||||
|
fds,
|
||||||
|
},
|
||||||
|
MethodResponseSender(response),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[instrument(level = "debug", skip_all)]
|
|
||||||
pub fn new<
|
pub fn new<
|
||||||
F: FnOnce() -> S,
|
F: FnOnce() -> S,
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
|||||||
409
src/main.rs
409
src/main.rs
@@ -1,73 +1,86 @@
|
|||||||
|
#![allow(clippy::empty_docs)]
|
||||||
mod core;
|
mod core;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod objects;
|
mod objects;
|
||||||
|
mod session;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
mod wayland;
|
mod wayland;
|
||||||
|
|
||||||
use crate::core::destroy_queue;
|
use crate::core::destroy_queue;
|
||||||
use crate::nodes::{audio, drawable, hmd, input};
|
use crate::nodes::items::camera;
|
||||||
use crate::objects::input::eye_pointer::EyePointer;
|
use crate::nodes::{audio, drawable, input};
|
||||||
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 self::core::eventloop::EventLoop;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use core::client::Client;
|
||||||
|
use core::task;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
use objects::ServerObjects;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use session::{launch_start, save_session};
|
||||||
use stardust_xr::server;
|
use stardust_xr::server;
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use stereokit::{
|
use stereokit_rust::material::Material;
|
||||||
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread,
|
use stereokit_rust::shader::Shader;
|
||||||
TextureFormat, TextureType,
|
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, OriginMode, QuitReason, SkSettings};
|
||||||
};
|
use stereokit_rust::system::{LogLevel, Renderer};
|
||||||
use stereokit::{DisplayBlend, Sk};
|
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
|
||||||
use tokio::{runtime::Handle, sync::oneshot};
|
use stereokit_rust::ui::Ui;
|
||||||
|
use stereokit_rust::util::{Color128, Time};
|
||||||
|
use tokio::net::UnixListener;
|
||||||
|
use tokio::sync::Notify;
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
use tracing::{debug_span, error, info};
|
use tracing::{debug_span, error, info};
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
use zbus::fdo::ObjectManager;
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct CliArgs {
|
struct CliArgs {
|
||||||
/// Force flatscreen mode and use the mouse pointer as a 3D pointer
|
/// Force flatscreen mode and use the mouse pointer as a 3D pointer
|
||||||
#[clap(short, long, action)]
|
#[clap(short, long, action)]
|
||||||
flatscreen: bool,
|
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
|
/// Run Stardust XR as an overlay with given priority
|
||||||
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
|
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
|
||||||
overlay_priority: Option<u32>,
|
overlay_priority: Option<u32>,
|
||||||
|
|
||||||
/// Don't create a tip input for controller because SOME RUNTIMES will lie
|
/// Debug the clients started by the server
|
||||||
#[clap(long, action)]
|
#[clap(short = 'd', long = "debug", action)]
|
||||||
disable_controller: bool,
|
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.
|
/// 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)]
|
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
|
||||||
startup_script: Option<PathBuf>,
|
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||||
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
|
|
||||||
|
|
||||||
struct EventLoopInfo {
|
// #[tokio::main]
|
||||||
tokio_handle: Handle,
|
#[tokio::main(flavor = "current_thread")]
|
||||||
socket_path: PathBuf,
|
async fn main() {
|
||||||
}
|
color_eyre::install().unwrap();
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let registry = tracing_subscriber::registry();
|
let registry = tracing_subscriber::registry();
|
||||||
|
|
||||||
#[cfg(feature = "profile_app")]
|
#[cfg(feature = "profile_app")]
|
||||||
let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new()
|
let registry = registry.with(
|
||||||
.include_args(true)
|
tracing_tracy::TracyLayer::new(tracing_tracy::DefaultConfig::default())
|
||||||
.build();
|
.with_filter(LevelFilter::DEBUG),
|
||||||
#[cfg(feature = "profile_app")]
|
);
|
||||||
let registry = registry.with(chrome_layer);
|
|
||||||
|
|
||||||
#[cfg(feature = "profile_tokio")]
|
#[cfg(feature = "profile_tokio")]
|
||||||
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
|
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
|
||||||
@@ -81,22 +94,93 @@ fn main() {
|
|||||||
.with_filter(EnvFilter::from_default_env());
|
.with_filter(EnvFilter::from_default_env());
|
||||||
registry.with(log_layer).init();
|
registry.with(log_layer).init();
|
||||||
|
|
||||||
|
let cli_args = CliArgs::parse();
|
||||||
|
|
||||||
|
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");
|
let project_dirs = ProjectDirs::from("", "", "stardust");
|
||||||
if project_dirs.is_none() {
|
if project_dirs.is_none() {
|
||||||
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
|
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
|
||||||
}
|
}
|
||||||
let cli_args = Arc::new(CliArgs::parse());
|
|
||||||
|
|
||||||
let sk = stereokit::Settings {
|
let dbus_connection = Connection::session()
|
||||||
app_name: "Stardust XR".to_string(),
|
.await
|
||||||
display_preference: if cli_args.flatscreen {
|
.expect("Could not open dbus session");
|
||||||
DisplayMode::Flatscreen
|
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 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)
|
||||||
|
});
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stereokit_loop(
|
||||||
|
sk_ready_notifier: Arc<Notify>,
|
||||||
|
project_dirs: Option<ProjectDirs>,
|
||||||
|
args: CliArgs,
|
||||||
|
dbus_connection: Connection,
|
||||||
|
) {
|
||||||
|
let sk = SkSettings::default()
|
||||||
|
.app_name("Stardust XR")
|
||||||
|
.mode(if args.flatscreen {
|
||||||
|
AppMode::Simulator
|
||||||
} else {
|
} else {
|
||||||
DisplayMode::MixedReality
|
AppMode::XR
|
||||||
},
|
})
|
||||||
blend_preference: DisplayBlend::AnyTransparent,
|
.depth_mode(DepthMode::D32)
|
||||||
depth_mode: DepthMode::D32,
|
.log_filter(match EnvFilter::from_default_env().max_level_hint() {
|
||||||
log_filter: match EnvFilter::from_default_env().max_level_hint() {
|
|
||||||
Some(LevelFilter::ERROR) => LogLevel::Error,
|
Some(LevelFilter::ERROR) => LogLevel::Error,
|
||||||
Some(LevelFilter::WARN) => LogLevel::Warning,
|
Some(LevelFilter::WARN) => LogLevel::Warning,
|
||||||
Some(LevelFilter::INFO) => LogLevel::Inform,
|
Some(LevelFilter::INFO) => LogLevel::Inform,
|
||||||
@@ -104,192 +188,90 @@ fn main() {
|
|||||||
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
|
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
|
||||||
Some(LevelFilter::OFF) => LogLevel::None,
|
Some(LevelFilter::OFF) => LogLevel::None,
|
||||||
None => LogLevel::Warning,
|
None => LogLevel::Warning,
|
||||||
},
|
})
|
||||||
overlay_app: cli_args.overlay_priority.is_some(),
|
.overlay_app(args.overlay_priority.is_some())
|
||||||
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
|
.overlay_priority(args.overlay_priority.unwrap_or(u32::MAX))
|
||||||
disable_desktop_input_window: true,
|
.disable_desktop_input_window(true)
|
||||||
..Default::default()
|
.origin(OriginMode::Local)
|
||||||
}
|
.init()
|
||||||
.init()
|
.expect("StereoKit failed to initialize");
|
||||||
.expect("StereoKit failed to initialize");
|
|
||||||
let _ = SK_MULTITHREAD.set(sk.multithreaded());
|
|
||||||
info!("Init StereoKit");
|
info!("Init StereoKit");
|
||||||
|
|
||||||
sk.material_set_shader(
|
Renderer::multisample(0);
|
||||||
sk.material_find("default/material_pbr").unwrap(),
|
Material::default().shader(Shader::pbr_clip());
|
||||||
sk.shader_find("default/shader_pbr_clip").unwrap(),
|
Ui::enable_far_interact(false);
|
||||||
);
|
|
||||||
|
|
||||||
// Skytex/light stuff
|
// Skytex/light stuff
|
||||||
{
|
{
|
||||||
if let Some((light, tex)) = project_dirs
|
if let Some(sky) = project_dirs
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|dirs| {
|
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||||
let skytex_path = dirs.config_dir().join("skytex.hdr");
|
.filter(|f| f.exists())
|
||||||
skytex_path
|
.and_then(|p| SHCubemap::from_cubemap_equirectangular(p, true, 100).ok())
|
||||||
.exists()
|
|
||||||
.then(|| sk.tex_create_cubemap_file(&skytex_path, true, 100).ok())
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
{
|
{
|
||||||
sk.render_set_skytex(&tex);
|
sky.render_as_sky();
|
||||||
sk.render_set_skylight(light);
|
|
||||||
} else {
|
} else {
|
||||||
sk.render_set_skytex(sk.tex_gen_color(
|
Renderer::skytex(Tex::gen_color(
|
||||||
BLACK,
|
Color128::BLACK,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
TextureType::CUBEMAP,
|
TexType::Cubemap,
|
||||||
TextureFormat::RGBA32,
|
TexFormat::RGBA32,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_pointer = cli_args
|
|
||||||
.flatscreen
|
|
||||||
.then(MousePointer::new)
|
|
||||||
.transpose()
|
|
||||||
.unwrap();
|
|
||||||
let mut hands = (!cli_args.flatscreen)
|
|
||||||
.then(|| {
|
|
||||||
let left = SkHand::new(Handed::Left).ok();
|
|
||||||
let right = SkHand::new(Handed::Right).ok();
|
|
||||||
left.zip(right)
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
|
|
||||||
.then(|| {
|
|
||||||
let left = SkController::new(Handed::Left).ok();
|
|
||||||
let right = SkController::new(Handed::Right).ok();
|
|
||||||
left.zip(right)
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze())
|
|
||||||
.then(EyePointer::new)
|
|
||||||
.transpose()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if hands.is_none() {
|
|
||||||
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")]
|
#[cfg(feature = "wayland")]
|
||||||
let mut wayland = wayland::Wayland::new().unwrap();
|
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!");
|
info!("Stardust ready!");
|
||||||
|
|
||||||
if let Some(project_dirs) = project_dirs.as_ref() {
|
let mut objects = ServerObjects::new(
|
||||||
let startup_script_path = cli_args
|
dbus_connection.clone(),
|
||||||
.startup_script
|
&sk,
|
||||||
.clone()
|
args.disable_controllers,
|
||||||
.and_then(|p| p.canonicalize().ok())
|
args.disable_hands,
|
||||||
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
|
);
|
||||||
let _startup = Command::new(startup_script_path)
|
|
||||||
.stdin(Stdio::null())
|
|
||||||
.env(
|
|
||||||
"FLAT_WAYLAND_DISPLAY",
|
|
||||||
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.env("WAYLAND_DISPLAY", &wayland.socket_name)
|
|
||||||
.env(
|
|
||||||
"STARDUST_INSTANCE",
|
|
||||||
event_loop_info
|
|
||||||
.socket_path
|
|
||||||
.file_name()
|
|
||||||
.expect("Stardust socket path not found"),
|
|
||||||
)
|
|
||||||
.env("GDK_BACKEND", "wayland")
|
|
||||||
.env("QT_QPA_PLATFORM", "wayland")
|
|
||||||
.env("MOZ_ENABLE_WAYLAND", "1")
|
|
||||||
.env("CLUTTER_BACKEND", "wayland")
|
|
||||||
.env("SDL_VIDEODRIVER", "wayland")
|
|
||||||
.spawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut last_frame_delta = Duration::ZERO;
|
let mut last_frame_delta = Duration::ZERO;
|
||||||
let mut sleep_duration = Duration::ZERO;
|
let mut sleep_duration = Duration::ZERO;
|
||||||
debug_span!("StereoKit").in_scope(|| {
|
while let Some(token) = sk.step() {
|
||||||
sk.run(
|
let _span = debug_span!("StereoKit step");
|
||||||
|sk| {
|
let _span = _span.enter();
|
||||||
let _span = debug_span!("StereoKit step");
|
|
||||||
let _span = _span.enter();
|
|
||||||
|
|
||||||
hmd::frame(sk);
|
camera::update(token);
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
wayland.frame_event(sk);
|
wayland.frame_event();
|
||||||
destroy_queue::clear();
|
destroy_queue::clear();
|
||||||
|
|
||||||
if let Some(mouse_pointer) = &mouse_pointer {
|
objects.update(&sk, token);
|
||||||
mouse_pointer.update(sk);
|
input::process_input();
|
||||||
}
|
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
|
||||||
if let Some((left_hand, right_hand)) = &mut hands {
|
adaptive_sleep(
|
||||||
left_hand.update(sk);
|
&mut last_frame_delta,
|
||||||
right_hand.update(sk);
|
&mut sleep_duration,
|
||||||
}
|
Duration::from_micros(250),
|
||||||
if let Some((left_controller, right_controller)) = &mut controllers {
|
);
|
||||||
left_controller.update(sk);
|
|
||||||
right_controller.update(sk);
|
|
||||||
}
|
|
||||||
if let Some(eye_pointer) = &eye_pointer {
|
|
||||||
eye_pointer.update(sk);
|
|
||||||
}
|
|
||||||
if let Some(play_space) = &play_space {
|
|
||||||
play_space.update(sk);
|
|
||||||
}
|
|
||||||
input::process_input();
|
|
||||||
nodes::root::Root::send_frame_events(sk.time_elapsed_unscaled());
|
|
||||||
adaptive_sleep(
|
|
||||||
sk,
|
|
||||||
&mut last_frame_delta,
|
|
||||||
&mut sleep_duration,
|
|
||||||
Duration::from_micros(250),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
wayland.update(sk);
|
wayland.update();
|
||||||
drawable::draw(sk);
|
drawable::draw(token);
|
||||||
audio::update(sk);
|
audio::update();
|
||||||
#[cfg(feature = "wayland")]
|
}
|
||||||
wayland.make_context_current();
|
|
||||||
},
|
|
||||||
|_| {
|
|
||||||
info!("Cleanly shut down StereoKit");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
info!("Cleanly shut down StereoKit");
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
let _wayland = ManuallyDrop::new(wayland);
|
drop(wayland);
|
||||||
|
|
||||||
let _ = event_stop_tx.send(());
|
|
||||||
event_thread
|
|
||||||
.join()
|
|
||||||
.expect("Failed to cleanly shut down event loop")
|
|
||||||
.unwrap();
|
|
||||||
info!("Cleanly shut down Stardust");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adaptive_sleep(
|
fn adaptive_sleep(
|
||||||
sk: &impl StereoKitMultiThread,
|
|
||||||
last_frame_delta: &mut Duration,
|
last_frame_delta: &mut Duration,
|
||||||
sleep_duration: &mut Duration,
|
sleep_duration: &mut Duration,
|
||||||
sleep_duration_increase: 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 *last_frame_delta < frame_delta {
|
||||||
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_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) {
|
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
|
||||||
@@ -305,42 +287,3 @@ fn adaptive_sleep(
|
|||||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
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,141 @@
|
|||||||
use super::Node;
|
use super::{Aspect, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::{client::Client, registry::Registry};
|
||||||
use color_eyre::eyre::{ensure, Result};
|
use color_eyre::eyre::Result;
|
||||||
use std::sync::{Arc, Weak};
|
use std::{
|
||||||
|
ops::Add,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct AliasInfo {
|
pub struct AliasInfo {
|
||||||
pub(super) server_signals: Vec<&'static str>,
|
pub(super) server_signals: Vec<u64>,
|
||||||
pub(super) server_methods: Vec<&'static str>,
|
pub(super) server_methods: Vec<u64>,
|
||||||
pub(super) client_signals: Vec<&'static str>,
|
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 struct Alias {
|
||||||
pub(super) node: Weak<Node>,
|
pub(super) node: Weak<Node>,
|
||||||
pub original: Weak<Node>,
|
pub(super) original: Weak<Node>,
|
||||||
|
pub(super) info: AliasInfo,
|
||||||
pub info: AliasInfo,
|
|
||||||
}
|
}
|
||||||
impl Alias {
|
impl Alias {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
client: &Arc<Client>,
|
|
||||||
parent: &str,
|
|
||||||
name: &str,
|
|
||||||
original: &Arc<Node>,
|
original: &Arc<Node>,
|
||||||
|
client: &Arc<Client>,
|
||||||
info: AliasInfo,
|
info: AliasInfo,
|
||||||
|
list: Option<&AliasList>,
|
||||||
) -> Result<Arc<Node>> {
|
) -> Result<Arc<Node>> {
|
||||||
ensure!(
|
let node = Node::generate(client, true).add_to_scenegraph()?;
|
||||||
client
|
Self::add_to(&node, original, info)?;
|
||||||
.scenegraph
|
if let Some(list) = list {
|
||||||
.get_node(&(parent.to_string() + "/" + name))
|
list.add(&node);
|
||||||
.is_none(),
|
}
|
||||||
"Node already exists"
|
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 {
|
let alias = Alias {
|
||||||
node: Arc::downgrade(&node),
|
node: Arc::downgrade(new_node),
|
||||||
original: Arc::downgrade(original),
|
original: Arc::downgrade(original),
|
||||||
info,
|
info,
|
||||||
};
|
};
|
||||||
let alias = original.aliases.add(alias);
|
let alias = original.aliases.add(alias);
|
||||||
let _ = node.alias.set(alias);
|
new_node.add_aspect_raw(alias);
|
||||||
Ok(node)
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Aspect for Alias {
|
||||||
|
const NAME: &'static str = "Alias";
|
||||||
|
}
|
||||||
|
|
||||||
|
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: Aspect>(&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: Aspect>(&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,53 +1,45 @@
|
|||||||
use super::Node;
|
use super::{Aspect, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::destroy_queue;
|
use crate::core::destroy_queue;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::resource::ResourceID;
|
use crate::core::resource::get_resource_file;
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
use crate::create_interface;
|
||||||
use color_eyre::eyre::{ensure, eyre, Result};
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::spatial::{Spatial, Transform};
|
||||||
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use glam::{vec3, Vec4Swizzles};
|
use glam::{vec3, Vec4Swizzles};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use send_wrapper::SendWrapper;
|
use stardust_xr::values::ResourceID;
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::sync::Arc;
|
||||||
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
|
use std::{ffi::OsStr, path::PathBuf};
|
||||||
|
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
|
||||||
|
|
||||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||||
|
|
||||||
|
stardust_xr_server_codegen::codegen_audio_protocol!();
|
||||||
pub struct Sound {
|
pub struct Sound {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
|
|
||||||
volume: f32,
|
volume: f32,
|
||||||
pending_audio_path: PathBuf,
|
pending_audio_path: PathBuf,
|
||||||
sk_sound: OnceCell<SendWrapper<SkSound>>,
|
sk_sound: OnceCell<SkSound>,
|
||||||
instance: Mutex<Option<SoundInstance>>,
|
instance: Mutex<Option<SoundInst>>,
|
||||||
stop: Mutex<Option<()>>,
|
stop: Mutex<Option<()>>,
|
||||||
play: Mutex<Option<()>>,
|
play: Mutex<Option<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sound {
|
impl Sound {
|
||||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
|
||||||
ensure!(
|
let pending_audio_path = get_resource_file(
|
||||||
node.spatial.get().is_some(),
|
&resource_id,
|
||||||
"Internal: Node does not have a spatial attached!"
|
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||||
);
|
&[OsStr::new("wav"), OsStr::new("mp3")],
|
||||||
let pending_audio_path = resource_id
|
)
|
||||||
.get_file(
|
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||||
&node
|
|
||||||
.get_client()
|
|
||||||
.ok_or_else(|| eyre!("Client not found"))?
|
|
||||||
.base_resource_prefixes
|
|
||||||
.lock()
|
|
||||||
.clone(),
|
|
||||||
&[OsStr::new("wav"), OsStr::new("mp3")],
|
|
||||||
)
|
|
||||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
|
||||||
let sound = Sound {
|
let sound = Sound {
|
||||||
space: node.spatial.get().unwrap().clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
pending_audio_path,
|
pending_audio_path,
|
||||||
sk_sound: OnceCell::new(),
|
sk_sound: OnceCell::new(),
|
||||||
@@ -56,81 +48,77 @@ impl Sound {
|
|||||||
play: Mutex::new(None),
|
play: Mutex::new(None),
|
||||||
};
|
};
|
||||||
let sound_arc = SOUND_REGISTRY.add(sound);
|
let sound_arc = SOUND_REGISTRY.add(sound);
|
||||||
node.add_local_signal("play", Sound::play_flex);
|
node.add_aspect_raw(sound_arc.clone());
|
||||||
node.add_local_signal("stop", Sound::stop_flex);
|
<Sound as SoundAspect>::add_node_members(node);
|
||||||
let _ = node.sound.set(sound_arc.clone());
|
|
||||||
Ok(sound_arc)
|
Ok(sound_arc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, sk: &impl StereoKitDraw) {
|
fn update(&self) {
|
||||||
let sound = self.sk_sound.get_or_init(|| {
|
let sound = self
|
||||||
SendWrapper::new(sk.sound_create(self.pending_audio_path.clone()).unwrap())
|
.sk_sound
|
||||||
});
|
.get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
|
||||||
if self.stop.lock().take().is_some() {
|
if self.stop.lock().take().is_some() {
|
||||||
if let Some(instance) = self.instance.lock().take() {
|
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() {
|
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
|
||||||
self.instance.lock().replace(sk.sound_play(
|
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
|
||||||
sound.as_ref(),
|
self.instance.lock().replace(instance);
|
||||||
vec3(0.0, 0.0, 0.0),
|
|
||||||
self.volume,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
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>, _data: &[u8]) -> Result<()> {
|
impl Aspect for Sound {
|
||||||
let sound = node.sound.get().unwrap();
|
const NAME: &'static str = "Sound";
|
||||||
|
}
|
||||||
|
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(());
|
sound.play.lock().replace(());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
let sound = node.get_aspect::<Sound>().unwrap();
|
||||||
let sound = node.sound.get().unwrap();
|
|
||||||
sound.stop.lock().replace(());
|
sound.stop.lock().replace(());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(sk: &impl StereoKitDraw) {
|
|
||||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
|
||||||
sound.update(sk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
|
||||||
let node = Node::create(client, "", "audio", false);
|
|
||||||
node.add_local_signal("create_sound", create_flex);
|
|
||||||
node.add_to_scenegraph().map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateSoundInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
resource: ResourceID,
|
|
||||||
}
|
|
||||||
let info: CreateSoundInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/audio/sound", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
Sound::add_to(&node, info.resource)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Sound {
|
impl Drop for Sound {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(instance) = self.instance.lock().take() {
|
if let Some(sk_sound) = self.sk_sound.take() {
|
||||||
destroy_queue::add(instance);
|
destroy_queue::add(sk_sound);
|
||||||
}
|
}
|
||||||
SOUND_REGISTRY.remove(self);
|
SOUND_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update() {
|
||||||
|
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||||
|
sound.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_interface!(AudioInterface);
|
||||||
|
struct AudioInterface;
|
||||||
|
impl InterfaceAspect for AudioInterface {
|
||||||
|
#[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,30 +1,51 @@
|
|||||||
use super::alias::AliasInfo;
|
use super::alias::AliasList;
|
||||||
use super::fields::Field;
|
use super::fields::Field;
|
||||||
use super::spatial::{parse_transform, Spatial};
|
use super::spatial::{parse_transform, Spatial};
|
||||||
use super::{Alias, Node};
|
use super::{Alias, Aspect, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO};
|
use crate::create_interface;
|
||||||
use crate::nodes::spatial::find_spatial_parent;
|
use crate::nodes::fields::FIELD_ALIAS_INFO;
|
||||||
use color_eyre::eyre::{ensure, eyre, Result};
|
use crate::nodes::spatial::Transform;
|
||||||
use glam::vec3a;
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use mint::{Quaternion, Vector3};
|
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||||
use nanoid::nanoid;
|
use lazy_static::lazy_static;
|
||||||
use serde::{Deserialize, Serialize};
|
use parking_lot::Mutex;
|
||||||
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
|
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
|
||||||
use stardust_xr::values::Transform;
|
use stardust_xr::schemas::flex::flexbuffers;
|
||||||
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: probably just use d-bus for this stuff (custom protocol for exporting spatials as refs) because the mask stuff is just too confusing
|
||||||
|
|
||||||
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
|
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
|
||||||
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
|
||||||
|
|
||||||
pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
|
pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
|
||||||
|
flexbuffers::Reader::get_root(datamap.raw().as_slice())
|
||||||
|
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
|
||||||
|
.get_map()
|
||||||
|
.map_err(|_| eyre!("Mask is not a valid map"))
|
||||||
|
}
|
||||||
|
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
|
||||||
(|| -> Result<_> {
|
(|| -> Result<_> {
|
||||||
for key in mask_map_lesser.get_mask()?.iter_keys() {
|
for key in get_mask(mask_map_lesser)?.iter_keys() {
|
||||||
let lesser_key = mask_map_lesser.get_mask()?.index(key)?;
|
let lesser_key = get_mask(mask_map_lesser)?.index(key)?;
|
||||||
let greater_key = mask_map_greater.get_mask()?.index(key)?;
|
let greater_key = get_mask(mask_map_greater)?.index(key)?;
|
||||||
if lesser_key.to_string() != greater_key.to_string() {
|
// otherwise zero-length vectors don't count the same as a single type vector
|
||||||
|
if lesser_key.flexbuffer_type().is_heterogenous_vector()
|
||||||
|
&& lesser_key.as_vector().is_empty()
|
||||||
|
&& greater_key.flexbuffer_type().is_vector()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !lesser_key.flexbuffer_type().is_null()
|
||||||
|
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
|
||||||
|
{
|
||||||
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,49 +54,26 @@ pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mask(pub Vec<u8>);
|
stardust_xr_server_codegen::codegen_data_protocol!();
|
||||||
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 {
|
pub struct PulseSender {
|
||||||
uid: String,
|
|
||||||
node: Weak<Node>,
|
node: Weak<Node>,
|
||||||
pub mask: Mask,
|
pub mask: Datamap,
|
||||||
aliases: LifeLinkedNodeMap<String>,
|
aliases: AliasList,
|
||||||
|
field_aliases: AliasList,
|
||||||
}
|
}
|
||||||
impl PulseSender {
|
impl PulseSender {
|
||||||
pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> {
|
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
|
||||||
ensure!(
|
|
||||||
node.spatial.get().is_some(),
|
|
||||||
"Internal: Node does not have a spatial attached!"
|
|
||||||
);
|
|
||||||
|
|
||||||
let sender = PulseSender {
|
let sender = PulseSender {
|
||||||
uid: nanoid!(),
|
|
||||||
node: Arc::downgrade(node),
|
node: Arc::downgrade(node),
|
||||||
mask,
|
mask,
|
||||||
aliases: LifeLinkedNodeMap::default(),
|
aliases: AliasList::default(),
|
||||||
|
field_aliases: AliasList::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// <PulseSender as PulseSenderAspect>::add_node_members(node);
|
||||||
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
let sender = PULSE_SENDER_REGISTRY.add(sender);
|
||||||
let _ = node.pulse_sender.set(sender.clone());
|
node.add_aspect_raw(sender.clone());
|
||||||
node.add_local_signal("send_data", PulseSender::send_data_flex);
|
|
||||||
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
|
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
|
||||||
sender.handle_new_receiver(&receiver);
|
sender.handle_new_receiver(&receiver);
|
||||||
}
|
}
|
||||||
@@ -85,95 +83,60 @@ impl PulseSender {
|
|||||||
if !mask_matches(&self.mask, &receiver.mask) {
|
if !mask_matches(&self.mask, &receiver.mask) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(tx_node) = self.node.upgrade() else { return };
|
let Some(tx_node) = self.node.upgrade() else {
|
||||||
let Some(tx_client) = tx_node.get_client() else { return };
|
return;
|
||||||
let Some(rx_node) = receiver.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
|
// Receiver itself
|
||||||
let rx_alias = Alias::create(
|
let Ok(rx_alias) = Alias::create(
|
||||||
&tx_client,
|
|
||||||
tx_node.get_path(),
|
|
||||||
receiver.uid.as_str(),
|
|
||||||
&rx_node,
|
&rx_node,
|
||||||
AliasInfo {
|
&tx_client,
|
||||||
server_methods: vec!["sendData", "getTransform"],
|
PULSE_RECEIVER_ASPECT_ALIAS_INFO.clone(),
|
||||||
..Default::default()
|
Some(&self.aliases),
|
||||||
},
|
) else {
|
||||||
);
|
return;
|
||||||
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};
|
// Receiver's field
|
||||||
let _ = tx_node.send_remote_signal("new_receiver", &data);
|
let Ok(rx_field_alias) = Alias::create(
|
||||||
|
&rx_node
|
||||||
|
.get_aspect::<PulseReceiver>()
|
||||||
|
.unwrap()
|
||||||
|
.field
|
||||||
|
.spatial
|
||||||
|
.node()
|
||||||
|
.unwrap(),
|
||||||
|
&tx_client,
|
||||||
|
FIELD_ALIAS_INFO.clone(),
|
||||||
|
Some(&self.aliases),
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = pulse_sender_client::new_receiver(&tx_node, &rx_alias, &rx_field_alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
|
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
|
||||||
let uid = receiver.uid.as_str();
|
let Some(node) = receiver.node.upgrade() else {
|
||||||
self.aliases.remove(uid);
|
return;
|
||||||
self.aliases.remove(&(uid.to_string() + "-field"));
|
};
|
||||||
let Some(tx_node) = self.node.upgrade() else {return};
|
self.aliases.remove_aspect(receiver);
|
||||||
let Ok(data) = serialize(&uid) else {return};
|
self.field_aliases.remove_aspect(receiver.field.as_ref());
|
||||||
let _ = tx_node.send_remote_signal("drop_receiver", &data);
|
let Some(tx_node) = self.node.upgrade() else {
|
||||||
}
|
return;
|
||||||
|
};
|
||||||
fn send_data_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
let _ = pulse_sender_client::drop_receiver(&tx_node, node.get_id());
|
||||||
let info: SendDataInfo = deserialize(data)?;
|
|
||||||
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
|
|
||||||
let receiver =
|
|
||||||
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
|
|
||||||
let receiver_mask = &receiver_node
|
|
||||||
.get_aspect("Pulse receiver", "pulse receiver", |node| {
|
|
||||||
&node.pulse_receiver
|
|
||||||
})?
|
|
||||||
.mask;
|
|
||||||
|
|
||||||
let data_mask = Mask(info.data);
|
|
||||||
data_mask.get_mask()?;
|
|
||||||
ensure!(
|
|
||||||
mask_matches(receiver_mask, &data_mask),
|
|
||||||
"Message does not contain the same keys as the receiver's mask"
|
|
||||||
);
|
|
||||||
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Aspect for PulseSender {
|
||||||
|
const NAME: &'static str = "PulseSender";
|
||||||
|
}
|
||||||
|
impl PulseSenderAspect for PulseSender {}
|
||||||
impl Drop for PulseSender {
|
impl Drop for PulseSender {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
PULSE_SENDER_REGISTRY.remove(self);
|
PULSE_SENDER_REGISTRY.remove(self);
|
||||||
@@ -181,41 +144,52 @@ impl Drop for PulseSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PulseReceiver {
|
pub struct PulseReceiver {
|
||||||
uid: String,
|
|
||||||
pub node: Weak<Node>,
|
pub node: Weak<Node>,
|
||||||
pub field: Arc<Field>,
|
pub field: Arc<Field>,
|
||||||
pub mask: Mask,
|
pub mask: Datamap,
|
||||||
}
|
}
|
||||||
impl PulseReceiver {
|
impl PulseReceiver {
|
||||||
pub fn add_to(node: &Arc<Node>, field: Arc<Field>, mask: Mask) -> Result<Arc<PulseReceiver>> {
|
pub fn add_to(
|
||||||
ensure!(
|
node: &Arc<Node>,
|
||||||
node.spatial.get().is_some(),
|
field: Arc<Field>,
|
||||||
"Internal: Node does not have a spatial attached!"
|
mask: Datamap,
|
||||||
);
|
) -> Result<Arc<PulseReceiver>> {
|
||||||
|
|
||||||
let receiver = PulseReceiver {
|
let receiver = PulseReceiver {
|
||||||
uid: nanoid!(),
|
|
||||||
node: Arc::downgrade(node),
|
node: Arc::downgrade(node),
|
||||||
field,
|
field,
|
||||||
mask,
|
mask,
|
||||||
};
|
};
|
||||||
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
|
||||||
|
|
||||||
|
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
|
||||||
|
node.add_aspect_raw(receiver.clone());
|
||||||
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
|
||||||
sender.handle_new_receiver(&receiver);
|
sender.handle_new_receiver(&receiver);
|
||||||
}
|
}
|
||||||
let _ = node.pulse_receiver.set(receiver.clone());
|
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl Aspect for PulseReceiver {
|
||||||
|
const NAME: &'static str = "PulseReceiver";
|
||||||
|
}
|
||||||
|
impl PulseReceiverAspect for PulseReceiver {
|
||||||
|
fn send_data(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
sender: Arc<Node>,
|
||||||
|
data: Datamap,
|
||||||
|
) -> Result<()> {
|
||||||
|
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
|
||||||
|
|
||||||
pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> {
|
ensure!(
|
||||||
if let Some(node) = self.node.upgrade() {
|
mask_matches(&this_receiver.mask, &data),
|
||||||
node.send_remote_signal("data", &serialize(SendDataInfo { uid, data })?)?;
|
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
|
||||||
}
|
this_receiver.mask
|
||||||
|
);
|
||||||
|
pulse_receiver_client::data(&node, &sender, &data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for PulseReceiver {
|
impl Drop for PulseReceiver {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
PULSE_RECEIVER_REGISTRY.remove(self);
|
PULSE_RECEIVER_REGISTRY.remove(self);
|
||||||
@@ -225,62 +199,78 @@ impl Drop for PulseReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
create_interface!(DataInterface);
|
||||||
let node = Node::create(client, "", "data", false);
|
struct DataInterface;
|
||||||
node.add_local_signal("create_pulse_sender", create_pulse_sender_flex);
|
impl InterfaceAspect for DataInterface {
|
||||||
node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex);
|
fn create_pulse_sender(
|
||||||
node.add_to_scenegraph().map(|_| ())
|
_node: Arc<Node>,
|
||||||
}
|
calling_client: Arc<Client>,
|
||||||
|
id: u64,
|
||||||
pub fn create_pulse_sender_flex(
|
parent: Arc<Node>,
|
||||||
_node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreatePulseSenderInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
mask: Vec<u8>,
|
mask: Datamap,
|
||||||
|
) -> Result<()> {
|
||||||
|
get_mask(&mask)?;
|
||||||
|
let node = Node::from_id(&calling_client, id, true);
|
||||||
|
let parent = parent.get_aspect::<Spatial>()?;
|
||||||
|
let transform = transform.to_mat4(true, true, false);
|
||||||
|
|
||||||
|
let node = node.add_to_scenegraph()?;
|
||||||
|
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||||
|
PulseSender::add_to(&node, mask)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
let info: CreatePulseSenderInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/data/sender", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
|
|
||||||
let mask = Mask(info.mask);
|
fn create_pulse_receiver(
|
||||||
mask.get_mask()?;
|
_node: Arc<Node>,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
let node = node.add_to_scenegraph()?;
|
id: u64,
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
parent: Arc<Node>,
|
||||||
PulseSender::add_to(&node, mask)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_pulse_receiver_flex(
|
|
||||||
_node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreatePulseReceiverInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
field_path: &'a str,
|
field: Arc<Node>,
|
||||||
mask: Vec<u8>,
|
mask: Datamap,
|
||||||
}
|
) -> Result<()> {
|
||||||
let info: CreatePulseReceiverInfo = deserialize(data)?;
|
get_mask(&mask)?;
|
||||||
let node = Node::create(&calling_client, "/data/receiver", info.name, true);
|
let node = Node::from_id(&calling_client, id, true);
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
let parent = parent.get_aspect::<Spatial>()?;
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
let transform = parse_transform(transform, true, true, false);
|
||||||
let field = find_field(&calling_client, info.field_path)?;
|
let field = field.get_aspect::<Field>()?;
|
||||||
let mask = Mask(info.mask);
|
|
||||||
mask.get_mask()?;
|
|
||||||
|
|
||||||
let node = node.add_to_scenegraph()?;
|
let node = node.add_to_scenegraph()?;
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||||
PulseReceiver::add_to(&node, field, mask)?;
|
PulseReceiver::add_to(&node, field, mask)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_keymap(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
keymap: String,
|
||||||
|
) -> Result<u64> {
|
||||||
|
let mut keymaps = KEYMAPS.lock();
|
||||||
|
if let Some(found_keymap_id) = keymaps
|
||||||
|
.iter()
|
||||||
|
.filter(|(_k, v)| *v == &keymap)
|
||||||
|
.map(|(k, _v)| k)
|
||||||
|
.last()
|
||||||
|
{
|
||||||
|
return Ok(found_keymap_id.data().as_ffi());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = keymaps.insert(keymap);
|
||||||
|
Ok(key.data().as_ffi())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_keymap(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
keymap_id: u64,
|
||||||
|
) -> Result<String> {
|
||||||
|
let keymaps = KEYMAPS.lock();
|
||||||
|
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else {
|
||||||
|
bail!("Could not find keymap. Try registering it")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(keymap.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,118 +1,95 @@
|
|||||||
|
use super::{Line, LinesAspect};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry},
|
core::{client::Client, registry::Registry},
|
||||||
nodes::{
|
nodes::{spatial::Spatial, Aspect, Node},
|
||||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{bail, ensure, Result};
|
use color_eyre::eyre::Result;
|
||||||
use glam::Vec3A;
|
use glam::Vec3;
|
||||||
use mint::Vector3;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use portable_atomic::{AtomicBool, Ordering};
|
use prisma::Lerp;
|
||||||
use prisma::{Flatten, Lerp, Rgba};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::{schemas::flex::deserialize, values::Transform};
|
|
||||||
use std::{collections::VecDeque, sync::Arc};
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
|
use stereokit_rust::{
|
||||||
|
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128,
|
||||||
use super::Drawable;
|
};
|
||||||
|
|
||||||
static LINES_REGISTRY: Registry<Lines> = Registry::new();
|
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 {
|
pub struct Lines {
|
||||||
enabled: Arc<AtomicBool>,
|
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
data: Mutex<LineData>,
|
data: Mutex<Vec<Line>>,
|
||||||
}
|
}
|
||||||
impl Lines {
|
impl Lines {
|
||||||
fn add_to(node: &Arc<Node>, points: Vec<LinePointRaw>, cyclic: bool) -> Result<Arc<Lines>> {
|
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
|
||||||
ensure!(
|
let _ = node
|
||||||
node.drawable.get().is_none(),
|
.get_aspect::<Spatial>()
|
||||||
"Internal: Node already has a drawable attached!"
|
.unwrap()
|
||||||
);
|
.bounding_box_calc
|
||||||
|
.set(|node| {
|
||||||
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
|
let mut bounds = Bounds::default();
|
||||||
let mut bounds = Bounds::default();
|
if let Ok(lines) = node.get_aspect::<Lines>() {
|
||||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {return bounds};
|
for line in &*lines.data.lock() {
|
||||||
for point in &lines.data.lock().points {
|
for point in &line.points {
|
||||||
bounds = bounds_grow_to_fit_pt(bounds, point.point);
|
bounds.grown_point(Vec3::from(point.point));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bounds
|
}
|
||||||
});
|
bounds
|
||||||
|
});
|
||||||
|
|
||||||
let lines = LINES_REGISTRY.add(Lines {
|
let lines = LINES_REGISTRY.add(Lines {
|
||||||
enabled: node.enabled.clone(),
|
space: node.get_aspect::<Spatial>()?.clone(),
|
||||||
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(),
|
data: Mutex::new(lines),
|
||||||
data: Mutex::new(LineData { points, cyclic }),
|
|
||||||
});
|
});
|
||||||
node.add_local_signal("set_points", Lines::set_points_flex);
|
<Lines as LinesAspect>::add_node_members(node);
|
||||||
node.add_local_signal("set_cyclic", Lines::set_cyclic_flex);
|
node.add_aspect_raw(lines.clone());
|
||||||
let _ = node.drawable.set(Drawable::Lines(lines.clone()));
|
|
||||||
|
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
|
fn draw(&self, token: &MainThreadToken) {
|
||||||
let transform_mat = self.space.global_transform();
|
let transform_mat = self.space.global_transform();
|
||||||
let data = self.data.lock().clone();
|
let data = self.data.lock().clone();
|
||||||
let mut points: VecDeque<SkLinePoint> = data
|
for line in &data {
|
||||||
.points
|
let mut points: VecDeque<SkLinePoint> = line
|
||||||
.iter()
|
.points
|
||||||
.map(|p| SkLinePoint {
|
.iter()
|
||||||
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
|
.map(|p| SkLinePoint {
|
||||||
thickness: p.thickness,
|
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
|
||||||
color: p.color.map(|c| (c * 255.0) as u8).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 data.cyclic && !points.is_empty() {
|
.collect();
|
||||||
let first = data.points.first().unwrap();
|
if line.cyclic && !points.is_empty() {
|
||||||
let last = data.points.last().unwrap();
|
let first = line.points.first().unwrap();
|
||||||
let color = Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5);
|
let last = line.points.last().unwrap();
|
||||||
let connect_point = SkLinePoint {
|
|
||||||
pt: transform_mat
|
let color = Color128 {
|
||||||
.transform_point3a(Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5))
|
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
|
||||||
.into(),
|
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
|
||||||
thickness: (first.thickness + last.thickness) * 0.5,
|
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
|
||||||
color: Color128::from([color.red(), color.green(), color.blue(), color.alpha()])
|
a: first.color.a.lerp(&last.color.a, 0.5),
|
||||||
.into(),
|
};
|
||||||
};
|
let connect_point = SkLinePoint {
|
||||||
points.push_front(connect_point.clone());
|
pt: transform_mat
|
||||||
points.push_back(connect_point);
|
.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>, data: &[u8]) -> Result<()> {
|
impl Aspect for Lines {
|
||||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
const NAME: &'static str = "Lines";
|
||||||
|
}
|
||||||
let mut points: Vec<LinePointRaw> = deserialize(data)?;
|
impl LinesAspect for Lines {
|
||||||
for p in &mut points {
|
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
||||||
p.color[0] = p.color[0].powf(2.2);
|
let lines_aspect = node.get_aspect::<Lines>()?;
|
||||||
p.color[1] = p.color[1].powf(2.2);
|
*lines_aspect.data.lock() = lines;
|
||||||
p.color[2] = p.color[2].powf(2.2);
|
|
||||||
}
|
|
||||||
lines.data.lock().points = points;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn set_cyclic_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
|
|
||||||
|
|
||||||
lines.data.lock().cyclic = deserialize(data)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,36 +99,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() {
|
for lines in LINES_REGISTRY.get_valid_contents() {
|
||||||
if lines.enabled.load(Ordering::Relaxed) {
|
if let Some(node) = lines.space.node() {
|
||||||
lines.draw(draw_ctx);
|
if node.enabled() {
|
||||||
|
lines.draw(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateTextInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
points: Vec<LinePointRaw>,
|
|
||||||
cyclic: bool,
|
|
||||||
}
|
|
||||||
let mut info: CreateTextInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/drawable/lines", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
|
||||||
|
|
||||||
for p in &mut info.points {
|
|
||||||
p.color[0] = p.color[0].powf(2.2);
|
|
||||||
p.color[1] = p.color[1].powf(2.2);
|
|
||||||
p.color[2] = p.color[2].powf(2.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
Lines::add_to(&node, info.points, info.cyclic)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,54 +1,40 @@
|
|||||||
pub mod lines;
|
pub mod lines;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
#[cfg(feature = "wayland")]
|
||||||
|
pub mod shader_manipulation;
|
||||||
pub mod shaders;
|
pub mod shaders;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
use self::{
|
use self::{lines::Lines, model::Model, text::Text};
|
||||||
lines::Lines,
|
use super::{
|
||||||
model::{Model, ModelPart},
|
spatial::{Spatial, Transform},
|
||||||
text::Text,
|
Node,
|
||||||
};
|
};
|
||||||
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use super::Node;
|
use crate::{
|
||||||
use crate::core::client::Client;
|
core::{client::Client, resource::get_resource_file},
|
||||||
use color_eyre::eyre::Result;
|
create_interface,
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::{self, Result};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use stardust_xr::values::ResourceID;
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
|
||||||
use stereokit::StereoKitDraw;
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
// #[instrument(level = "debug", skip(sk))]
|
||||||
let node = Node::create(client, "", "drawable", false);
|
pub fn draw(token: &MainThreadToken) {
|
||||||
node.add_local_signal("create_lines", lines::create_flex);
|
lines::draw_all(token);
|
||||||
node.add_local_signal("create_model", model::create_flex);
|
model::draw_all(token);
|
||||||
node.add_local_signal("create_text", text::create_flex);
|
text::draw_all(token);
|
||||||
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 Some(skytex) = QUEUED_SKYTEX.lock().take() {
|
||||||
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
|
if let Ok(skytex) = SHCubemap::from_cubemap_equirectangular(skytex, true, 100) {
|
||||||
sk.render_set_skytex(&skytex);
|
Renderer::skytex(skytex.tex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
|
||||||
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
|
if let Ok(skylight) = SHCubemap::from_cubemap_equirectangular(skylight, true, 100) {
|
||||||
sk.render_set_skylight(skylight);
|
Renderer::skylight(skylight.sh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,21 +42,80 @@ pub fn draw(sk: &impl StereoKitDraw) {
|
|||||||
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
|
|
||||||
fn set_sky_file_flex(_node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
stardust_xr_server_codegen::codegen_drawable_protocol!();
|
||||||
#[derive(Deserialize)]
|
create_interface!(DrawableInterface);
|
||||||
struct SkyFileInfo {
|
|
||||||
path: PathBuf,
|
pub struct DrawableInterface;
|
||||||
skytex: Option<bool>,
|
impl InterfaceAspect for DrawableInterface {
|
||||||
skylight: Option<bool>,
|
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
|
||||||
}
|
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
|
||||||
let info: SkyFileInfo = deserialize(data)?;
|
.ok_or(eyre::eyre!("Could not find resource"))?;
|
||||||
info.path.metadata()?;
|
QUEUED_SKYTEX.lock().replace(resource_path);
|
||||||
if info.skytex.unwrap_or_default() {
|
Ok(())
|
||||||
QUEUED_SKYTEX.lock().replace(info.path.clone());
|
|
||||||
}
|
|
||||||
if info.skylight.unwrap_or_default() {
|
|
||||||
QUEUED_SKYLIGHT.lock().replace(info.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
fn set_sky_light(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
light: ResourceID,
|
||||||
|
) -> Result<()> {
|
||||||
|
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
|
||||||
|
.ok_or(eyre::eyre!("Could not find resource"))?;
|
||||||
|
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,114 +1,124 @@
|
|||||||
use super::Node;
|
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::resource::ResourceID;
|
use crate::core::resource::get_resource_file;
|
||||||
use crate::nodes::drawable::Drawable;
|
use crate::nodes::alias::{Alias, AliasList};
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
use crate::nodes::spatial::Spatial;
|
||||||
use crate::SK_MULTITHREAD;
|
use crate::nodes::{Aspect, Node};
|
||||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
use color_eyre::eyre::{bail, eyre, Result};
|
||||||
use glam::Mat4;
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
|
use once_cell::sync::{Lazy, OnceCell};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use portable_atomic::{AtomicBool, Ordering};
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use send_wrapper::SendWrapper;
|
use stardust_xr::values::ResourceID;
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::PathBuf;
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use stereokit::named_colors::WHITE;
|
use stereokit_rust::material::Transparency;
|
||||||
use stereokit::{
|
use stereokit_rust::maths::Bounds;
|
||||||
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
|
use stereokit_rust::sk::MainThreadToken;
|
||||||
StereoKitMultiThread,
|
use stereokit_rust::{material::Material, model::Model as SKModel, tex::Tex, util::Color128};
|
||||||
};
|
|
||||||
|
|
||||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
pub struct MaterialWrapper(pub Material);
|
||||||
|
impl Hash for MaterialWrapper {
|
||||||
#[derive(Deserialize, Debug)]
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
#[serde(tag = "t", content = "c")]
|
self.0.get_shader().0.as_ptr().hash(state);
|
||||||
pub enum MaterialParameter {
|
for param in self.0.get_all_param_info() {
|
||||||
Float(f32),
|
param.to_string().hash(state)
|
||||||
Vector2(Vector2<f32>),
|
}
|
||||||
Vector3(Vector3<f32>),
|
self.0.get_chain().map(MaterialWrapper).hash(state)
|
||||||
Vector4(Vector4<f32>),
|
}
|
||||||
Color([f32; 4]),
|
|
||||||
Int(i32),
|
|
||||||
Int2(Vector2<i32>),
|
|
||||||
Int3(Vector3<i32>),
|
|
||||||
Int4(Vector4<i32>),
|
|
||||||
Bool(bool),
|
|
||||||
UInt(u32),
|
|
||||||
UInt2(Vector2<u32>),
|
|
||||||
UInt3(Vector3<u32>),
|
|
||||||
UInt4(Vector4<u32>),
|
|
||||||
Matrix(ColumnMatrix4<f32>),
|
|
||||||
Texture(ResourceID),
|
|
||||||
}
|
}
|
||||||
|
impl 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, String>>);
|
||||||
|
impl MaterialRegistry {
|
||||||
|
fn add_or_get(&self, material: Arc<MaterialWrapper>) -> Arc<MaterialWrapper> {
|
||||||
|
let mut lock = self.0.lock();
|
||||||
|
let hash = {
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
material.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(id) = lock.get(&hash) {
|
||||||
|
if let Ok(existing) = Material::find(id) {
|
||||||
|
return Arc::new(MaterialWrapper(existing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.insert(hash, material.0.get_id().to_string());
|
||||||
|
material
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MATERIAL_REGISTRY: Lazy<MaterialRegistry> = Lazy::new(MaterialRegistry::default);
|
||||||
|
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||||
|
static HOLDOUT_MATERIAL: OnceCell<Arc<MaterialWrapper>> = OnceCell::new();
|
||||||
|
|
||||||
impl MaterialParameter {
|
impl MaterialParameter {
|
||||||
fn apply_to_material(
|
fn apply_to_material(&self, client: &Client, material: &Material, parameter_name: &str) {
|
||||||
&self,
|
let mut params = material.get_all_param_info();
|
||||||
client: &Client,
|
|
||||||
sk: &impl StereoKitMultiThread,
|
|
||||||
material: &Material,
|
|
||||||
parameter_name: &str,
|
|
||||||
) {
|
|
||||||
match self {
|
match self {
|
||||||
MaterialParameter::Float(val) => {
|
MaterialParameter::Bool(val) => {
|
||||||
sk.material_set_float(material, parameter_name, *val);
|
params.set_bool(parameter_name, *val);
|
||||||
}
|
|
||||||
MaterialParameter::Vector2(val) => {
|
|
||||||
sk.material_set_vector2(material, parameter_name, *val);
|
|
||||||
}
|
|
||||||
MaterialParameter::Vector3(val) => {
|
|
||||||
sk.material_set_vector3(material, parameter_name, *val);
|
|
||||||
}
|
|
||||||
MaterialParameter::Vector4(val) => {
|
|
||||||
sk.material_set_vector4(material, parameter_name, *val);
|
|
||||||
}
|
|
||||||
MaterialParameter::Color(val) => {
|
|
||||||
sk.material_set_color(material, parameter_name, Color128::from(val.clone()));
|
|
||||||
}
|
}
|
||||||
MaterialParameter::Int(val) => {
|
MaterialParameter::Int(val) => {
|
||||||
sk.material_set_int(material, parameter_name, *val);
|
params.set_int(parameter_name, &[*val]);
|
||||||
}
|
|
||||||
MaterialParameter::Int2(val) => {
|
|
||||||
sk.material_set_int2(material, parameter_name, val.x, val.y);
|
|
||||||
}
|
|
||||||
MaterialParameter::Int3(val) => {
|
|
||||||
sk.material_set_int3(material, parameter_name, val.x, val.y, val.z);
|
|
||||||
}
|
|
||||||
MaterialParameter::Int4(val) => {
|
|
||||||
sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z);
|
|
||||||
}
|
|
||||||
MaterialParameter::Bool(val) => {
|
|
||||||
sk.material_set_bool(material, parameter_name, *val);
|
|
||||||
}
|
}
|
||||||
MaterialParameter::UInt(val) => {
|
MaterialParameter::UInt(val) => {
|
||||||
sk.material_set_uint(material, parameter_name, *val);
|
params.set_uint(parameter_name, &[*val]);
|
||||||
}
|
}
|
||||||
MaterialParameter::UInt2(val) => {
|
MaterialParameter::Float(val) => {
|
||||||
sk.material_set_uint2(material, parameter_name, val.x, val.y);
|
params.set_float(parameter_name, *val);
|
||||||
}
|
}
|
||||||
MaterialParameter::UInt3(val) => {
|
MaterialParameter::Vec2(val) => {
|
||||||
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z);
|
params.set_vec2(parameter_name, Vec2::from(*val));
|
||||||
}
|
}
|
||||||
MaterialParameter::UInt4(val) => {
|
MaterialParameter::Vec3(val) => {
|
||||||
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z);
|
params.set_vec3(parameter_name, Vec3::from(*val));
|
||||||
}
|
}
|
||||||
MaterialParameter::Matrix(val) => {
|
MaterialParameter::Color(val) => {
|
||||||
sk.material_set_matrix(material, parameter_name, Mat4::from(*val));
|
params.set_color(
|
||||||
|
parameter_name,
|
||||||
|
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
MaterialParameter::Texture(resource) => {
|
MaterialParameter::Texture(resource) => {
|
||||||
let Some(texture_path) = resource.get_file(
|
let Some(texture_path) =
|
||||||
&client.base_resource_prefixes.lock().clone(),
|
get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")])
|
||||||
&[OsStr::new("png"), OsStr::new("jpg")],
|
else {
|
||||||
) else {return};
|
return;
|
||||||
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
|
};
|
||||||
sk.material_set_texture(material, parameter_name, &tex);
|
if let Ok(tex) = Tex::from_file(texture_path, true, None) {
|
||||||
|
params.set_texture(parameter_name, &tex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,245 +127,275 @@ impl MaterialParameter {
|
|||||||
|
|
||||||
pub struct ModelPart {
|
pub struct ModelPart {
|
||||||
id: i32,
|
id: i32,
|
||||||
path: PathBuf,
|
path: String,
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
model: Weak<Model>,
|
model: Weak<Model>,
|
||||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
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 {
|
impl ModelPart {
|
||||||
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
|
fn create_for_model(model: &Arc<Model>, sk_model: &SKModel) {
|
||||||
let first_root_part = sk.model_node_get_root(sk_model);
|
HOLDOUT_MATERIAL.get_or_init(|| {
|
||||||
let mut current_option_part = Some(first_root_part);
|
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 {
|
let nodes = sk_model.get_nodes();
|
||||||
ModelPart::create(sk, model, sk_model, *current_part);
|
for part in nodes.all() {
|
||||||
|
ModelPart::create(model, &part);
|
||||||
if let Some(child) = sk.model_node_child(sk_model, *current_part) {
|
|
||||||
*current_part = child;
|
|
||||||
} else if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
|
|
||||||
*current_part = sibling;
|
|
||||||
} else {
|
|
||||||
while let Some(current_part) = &mut current_option_part {
|
|
||||||
if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
|
|
||||||
*current_part = sibling;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current_option_part = sk.model_node_parent(sk_model, *current_part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(
|
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
|
||||||
sk: &impl StereoKitMultiThread,
|
let mut parts = model.parts.lock();
|
||||||
model: &Arc<Model>,
|
let parent_part = part
|
||||||
sk_model: &SKModel,
|
.get_parent()
|
||||||
id: i32,
|
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
|
||||||
) -> Option<Arc<Self>> {
|
|
||||||
let parent_node = sk
|
|
||||||
.model_node_parent(sk_model, id)
|
|
||||||
.and_then(|id| model.parts.get(&id));
|
|
||||||
let parent_part = parent_node
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|node| match node.drawable.get() {
|
|
||||||
Some(Drawable::ModelPart(model_part)) => Some(model_part),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let stardust_model_part = model.space.node()?;
|
let stardust_model_part = model.space.node()?;
|
||||||
let client = stardust_model_part.get_client()?;
|
let client = stardust_model_part.get_client()?;
|
||||||
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default();
|
let mut part_path = parent_part
|
||||||
part_path.push(sk.model_node_get_name(sk_model, id)?);
|
.map(|n| n.path.clone() + "/")
|
||||||
let node = client.scenegraph.add_node(Node::create(
|
.unwrap_or_default();
|
||||||
&client,
|
part_path += part.get_name().unwrap();
|
||||||
stardust_model_part.get_path(),
|
|
||||||
part_path.to_str()?,
|
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||||
false,
|
let spatial_parent = parent_part
|
||||||
));
|
.map(|n| n.space.clone())
|
||||||
let spatial_parent = parent_node
|
|
||||||
.and_then(|n| n.spatial.get().cloned())
|
|
||||||
.unwrap_or_else(|| model.space.clone());
|
.unwrap_or_else(|| model.space.clone());
|
||||||
|
|
||||||
|
let local_transform = unsafe { part.get_local_transform().m };
|
||||||
let space = Spatial::add_to(
|
let space = Spatial::add_to(
|
||||||
&node,
|
&node,
|
||||||
Some(spatial_parent),
|
Some(spatial_parent),
|
||||||
sk.model_node_get_transform_local(sk_model, id),
|
Mat4::from_cols_array(&local_transform),
|
||||||
false,
|
false,
|
||||||
)
|
);
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
|
let _ = space.bounding_box_calc.set(|node| {
|
||||||
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {return Bounds::default()};
|
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
|
||||||
let Some(sk) = SK_MULTITHREAD.get() else {return Bounds::default()};
|
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(model) = model_part.model.upgrade() else {
|
||||||
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {return Bounds::default()};
|
return Bounds::default();
|
||||||
sk.mesh_get_bounds(sk_mesh)
|
};
|
||||||
|
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 {
|
let model_part = Arc::new(ModelPart {
|
||||||
id,
|
id: *part.get_id(),
|
||||||
path: part_path,
|
path: part_path,
|
||||||
space,
|
space,
|
||||||
model: Arc::downgrade(model),
|
model: Arc::downgrade(model),
|
||||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||||
pending_material_replacement: Mutex::new(None),
|
pending_material_replacement: Mutex::new(None),
|
||||||
|
aliases: AliasList::default(),
|
||||||
});
|
});
|
||||||
node.add_local_signal(
|
<ModelPart as ModelPartAspect>::add_node_members(&node);
|
||||||
"set_material_parameter",
|
node.add_aspect_raw(model_part.clone());
|
||||||
ModelPart::set_material_parameter_flex,
|
parts.push(model_part.clone());
|
||||||
);
|
|
||||||
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
|
|
||||||
model.parts.add(id, &node);
|
|
||||||
Some(model_part)
|
Some(model_part)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_material_parameter_flex(
|
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
|
||||||
node: &Node,
|
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
|
||||||
_calling_client: Arc<Client>,
|
self.pending_material_replacement
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
|
|
||||||
|
|
||||||
let (name, value): (String, MaterialParameter) = deserialize(data)?;
|
|
||||||
|
|
||||||
model_part
|
|
||||||
.pending_material_parameters
|
|
||||||
.lock()
|
.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())));
|
||||||
|
part.material(&shared_material.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
part.material(&material_replacement.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
'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)));
|
||||||
|
part.material(&shared_material.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Aspect for ModelPart {
|
||||||
|
const NAME: &'static str = "ModelPart";
|
||||||
|
}
|
||||||
|
impl ModelPartAspect for ModelPart {
|
||||||
|
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
|
||||||
|
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
let model_part = node.get_aspect::<ModelPart>()?;
|
||||||
|
model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
|
#[doc = "Set the material parameter with `parameter_name` to `value`"]
|
||||||
self.pending_material_replacement
|
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()
|
.lock()
|
||||||
.replace(replacement);
|
.insert(parameter_name, value);
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&self, sk: &impl StereoKitDraw) {
|
Ok(())
|
||||||
let Some(model) = self.model.upgrade() else {return};
|
|
||||||
let Some(sk_model) = model.sk_model.get() else {return};
|
|
||||||
let Some(node) = model.space.node() else {return};
|
|
||||||
let Some(client) = node.get_client() else {return};
|
|
||||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
|
||||||
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut material_parameters = self.pending_material_parameters.lock();
|
|
||||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
|
||||||
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {continue};
|
|
||||||
let new_material = sk.material_copy(material);
|
|
||||||
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
|
|
||||||
sk.model_node_set_material(sk_model, self.id, &new_material);
|
|
||||||
}
|
|
||||||
|
|
||||||
sk.model_node_set_transform_model(
|
|
||||||
sk_model,
|
|
||||||
self.id,
|
|
||||||
Spatial::space_to_space_matrix(Some(&self.space), Some(&model.space)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
self_ref: Weak<Model>,
|
|
||||||
enabled: Arc<AtomicBool>,
|
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
_resource_id: ResourceID,
|
_resource_id: ResourceID,
|
||||||
sk_model: OnceCell<SKModel>,
|
sk_model: OnceCell<SKModel>,
|
||||||
parts: LifeLinkedNodeMap<i32>,
|
parts: Mutex<Vec<Arc<ModelPart>>>,
|
||||||
}
|
}
|
||||||
unsafe impl Send for Model {}
|
|
||||||
unsafe impl Sync for Model {}
|
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
|
||||||
ensure!(
|
let pending_model_path = get_resource_file(
|
||||||
node.spatial.get().is_some(),
|
&resource_id,
|
||||||
"Internal: Node does not have a spatial attached!"
|
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
|
||||||
);
|
&[OsStr::new("glb"), OsStr::new("gltf")],
|
||||||
ensure!(
|
)
|
||||||
node.drawable.get().is_none(),
|
.ok_or_else(|| eyre!("Resource not found"))?;
|
||||||
"Internal: Node already has a drawable attached!"
|
|
||||||
);
|
|
||||||
|
|
||||||
let pending_model_path = resource_id
|
let model = Arc::new(Model {
|
||||||
.get_file(
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
&node
|
|
||||||
.get_client()
|
|
||||||
.ok_or_else(|| eyre!("Client not found"))?
|
|
||||||
.base_resource_prefixes
|
|
||||||
.lock()
|
|
||||||
.clone(),
|
|
||||||
&[OsStr::new("glb"), OsStr::new("gltf")],
|
|
||||||
)
|
|
||||||
.ok_or_else(|| eyre!("Resource not found"))?;
|
|
||||||
|
|
||||||
let model = Arc::new_cyclic(|self_ref| Model {
|
|
||||||
self_ref: self_ref.clone(),
|
|
||||||
enabled: node.enabled.clone(),
|
|
||||||
space: node.spatial.get().unwrap().clone(),
|
|
||||||
_resource_id: resource_id,
|
_resource_id: resource_id,
|
||||||
sk_model: OnceCell::new(),
|
sk_model: OnceCell::new(),
|
||||||
parts: LifeLinkedNodeMap::default(),
|
parts: Mutex::new(Vec::default()),
|
||||||
});
|
});
|
||||||
|
<Model as ModelAspect>::add_node_members(node);
|
||||||
MODEL_REGISTRY.add_raw(&model);
|
MODEL_REGISTRY.add_raw(&model);
|
||||||
|
|
||||||
let sk = SK_MULTITHREAD.get().unwrap();
|
// 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 = sk.model_copy(
|
let sk_model = SKModel::copy(SKModel::from_file(
|
||||||
sk.model_create_file(pending_model_path.to_str().unwrap(), None::<Shader>)?,
|
pending_model_path.to_str().unwrap(),
|
||||||
);
|
None,
|
||||||
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
|
)?);
|
||||||
|
ModelPart::create_for_model(&model, &sk_model);
|
||||||
let _ = model.sk_model.set(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)
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, sk: &impl StereoKitDraw) {
|
fn draw(&self, token: &MainThreadToken) {
|
||||||
let Some(sk_model) = self.sk_model.get() else {return};
|
let Some(sk_model) = self.sk_model.get() else {
|
||||||
for model_node_node in self.parts.nodes() {
|
return;
|
||||||
let Some(Drawable::ModelPart(model_node)) = model_node_node.drawable.get() else {continue};
|
};
|
||||||
model_node.update(sk);
|
let parts = self.parts.lock();
|
||||||
|
for model_node in &*parts {
|
||||||
|
model_node.update();
|
||||||
}
|
}
|
||||||
|
drop(parts);
|
||||||
|
|
||||||
sk.model_draw(
|
if let Some(node) = self.space.node() {
|
||||||
sk_model,
|
if node.enabled() {
|
||||||
self.space.global_transform(),
|
sk_model.draw(token, self.space.global_transform(), None, None);
|
||||||
WHITE,
|
}
|
||||||
RenderLayer::LAYER0,
|
}
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
|
||||||
|
unsafe impl Send for Model {}
|
||||||
|
unsafe impl Sync for Model {}
|
||||||
|
impl Aspect for Model {
|
||||||
|
const NAME: &'static str = "Model";
|
||||||
|
}
|
||||||
|
impl ModelAspect for Model {
|
||||||
|
#[doc = "Bind a model part to the node with the ID input."]
|
||||||
|
fn bind_model_part(
|
||||||
|
node: Arc<Node>,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
id: u64,
|
||||||
|
part_path: String,
|
||||||
|
) -> color_eyre::eyre::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 {
|
impl Drop for Model {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
MODEL_REGISTRY.remove(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() {
|
for model in MODEL_REGISTRY.get_valid_contents() {
|
||||||
if model.enabled.load(Ordering::Relaxed) {
|
model.draw(token);
|
||||||
model.draw(sk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateModelInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
resource: ResourceID,
|
|
||||||
}
|
|
||||||
let info: CreateModelInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/drawable/model", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
Model::add_to(&node, info.resource)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
137
src/nodes/drawable/shader_manipulation.rs
Normal file
137
src/nodes/drawable/shader_manipulation.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use smithay::backend::renderer::gles::{
|
||||||
|
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
||||||
|
GlesError,
|
||||||
|
};
|
||||||
|
use stereokit_rust::shader::{Shader, _ShaderT};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
struct FfiAssetHeader {
|
||||||
|
asset_type: i32,
|
||||||
|
asset_state: i32,
|
||||||
|
id: u64,
|
||||||
|
index: u64,
|
||||||
|
refs: i32,
|
||||||
|
debug: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FfiSkgShader {
|
||||||
|
meta: *mut u8,
|
||||||
|
vertex: u32,
|
||||||
|
pixel: u32,
|
||||||
|
program: u32,
|
||||||
|
compute: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FfiShader {
|
||||||
|
header: FfiAssetHeader,
|
||||||
|
shader: FfiSkgShader,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn compile_shader(
|
||||||
|
gl: &ffi::Gles2,
|
||||||
|
variant: ffi::types::GLuint,
|
||||||
|
src: &str,
|
||||||
|
) -> Result<ffi::types::GLuint, GlesError> {
|
||||||
|
let shader = gl.CreateShader(variant);
|
||||||
|
if shader == 0 {
|
||||||
|
return Err(GlesError::CreateShaderObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.ShaderSource(
|
||||||
|
shader,
|
||||||
|
1,
|
||||||
|
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
||||||
|
&(src.len() as i32) as *const _,
|
||||||
|
);
|
||||||
|
gl.CompileShader(shader);
|
||||||
|
|
||||||
|
let mut status = ffi::FALSE as i32;
|
||||||
|
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
||||||
|
if status == ffi::FALSE as i32 {
|
||||||
|
let mut max_len = 0;
|
||||||
|
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||||
|
|
||||||
|
let mut error = Vec::with_capacity(max_len as usize);
|
||||||
|
let mut len = 0;
|
||||||
|
gl.GetShaderInfoLog(
|
||||||
|
shader,
|
||||||
|
max_len as _,
|
||||||
|
&mut len as *mut _,
|
||||||
|
error.as_mut_ptr() as *mut _,
|
||||||
|
);
|
||||||
|
error.set_len(len as usize);
|
||||||
|
|
||||||
|
error!(
|
||||||
|
"[GL] {}",
|
||||||
|
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.DeleteShader(shader);
|
||||||
|
return Err(GlesError::ShaderCompileError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn link_program(
|
||||||
|
gl: &ffi::Gles2,
|
||||||
|
vert: ffi::types::GLuint,
|
||||||
|
frag: ffi::types::GLuint,
|
||||||
|
) -> Result<ffi::types::GLuint, GlesError> {
|
||||||
|
let program = gl.CreateProgram();
|
||||||
|
gl.AttachShader(program, vert);
|
||||||
|
gl.AttachShader(program, frag);
|
||||||
|
gl.LinkProgram(program);
|
||||||
|
// gl.DetachShader(program, vert);
|
||||||
|
// gl.DetachShader(program, frag);
|
||||||
|
// gl.DeleteShader(vert);
|
||||||
|
// gl.DeleteShader(frag);
|
||||||
|
|
||||||
|
let mut status = ffi::FALSE as i32;
|
||||||
|
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
||||||
|
if status == ffi::FALSE as i32 {
|
||||||
|
let mut max_len = 0;
|
||||||
|
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||||
|
|
||||||
|
let mut error = Vec::with_capacity(max_len as usize);
|
||||||
|
let mut len = 0;
|
||||||
|
gl.GetProgramInfoLog(
|
||||||
|
program,
|
||||||
|
max_len as _,
|
||||||
|
&mut len as *mut _,
|
||||||
|
error.as_mut_ptr() as *mut _,
|
||||||
|
);
|
||||||
|
error.set_len(len as usize);
|
||||||
|
|
||||||
|
error!(
|
||||||
|
"[GL] {}",
|
||||||
|
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.DeleteProgram(program);
|
||||||
|
return Err(GlesError::ProgramLinkError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn shader_inject(
|
||||||
|
c: &Gles2,
|
||||||
|
sk_shader: &mut Shader,
|
||||||
|
vert_str: &str,
|
||||||
|
frag_str: &str,
|
||||||
|
) -> Result<(), GlesError> {
|
||||||
|
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
||||||
|
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
||||||
|
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
||||||
|
|
||||||
|
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
|
||||||
|
if let Some(shader) = shader.as_mut() {
|
||||||
|
shader.shader.vertex = gl_vert;
|
||||||
|
shader.shader.pixel = gl_frag;
|
||||||
|
shader.shader.program = gl_prog;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -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::{
|
// Simula shader with fancy lanzcos sampling
|
||||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");
|
||||||
GlesError,
|
|
||||||
};
|
|
||||||
use std::mem::transmute;
|
|
||||||
use stereokit::Shader;
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
struct FfiAssetHeader {
|
|
||||||
asset_type: i32,
|
|
||||||
asset_state: i32,
|
|
||||||
id: u64,
|
|
||||||
index: u64,
|
|
||||||
refs: i32,
|
|
||||||
debug: *mut u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FfiSkgShader {
|
|
||||||
meta: *mut u8,
|
|
||||||
vertex: u32,
|
|
||||||
pixel: u32,
|
|
||||||
program: u32,
|
|
||||||
compute: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FfiShader {
|
|
||||||
header: FfiAssetHeader,
|
|
||||||
shader: FfiSkgShader,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn compile_shader(
|
|
||||||
gl: &ffi::Gles2,
|
|
||||||
variant: ffi::types::GLuint,
|
|
||||||
src: &str,
|
|
||||||
) -> Result<ffi::types::GLuint, GlesError> {
|
|
||||||
let shader = gl.CreateShader(variant);
|
|
||||||
if shader == 0 {
|
|
||||||
return Err(GlesError::CreateShaderObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.ShaderSource(
|
|
||||||
shader,
|
|
||||||
1,
|
|
||||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
|
||||||
&(src.len() as i32) as *const _,
|
|
||||||
);
|
|
||||||
gl.CompileShader(shader);
|
|
||||||
|
|
||||||
let mut status = ffi::FALSE as i32;
|
|
||||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
|
||||||
if status == ffi::FALSE as i32 {
|
|
||||||
let mut max_len = 0;
|
|
||||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
|
||||||
|
|
||||||
let mut error = Vec::with_capacity(max_len as usize);
|
|
||||||
let mut len = 0;
|
|
||||||
gl.GetShaderInfoLog(
|
|
||||||
shader,
|
|
||||||
max_len as _,
|
|
||||||
&mut len as *mut _,
|
|
||||||
error.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
error.set_len(len as usize);
|
|
||||||
|
|
||||||
error!(
|
|
||||||
"[GL] {}",
|
|
||||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.DeleteShader(shader);
|
|
||||||
return Err(GlesError::ShaderCompileError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(shader)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn link_program(
|
|
||||||
gl: &ffi::Gles2,
|
|
||||||
vert: ffi::types::GLuint,
|
|
||||||
frag: ffi::types::GLuint,
|
|
||||||
) -> Result<ffi::types::GLuint, GlesError> {
|
|
||||||
let program = gl.CreateProgram();
|
|
||||||
gl.AttachShader(program, vert);
|
|
||||||
gl.AttachShader(program, frag);
|
|
||||||
gl.LinkProgram(program);
|
|
||||||
// gl.DetachShader(program, vert);
|
|
||||||
// gl.DetachShader(program, frag);
|
|
||||||
// gl.DeleteShader(vert);
|
|
||||||
// gl.DeleteShader(frag);
|
|
||||||
|
|
||||||
let mut status = ffi::FALSE as i32;
|
|
||||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
|
||||||
if status == ffi::FALSE as i32 {
|
|
||||||
let mut max_len = 0;
|
|
||||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
|
||||||
|
|
||||||
let mut error = Vec::with_capacity(max_len as usize);
|
|
||||||
let mut len = 0;
|
|
||||||
gl.GetProgramInfoLog(
|
|
||||||
program,
|
|
||||||
max_len as _,
|
|
||||||
&mut len as *mut _,
|
|
||||||
error.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
error.set_len(len as usize);
|
|
||||||
|
|
||||||
error!(
|
|
||||||
"[GL] {}",
|
|
||||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.DeleteProgram(program);
|
|
||||||
return Err(GlesError::ProgramLinkError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn shader_inject(
|
|
||||||
c: &Gles2,
|
|
||||||
sk_shader: &mut Shader,
|
|
||||||
vert_str: &str,
|
|
||||||
frag_str: &str,
|
|
||||||
) -> Result<(), GlesError> {
|
|
||||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
|
||||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
|
||||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
|
||||||
|
|
||||||
let shader: *mut FfiShader = transmute(sk_shader.0.as_mut());
|
|
||||||
if let Some(shader) = shader.as_mut() {
|
|
||||||
shader.shader.vertex = gl_vert;
|
|
||||||
shader.shader.pixel = gl_frag;
|
|
||||||
shader.shader.program = gl_prog;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,110 +1,78 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
|
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
|
||||||
nodes::{
|
nodes::{spatial::Spatial, Aspect, Node},
|
||||||
drawable::Drawable,
|
|
||||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use glam::{vec3, Mat4, Vec2};
|
use glam::{vec3, Mat4, Vec2};
|
||||||
use mint::Vector2;
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
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 std::{ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle};
|
use stereokit_rust::{
|
||||||
|
font::Font,
|
||||||
|
sk::MainThreadToken,
|
||||||
|
system::{TextAlign, TextFit, TextStyle as SkTextStyle},
|
||||||
|
util::{Color128, Color32},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{TextAspect, TextStyle};
|
||||||
|
|
||||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||||
|
|
||||||
struct TextData {
|
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
|
||||||
text: String,
|
match (x_align, y_align) {
|
||||||
character_height: f32,
|
(super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft,
|
||||||
text_align: TextAlign,
|
(super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft,
|
||||||
bounds: Option<Vec2>,
|
(super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft,
|
||||||
fit: TextFit,
|
(super::XAlign::Center, super::YAlign::Top) => TextAlign::Center,
|
||||||
bounds_align: TextAlign,
|
(super::XAlign::Center, super::YAlign::Center) => TextAlign::Center,
|
||||||
color: Rgba<f32>,
|
(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 {
|
pub struct Text {
|
||||||
enabled: Arc<AtomicBool>,
|
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
font_path: Option<PathBuf>,
|
font_path: Option<PathBuf>,
|
||||||
style: OnceCell<SendWrapper<TextStyle>>,
|
style: OnceCell<SkTextStyle>,
|
||||||
|
|
||||||
data: Mutex<TextData>,
|
text: Mutex<String>,
|
||||||
|
data: Mutex<TextStyle>,
|
||||||
}
|
}
|
||||||
impl Text {
|
impl Text {
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
|
||||||
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!"
|
|
||||||
);
|
|
||||||
|
|
||||||
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
|
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
|
||||||
let text = TEXT_REGISTRY.add(Text {
|
let text = TEXT_REGISTRY.add(Text {
|
||||||
enabled: node.enabled.clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
space: node.spatial.get().unwrap().clone(),
|
font_path: style.font.as_ref().and_then(|res| {
|
||||||
font_path: font_resource_id.and_then(|res| {
|
get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
|
||||||
res.get_file(
|
|
||||||
&client.base_resource_prefixes.lock().clone(),
|
|
||||||
&[OsStr::new("ttf"), OsStr::new("otf")],
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
style: OnceCell::new(),
|
style: OnceCell::new(),
|
||||||
|
|
||||||
data: Mutex::new(TextData {
|
text: Mutex::new(text),
|
||||||
text,
|
data: Mutex::new(style),
|
||||||
character_height,
|
|
||||||
text_align,
|
|
||||||
bounds: bounds.map(|b| b.into()),
|
|
||||||
fit,
|
|
||||||
bounds_align,
|
|
||||||
color,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
node.add_local_signal("set_character_height", Text::set_character_height_flex);
|
<Text as TextAspect>::add_node_members(node);
|
||||||
node.add_local_signal("set_text", Text::set_text_flex);
|
node.add_aspect_raw(text.clone());
|
||||||
let _ = node.drawable.set(Drawable::Text(text.clone()));
|
|
||||||
|
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, sk: &impl StereoKitDraw) {
|
fn draw(&self, token: &MainThreadToken) {
|
||||||
let style = self.style.get_or_try_init(
|
let style =
|
||||||
|| -> Result<SendWrapper<TextStyle>, color_eyre::eyre::Error> {
|
self.style
|
||||||
let font = self
|
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
|
||||||
.font_path
|
let font = self
|
||||||
.as_deref()
|
.font_path
|
||||||
.and_then(|path| sk.font_create(path).ok())
|
.as_deref()
|
||||||
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
|
.and_then(|path| Font::from_file(path).ok())
|
||||||
Ok(SendWrapper::new(unsafe {
|
.unwrap_or_default();
|
||||||
sk.text_make_style(font, 1.0, WHITE)
|
Ok(SkTextStyle::from_font(font, 1.0, Color32::WHITE))
|
||||||
}))
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(style) = style {
|
if let Ok(style) = style {
|
||||||
|
let text = self.text.lock();
|
||||||
let data = self.data.lock();
|
let data = self.data.lock();
|
||||||
let transform = self.space.global_transform()
|
let transform = self.space.global_transform()
|
||||||
* Mat4::from_scale(vec3(
|
* Mat4::from_scale(vec3(
|
||||||
@@ -112,57 +80,75 @@ impl Text {
|
|||||||
data.character_height,
|
data.character_height,
|
||||||
data.character_height,
|
data.character_height,
|
||||||
));
|
));
|
||||||
if let Some(bounds) = data.bounds {
|
if let Some(bounds) = &data.bounds {
|
||||||
sk.text_add_in(
|
stereokit_rust::system::Text::add_in(
|
||||||
&data.text,
|
token,
|
||||||
|
&*text,
|
||||||
transform,
|
transform,
|
||||||
bounds / data.character_height,
|
Vec2::from(bounds.bounds) / data.character_height,
|
||||||
data.fit,
|
match bounds.fit {
|
||||||
**style,
|
super::TextFit::Wrap => TextFit::Wrap,
|
||||||
data.bounds_align,
|
super::TextFit::Clip => TextFit::Clip,
|
||||||
data.text_align,
|
super::TextFit::Squeeze => TextFit::Squeeze,
|
||||||
vec3(0.0, 0.0, 0.0),
|
super::TextFit::Exact => TextFit::Exact,
|
||||||
Color128::from([
|
super::TextFit::Overflow => TextFit::Overflow,
|
||||||
data.color.red(),
|
},
|
||||||
data.color.green(),
|
Some(*style),
|
||||||
data.color.blue(),
|
Some(Color128::new(
|
||||||
data.color.alpha(),
|
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 {
|
} else {
|
||||||
sk.text_add_at(
|
stereokit_rust::system::Text::add_at(
|
||||||
&data.text,
|
token,
|
||||||
|
&*text,
|
||||||
transform,
|
transform,
|
||||||
**style,
|
Some(*style),
|
||||||
data.bounds_align,
|
Some(Color128::new(
|
||||||
data.text_align,
|
data.color.c.r,
|
||||||
vec3(0.0, 0.0, 0.0),
|
data.color.c.g,
|
||||||
Color128::from([
|
data.color.c.b,
|
||||||
data.color.red(),
|
data.color.a,
|
||||||
data.color.green(),
|
)),
|
||||||
data.color.blue(),
|
data.bounds
|
||||||
data.color.alpha(),
|
.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(
|
impl Aspect for Text {
|
||||||
node: &Node,
|
const NAME: &'static str = "Text";
|
||||||
|
}
|
||||||
|
impl TextAspect for Text {
|
||||||
|
fn set_character_height(
|
||||||
|
node: Arc<Node>,
|
||||||
_calling_client: Arc<Client>,
|
_calling_client: Arc<Client>,
|
||||||
data: &[u8],
|
height: f32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
let this_text = node.get_aspect::<Text>()?;
|
||||||
|
this_text.data.lock().character_height = height;
|
||||||
text.data.lock().character_height = deserialize(data)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
|
||||||
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
|
let this_text = node.get_aspect::<Text>()?;
|
||||||
|
*this_text.text.lock() = text;
|
||||||
text.data.lock().text = deserialize(data)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,47 +161,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() {
|
for text in TEXT_REGISTRY.get_valid_contents() {
|
||||||
if text.enabled.load(Ordering::Relaxed) {
|
if let Some(node) = text.space.node() {
|
||||||
text.draw(sk);
|
if node.enabled() {
|
||||||
|
text.draw(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateTextInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
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(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
|
||||||
let 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(())
|
|
||||||
}
|
|
||||||
|
|||||||
287
src/nodes/fields.rs
Normal file
287
src/nodes/fields.rs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
use super::alias::{Alias, AliasInfo};
|
||||||
|
use super::spatial::{
|
||||||
|
Spatial, SPATIAL_REF_GET_LOCAL_BOUNDING_BOX_SERVER_OPCODE,
|
||||||
|
SPATIAL_REF_GET_RELATIVE_BOUNDING_BOX_SERVER_OPCODE, SPATIAL_REF_GET_TRANSFORM_SERVER_OPCODE,
|
||||||
|
};
|
||||||
|
use super::{Aspect, Node};
|
||||||
|
use crate::core::client::Client;
|
||||||
|
use crate::create_interface;
|
||||||
|
use crate::nodes::spatial::Transform;
|
||||||
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
|
use color_eyre::eyre::{OptionExt, Result};
|
||||||
|
use glam::{vec2, vec3, vec3a, Vec3, Vec3A, Vec3Swizzles};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use stardust_xr::values::Vector3;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against
|
||||||
|
|
||||||
|
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
|
||||||
|
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);
|
||||||
|
<Field as FieldRefAspect>::add_node_members(node);
|
||||||
|
<Field as FieldAspect>::add_node_members(node);
|
||||||
|
Ok(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Aspect for Field {
|
||||||
|
const NAME: &'static str = "Field";
|
||||||
|
}
|
||||||
|
impl FieldRefAspect for Field {
|
||||||
|
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 FieldAspect for Field {
|
||||||
|
fn set_shape(node: Arc<Node>, _calling_client: Arc<Client>, shape: Shape) -> Result<()> {
|
||||||
|
let field = node.get_aspect::<Field>()?;
|
||||||
|
*field.shape.lock() = shape;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn export_field(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||||
|
let id = rand::random();
|
||||||
|
EXPORTED_FIELDS.lock().insert(id, node);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FieldTrait for Field {
|
||||||
|
fn spatial_ref(&self) -> &Spatial {
|
||||||
|
&self.spatial
|
||||||
|
}
|
||||||
|
fn local_distance(&self, p: Vec3A) -> f32 {
|
||||||
|
match self.shape.lock().clone() {
|
||||||
|
Shape::Box(size) => {
|
||||||
|
let q = vec3(
|
||||||
|
p.x.abs() - (size.x * 0.5_f32),
|
||||||
|
p.y.abs() - (size.y * 0.5_f32),
|
||||||
|
p.z.abs() - (size.z * 0.5_f32),
|
||||||
|
);
|
||||||
|
let v = vec3a(q.x.max(0_f32), q.y.max(0_f32), q.z.max(0_f32));
|
||||||
|
v.length() + q.x.max(q.y.max(q.z)).min(0_f32)
|
||||||
|
}
|
||||||
|
Shape::Cylinder(CylinderShape { length, radius }) => {
|
||||||
|
let d = vec2(p.xy().length().abs() - radius, p.z.abs() - (length * 0.5));
|
||||||
|
d.x.max(d.y).min(0.0) + d.max(vec2(0.0, 0.0)).length()
|
||||||
|
}
|
||||||
|
Shape::Sphere(radius) => p.length() - radius,
|
||||||
|
Shape::Torus(TorusShape { radius_a, radius_b }) => {
|
||||||
|
let q = vec2(p.xz().length() - radius_a, p.y);
|
||||||
|
q.length() - radius_b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_interface!(FieldInterface);
|
||||||
|
pub struct FieldInterface;
|
||||||
|
impl InterfaceAspect for FieldInterface {
|
||||||
|
async fn import_field_ref(
|
||||||
|
_node: Arc<Node>,
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
uid: u64,
|
||||||
|
) -> Result<Arc<Node>> {
|
||||||
|
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,83 +0,0 @@
|
|||||||
use super::{Field, FieldTrait, Node};
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
|
||||||
use color_eyre::eyre::{ensure, Result};
|
|
||||||
use glam::{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>, data: &[u8]) -> Result<()> {
|
|
||||||
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
|
||||||
box_field.set_size(deserialize(data)?);
|
|
||||||
|
|
||||||
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>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateFieldInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
size: Vector3<f32>,
|
|
||||||
}
|
|
||||||
let info: CreateFieldInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
BoxField::add_to(&node, info.size)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
use super::{Field, FieldTrait, Node};
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
|
||||||
use color_eyre::eyre::{ensure, Result};
|
|
||||||
use glam::{swizzles::*, vec2, Vec3A};
|
|
||||||
use portable_atomic::AtomicF32;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct 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>, data: &[u8]) -> Result<()> {
|
|
||||||
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
|
||||||
let (length, radius) = deserialize(data)?;
|
|
||||||
cylinder_field.set_size(length, radius);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateFieldInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
length: f32,
|
|
||||||
radius: f32,
|
|
||||||
}
|
|
||||||
let info: CreateFieldInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
CylinderField::add_to(&node, info.length, info.radius)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,241 +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::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>, data: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct FieldInfoArgs<'a> {
|
|
||||||
reference_space_path: &'a str,
|
|
||||||
point: Vector3<f32>,
|
|
||||||
}
|
|
||||||
let args: FieldInfoArgs = deserialize(data)?;
|
|
||||||
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)?)
|
|
||||||
}
|
|
||||||
fn field_normal_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct FieldInfoArgs<'a> {
|
|
||||||
reference_space_path: &'a str,
|
|
||||||
point: Vector3<f32>,
|
|
||||||
radius: Option<f32>,
|
|
||||||
}
|
|
||||||
let args: FieldInfoArgs = deserialize(data)?;
|
|
||||||
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))?)
|
|
||||||
}
|
|
||||||
fn field_closest_point_flex(
|
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct FieldInfoArgs<'a> {
|
|
||||||
reference_space_path: &'a str,
|
|
||||||
point: Vector3<f32>,
|
|
||||||
radius: Option<f32>,
|
|
||||||
}
|
|
||||||
let args: FieldInfoArgs = deserialize(data)?;
|
|
||||||
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))?)
|
|
||||||
}
|
|
||||||
fn field_ray_march_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct FieldInfoArgs<'a> {
|
|
||||||
reference_space_path: &'a str,
|
|
||||||
ray_origin: Vector3<f32>,
|
|
||||||
ray_direction: Vector3<f32>,
|
|
||||||
}
|
|
||||||
let args: FieldInfoArgs = deserialize(data)?;
|
|
||||||
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
|
|
||||||
|
|
||||||
let ray_march_result = node.field.get().unwrap().ray_march(Ray {
|
|
||||||
origin: args.ray_origin.into(),
|
|
||||||
direction: args.ray_direction.into(),
|
|
||||||
space: reference_space,
|
|
||||||
});
|
|
||||||
Ok(serialize(ray_march_result)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Field {
|
|
||||||
Box(BoxField),
|
|
||||||
Cylinder(CylinderField),
|
|
||||||
Sphere(SphereField),
|
|
||||||
Torus(TorusField),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Field {
|
|
||||||
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,88 +0,0 @@
|
|||||||
use super::{Field, FieldTrait, Node};
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::nodes::spatial::{find_spatial_parent, Spatial};
|
|
||||||
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>, data: &[u8]) -> Result<()> {
|
|
||||||
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
|
||||||
sphere_field.set_radius(deserialize(data)?);
|
|
||||||
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>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateFieldInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
origin: Option<Vector3<f32>>,
|
|
||||||
radius: f32,
|
|
||||||
}
|
|
||||||
let info: CreateFieldInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = 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,88 +0,0 @@
|
|||||||
use super::{Field, FieldTrait, Node};
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
|
||||||
use color_eyre::eyre::{ensure, Result};
|
|
||||||
use glam::{swizzles::*, vec2, Vec3A};
|
|
||||||
use portable_atomic::AtomicF32;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct TorusField {
|
|
||||||
space: Arc<Spatial>,
|
|
||||||
radius_a: AtomicF32,
|
|
||||||
radius_b: AtomicF32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TorusField {
|
|
||||||
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) -> Result<()> {
|
|
||||||
ensure!(
|
|
||||||
node.spatial.get().is_some(),
|
|
||||||
"Internal: Node does not have a spatial attached!"
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
node.field.get().is_none(),
|
|
||||||
"Internal: Node already has a field attached!"
|
|
||||||
);
|
|
||||||
let torus_field = TorusField {
|
|
||||||
space: node.spatial.get().unwrap().clone(),
|
|
||||||
radius_a: AtomicF32::new(radius_a.abs()),
|
|
||||||
radius_b: AtomicF32::new(radius_b.abs()),
|
|
||||||
};
|
|
||||||
torus_field.add_field_methods(node);
|
|
||||||
node.add_local_signal("set_size", TorusField::set_size_flex);
|
|
||||||
let _ = node.field.set(Arc::new(Field::Torus(torus_field)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_size(&self, radius_a: f32, radius_b: f32) {
|
|
||||||
self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
|
|
||||||
self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
|
|
||||||
let (radius_a, radius_b) = deserialize(data)?;
|
|
||||||
torus_field.set_size(radius_a, radius_b);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FieldTrait for TorusField {
|
|
||||||
fn local_distance(&self, p: Vec3A) -> f32 {
|
|
||||||
let radius_a = self.radius_a.load(Ordering::Relaxed);
|
|
||||||
let radius_b = self.radius_b.load(Ordering::Relaxed);
|
|
||||||
let q = vec2(p.xz().length() - radius_a, p.y);
|
|
||||||
q.length() - radius_b
|
|
||||||
}
|
|
||||||
fn spatial_ref(&self) -> &Spatial {
|
|
||||||
self.space.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_torus_field_flex(
|
|
||||||
_node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateFieldInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
radius_a: f32,
|
|
||||||
radius_b: f32,
|
|
||||||
}
|
|
||||||
let info: CreateFieldInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/field", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
TorusField::add_to(&node, info.radius_a, info.radius_b)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -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 crate::nodes::spatial::Spatial;
|
||||||
use glam::{vec3a, Mat4};
|
use glam::{vec3a, Mat4, Quat};
|
||||||
use stardust_xr::schemas::flat::{Hand as FlatHand, InputDataType, Joint};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{DistanceLink, InputSpecialization};
|
impl Default for Joint {
|
||||||
|
fn default() -> Self {
|
||||||
#[derive(Debug, Default)]
|
Joint {
|
||||||
pub struct Hand {
|
position: [0.0; 3].into(),
|
||||||
pub base: FlatHand,
|
rotation: Quat::IDENTITY.into(),
|
||||||
}
|
radius: 0.0,
|
||||||
impl InputSpecialization for Hand {
|
distance: 0.0,
|
||||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
}
|
||||||
self.true_distance(space, field).abs()
|
|
||||||
}
|
}
|
||||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
}
|
||||||
|
#[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;
|
let mut min_distance = f32::MAX;
|
||||||
|
|
||||||
for tip in [
|
for tip in [
|
||||||
&self.base.thumb.tip.position,
|
&self.thumb.tip.position,
|
||||||
&self.base.index.tip.position,
|
&self.index.tip.position,
|
||||||
&self.base.middle.tip.position,
|
&self.middle.tip.position,
|
||||||
&self.base.ring.tip.position,
|
&self.ring.tip.position,
|
||||||
&self.base.little.tip.position,
|
&self.little.tip.position,
|
||||||
] {
|
] {
|
||||||
min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z)));
|
min_distance = min_distance.min(field.distance(space, vec3a(tip.x, tip.y, tip.z)));
|
||||||
}
|
}
|
||||||
|
|
||||||
min_distance
|
min_distance
|
||||||
}
|
}
|
||||||
fn serialize(
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||||
&self,
|
let local_to_handler_matrix =
|
||||||
_distance_link: &DistanceLink,
|
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
|
||||||
local_to_handler_matrix: Mat4,
|
|
||||||
) -> InputDataType {
|
|
||||||
let mut hand = self.base;
|
|
||||||
let mut joints: Vec<&mut Joint> = Vec::new();
|
let mut joints: Vec<&mut Joint> = Vec::new();
|
||||||
|
|
||||||
joints.extend([&mut hand.palm, &mut hand.wrist]);
|
joints.extend([&mut self.palm, &mut self.wrist]);
|
||||||
if let Some(elbow) = &mut hand.elbow {
|
if let Some(elbow) = &mut self.elbow {
|
||||||
joints.push(elbow);
|
joints.push(elbow);
|
||||||
}
|
}
|
||||||
for finger in [
|
for finger in [
|
||||||
&mut hand.index,
|
&mut self.index,
|
||||||
&mut hand.middle,
|
&mut self.middle,
|
||||||
&mut hand.ring,
|
&mut self.ring,
|
||||||
&mut hand.little,
|
&mut self.little,
|
||||||
] {
|
] {
|
||||||
joints.extend([
|
joints.extend([
|
||||||
&mut finger.tip,
|
&mut finger.tip,
|
||||||
@@ -56,10 +94,10 @@ impl InputSpecialization for Hand {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
joints.extend([
|
joints.extend([
|
||||||
&mut hand.thumb.tip,
|
&mut self.thumb.tip,
|
||||||
&mut hand.thumb.distal,
|
&mut self.thumb.distal,
|
||||||
&mut hand.thumb.proximal,
|
&mut self.thumb.proximal,
|
||||||
&mut hand.thumb.metacarpal,
|
&mut self.thumb.metacarpal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for joint in joints {
|
for joint in joints {
|
||||||
@@ -68,8 +106,7 @@ impl InputSpecialization for Hand {
|
|||||||
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
|
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
|
||||||
joint.position = position.into();
|
joint.position = position.into();
|
||||||
joint.rotation = rotation.into();
|
joint.rotation = rotation.into();
|
||||||
|
joint.distance = handler.field.distance(&handler.spatial, position.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDataType::Hand(Box::new(hand))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/nodes/input/handler.rs
Normal file
42
src/nodes/input/handler.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use super::{InputHandlerAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REGISTRY};
|
||||||
|
use crate::nodes::{alias::AliasList, fields::Field, spatial::Spatial, Aspect, Node};
|
||||||
|
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 Aspect for InputHandler {
|
||||||
|
const NAME: &'static str = "InputHandler";
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
233
src/nodes/input/method.rs
Normal file
233
src/nodes/input/method.rs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
use super::{
|
||||||
|
input_method_client, InputData, InputDataTrait, InputDataType, InputHandler, InputMethodAspect,
|
||||||
|
InputMethodRefAspect, INPUT_HANDLER_REGISTRY, INPUT_METHOD_REF_ASPECT_ALIAS_INFO,
|
||||||
|
INPUT_METHOD_REGISTRY,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
core::{client::Client, registry::Registry},
|
||||||
|
nodes::{
|
||||||
|
alias::{Alias, AliasList},
|
||||||
|
fields::{Field, FIELD_ALIAS_INFO},
|
||||||
|
spatial::Spatial,
|
||||||
|
Aspect, Node,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
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 internal_capture_requests: 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()),
|
||||||
|
internal_capture_requests: Registry::new(),
|
||||||
|
captures: Registry::new(),
|
||||||
|
};
|
||||||
|
<InputMethod as InputMethodRefAspect>::add_node_members(node);
|
||||||
|
<InputMethod as InputMethodAspect>::add_node_members(node);
|
||||||
|
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
|
||||||
|
method.handle_new_handler(&handler);
|
||||||
|
}
|
||||||
|
let method = INPUT_METHOD_REGISTRY.add(method);
|
||||||
|
node.add_aspect_raw(method.clone());
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Aspect for InputMethod {
|
||||||
|
const NAME: &'static str = "InputMethod";
|
||||||
|
}
|
||||||
|
impl InputMethodRefAspect for InputMethod {
|
||||||
|
#[doc = "Have the input handler that this method reference came from capture the method for the next frame."]
|
||||||
|
fn request_capture(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
handler: Arc<Node>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let input_method = node.get_aspect::<InputMethod>()?;
|
||||||
|
let input_handler = handler.get_aspect::<InputHandler>()?;
|
||||||
|
|
||||||
|
input_method
|
||||||
|
.internal_capture_requests
|
||||||
|
.add_raw(&input_handler);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,395 +1,152 @@
|
|||||||
pub mod hand;
|
#![allow(clippy::needless_question_mark)]
|
||||||
pub mod pointer;
|
|
||||||
pub mod tip;
|
|
||||||
|
|
||||||
use self::hand::Hand;
|
mod hand;
|
||||||
use self::pointer::Pointer;
|
mod handler;
|
||||||
use self::tip::Tip;
|
mod method;
|
||||||
|
mod pointer;
|
||||||
|
mod tip;
|
||||||
|
|
||||||
use super::{
|
pub use handler::*;
|
||||||
alias::{Alias, AliasInfo},
|
pub use method::*;
|
||||||
fields::{find_field, Field, FIELD_ALIAS_INFO},
|
|
||||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
use super::fields::Field;
|
||||||
Node,
|
use super::spatial::Spatial;
|
||||||
};
|
use crate::create_interface;
|
||||||
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry};
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
use color_eyre::eyre::{ensure, Result};
|
use crate::{core::client::Client, nodes::Node};
|
||||||
use glam::Mat4;
|
use crate::{core::registry::Registry, nodes::spatial::Transform};
|
||||||
use once_cell::sync::OnceCell;
|
use color_eyre::eyre::Result;
|
||||||
use parking_lot::Mutex;
|
use stardust_xr::values::Datamap;
|
||||||
use portable_atomic::AtomicBool;
|
use std::sync::Arc;
|
||||||
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};
|
|
||||||
|
|
||||||
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
|
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 {
|
stardust_xr_server_codegen::codegen_input_protocol!();
|
||||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
|
||||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
pub trait InputDataTrait {
|
||||||
fn serialize(
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler);
|
||||||
&self,
|
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
|
||||||
distance_link: &DistanceLink,
|
|
||||||
local_to_handler_matrix: Mat4,
|
|
||||||
) -> InputDataType;
|
|
||||||
}
|
}
|
||||||
pub enum InputType {
|
impl InputDataTrait for InputDataType {
|
||||||
Pointer(Pointer),
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||||
Hand(Box<Hand>),
|
|
||||||
Tip(Tip),
|
|
||||||
}
|
|
||||||
impl Deref for InputType {
|
|
||||||
type Target = dyn InputSpecialization;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
match self {
|
||||||
InputType::Pointer(p) => p,
|
InputDataType::Pointer(i) => i.transform(method, handler),
|
||||||
InputType::Hand(h) => h.as_ref(),
|
InputDataType::Hand(i) => i.transform(method, handler),
|
||||||
InputType::Tip(t) => t,
|
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 {
|
create_interface!(InputInterface);
|
||||||
node: Weak<Node>,
|
pub struct InputInterface;
|
||||||
uid: String,
|
impl InterfaceAspect for InputInterface {
|
||||||
pub enabled: Mutex<bool>,
|
#[doc = "Create an input method node"]
|
||||||
pub spatial: Arc<Spatial>,
|
fn create_input_method(
|
||||||
pub specialization: Mutex<InputType>,
|
_node: Arc<Node>,
|
||||||
captures: Registry<InputHandler>,
|
calling_client: Arc<Client>,
|
||||||
pub datamap: Mutex<Option<Datamap>>,
|
id: u64,
|
||||||
handler_aliases: LifeLinkedNodeMap<String>,
|
parent: Arc<Node>,
|
||||||
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>, data: &[u8]) -> Result<()> {
|
|
||||||
let method = InputMethod::get(node)?;
|
|
||||||
let handler = InputHandler::find(&calling_client, deserialize(data)?)?;
|
|
||||||
|
|
||||||
method.captures.add_raw(&handler);
|
|
||||||
node.send_remote_signal("capture", data)
|
|
||||||
}
|
|
||||||
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let method = InputMethod::get(node)?;
|
|
||||||
method.datamap.lock().replace(Datamap::new(data.to_vec())?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let method = InputMethod::get(node)?;
|
|
||||||
let handler_paths: Vec<&str> = deserialize(data)?;
|
|
||||||
let handlers: Vec<Weak<InputHandler>> = handler_paths
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
|
|
||||||
.map(|h| Arc::downgrade(&h))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
*method
|
|
||||||
.handler_order
|
|
||||||
.get_or_init(|| Mutex::new(Vec::new()))
|
|
||||||
.lock() = handlers;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn 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>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateInputHandlerInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
field_path: &'a str,
|
initial_data: InputDataType,
|
||||||
}
|
datamap: Datamap,
|
||||||
let info: CreateInputHandlerInfo = deserialize(data)?;
|
) -> Result<()> {
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
let parent = parent.get_aspect::<Spatial>()?;
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
let transform = transform.to_mat4(true, true, true);
|
||||||
let field = find_field(&calling_client, info.field_path)?;
|
|
||||||
|
|
||||||
let node =
|
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||||
Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph()?;
|
Spatial::add_to(&node, Some(parent.clone()), transform, false);
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
InputMethod::add_to(&node, initial_data, datamap)?;
|
||||||
InputHandler::add_to(&node, &field)?;
|
Ok(())
|
||||||
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")]
|
#[tracing::instrument(level = "debug")]
|
||||||
pub fn process_input() {
|
pub fn process_input() {
|
||||||
// Iterate over all valid input methods
|
// Iterate over all valid input methods
|
||||||
let methods = debug_span!("Get valid methods").in_scope(|| {
|
let methods = INPUT_METHOD_REGISTRY
|
||||||
INPUT_METHOD_REGISTRY
|
.get_valid_contents()
|
||||||
.get_valid_contents()
|
.into_iter()
|
||||||
.into_iter()
|
.filter(|method| {
|
||||||
.filter(|method| *method.enabled.lock())
|
let Some(node) = method.spatial.node() else {
|
||||||
.filter(|method| method.datamap.lock().is_some())
|
return false;
|
||||||
});
|
};
|
||||||
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
|
node.enabled()
|
||||||
for handler in &handlers {
|
});
|
||||||
handler.method_aliases.clear();
|
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 {
|
for method in methods {
|
||||||
debug_span!("Process input method").in_scope(|| {
|
method.internal_capture_requests.clear();
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,52 @@
|
|||||||
use super::{DistanceLink, InputSpecialization};
|
use super::{InputDataTrait, InputHandler, InputMethod, Pointer};
|
||||||
use crate::core::client::Client;
|
use crate::nodes::{
|
||||||
use crate::nodes::fields::{Field, Ray, RayMarchResult};
|
fields::{Field, FieldTrait, Ray, RayMarchResult},
|
||||||
use crate::nodes::input::{InputMethod, InputType};
|
spatial::Spatial,
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
};
|
||||||
use crate::nodes::Node;
|
use glam::{vec3, Mat4, Quat};
|
||||||
use glam::{vec3, Mat4};
|
use std::sync::{Arc, Weak};
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flat::{Datamap, InputDataType, Pointer as FlatPointer};
|
|
||||||
use stardust_xr::schemas::flex::deserialize;
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Default for Pointer {
|
||||||
pub struct Pointer;
|
fn default() -> Self {
|
||||||
// impl Default for Pointer {
|
Pointer {
|
||||||
// fn default() -> Self {
|
origin: [0.0; 3].into(),
|
||||||
// Pointer {
|
orientation: Quat::IDENTITY.into(),
|
||||||
// grab: Default::default(),
|
deepest_point: [0.0; 3].into(),
|
||||||
// select: Default::default(),
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
impl Pointer {
|
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 {
|
field.ray_march(Ray {
|
||||||
origin: vec3(0.0, 0.0, 0.0),
|
origin: vec3(0.0, 0.0, 0.0),
|
||||||
direction: vec3(0.0, 0.0, -1.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 InputDataTrait for Pointer {
|
||||||
impl InputSpecialization for Pointer {
|
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
|
||||||
let ray_info = self.ray_march(space, field);
|
|
||||||
ray_info
|
|
||||||
.deepest_point_distance
|
|
||||||
.hypot(ray_info.min_distance.recip())
|
|
||||||
}
|
|
||||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
|
||||||
let ray_info = self.ray_march(space, field);
|
let ray_info = self.ray_march(space, field);
|
||||||
ray_info.min_distance
|
ray_info.min_distance
|
||||||
}
|
}
|
||||||
fn serialize(
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||||
&self,
|
let local_to_handler_matrix =
|
||||||
distance_link: &DistanceLink,
|
Mat4::from_rotation_translation(self.orientation.into(), self.origin.into())
|
||||||
local_to_handler_matrix: Mat4,
|
* Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial));
|
||||||
) -> InputDataType {
|
|
||||||
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
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;
|
let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
|
||||||
|
|
||||||
InputDataType::Pointer(FlatPointer {
|
self.origin = origin.into();
|
||||||
origin: origin.into(),
|
self.orientation = orientation.into();
|
||||||
orientation: orientation.into(),
|
self.deepest_point = deepest_point.into();
|
||||||
deepest_point: deepest_point.into(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_pointer_flex(
|
|
||||||
_node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> color_eyre::eyre::Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreatePointerInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
datamap: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
let info: CreatePointerInfo = deserialize(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
InputMethod::add_to(
|
|
||||||
&node,
|
|
||||||
InputType::Pointer(Pointer),
|
|
||||||
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,73 +1,29 @@
|
|||||||
use super::{DistanceLink, InputSpecialization};
|
use super::{InputDataTrait, InputHandler, InputMethod, Tip};
|
||||||
use crate::core::client::Client;
|
use crate::nodes::{
|
||||||
use crate::nodes::fields::Field;
|
fields::{Field, FieldTrait},
|
||||||
use crate::nodes::input::{InputMethod, InputType};
|
spatial::Spatial,
|
||||||
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
|
};
|
||||||
use crate::nodes::Node;
|
use glam::{Mat4, Quat};
|
||||||
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 std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Default for Tip {
|
||||||
pub struct Tip {
|
fn default() -> Self {
|
||||||
pub radius: f32,
|
Tip {
|
||||||
}
|
origin: [0.0; 3].into(),
|
||||||
impl Tip {
|
orientation: Quat::IDENTITY.into(),
|
||||||
fn set_radius(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
if let InputType::Tip(tip) = &mut *node.input_method.get().unwrap().specialization.lock() {
|
|
||||||
tip.radius = deserialize(data)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InputSpecialization for Tip {
|
impl InputDataTrait for Tip {
|
||||||
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
||||||
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
|
field.distance(space, self.origin.into())
|
||||||
}
|
}
|
||||||
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
|
fn transform(&mut self, method: &InputMethod, handler: &InputHandler) {
|
||||||
field.distance(space, vec3a(0.0, 0.0, 0.0))
|
let local_to_handler_matrix =
|
||||||
}
|
Spatial::space_to_space_matrix(Some(&method.spatial), Some(&handler.spatial))
|
||||||
fn serialize(
|
* Mat4::from_rotation_translation(self.orientation.into(), self.origin.into());
|
||||||
&self,
|
|
||||||
_distance_link: &DistanceLink,
|
|
||||||
local_to_handler_matrix: Mat4,
|
|
||||||
) -> InputDataType {
|
|
||||||
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
|
||||||
InputDataType::Tip(FlatTip {
|
self.origin = origin.into();
|
||||||
origin: origin.into(),
|
self.orientation = orientation.into();
|
||||||
orientation: orientation.into(),
|
|
||||||
radius: self.radius,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> 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(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/input/method/tip", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
|
|
||||||
let 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(())
|
|
||||||
}
|
|
||||||
|
|||||||
231
src/nodes/items/camera.rs
Normal file
231
src/nodes/items/camera.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
use super::{
|
||||||
|
create_item_acceptor_flex, register_item_ui_flex, Item, ItemAcceptor, ItemInterface, ItemType,
|
||||||
|
};
|
||||||
|
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::{
|
||||||
|
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
||||||
|
create_interface,
|
||||||
|
nodes::{
|
||||||
|
drawable::{
|
||||||
|
model::{MaterialWrapper, ModelPart},
|
||||||
|
shaders::UNLIT_SHADER_BYTES,
|
||||||
|
},
|
||||||
|
items::TypeInfo,
|
||||||
|
spatial::{Spatial, Transform},
|
||||||
|
Message, Node,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::{bail, eyre, Result};
|
||||||
|
use glam::Mat4;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use mint::{ColumnMatrix4, Vector2};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use stereokit_rust::{
|
||||||
|
material::{Material, Transparency},
|
||||||
|
shader::Shader,
|
||||||
|
sk::MainThreadToken,
|
||||||
|
system::Renderer,
|
||||||
|
tex::{Tex, TexFormat, TexType},
|
||||||
|
util::Color128,
|
||||||
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
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: OnceCell<TexWrapper>,
|
||||||
|
sk_mat: OnceCell<Arc<MaterialWrapper>>,
|
||||||
|
applied_to: Registry<ModelPart>,
|
||||||
|
apply_to: Registry<ModelPart>,
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
impl CameraItem {
|
||||||
|
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
|
||||||
|
Item::add_to(
|
||||||
|
node,
|
||||||
|
&ITEM_TYPE_INFO_CAMERA,
|
||||||
|
ItemType::Camera(CameraItem {
|
||||||
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
|
frame_info: Mutex::new(FrameInfo {
|
||||||
|
proj_matrix,
|
||||||
|
px_size,
|
||||||
|
}),
|
||||||
|
sk_tex: OnceCell::new(),
|
||||||
|
sk_mat: OnceCell::new(),
|
||||||
|
applied_to: Registry::new(),
|
||||||
|
apply_to: Registry::new(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// <CameraItem as CameraItemAspect>::node_methods(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Err(eyre!("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_try_init(|| -> Result<Arc<MaterialWrapper>> {
|
||||||
|
let shader = Shader::from_memory(UNLIT_SHADER_BYTES)?;
|
||||||
|
let mut mat = Material::new(&shader, None);
|
||||||
|
mat.get_all_param_info().set_texture("diffuse", &sk_tex.0);
|
||||||
|
mat.transparency(Transparency::Blend);
|
||||||
|
Ok(Arc::new(MaterialWrapper(mat)))
|
||||||
|
});
|
||||||
|
let Ok(sk_mat) = sk_mat else {
|
||||||
|
error!("unable to make camera item stereokit texture");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
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 CameraItemAspect for CameraItem {}
|
||||||
|
|
||||||
|
impl CameraItemAcceptorAspect for ItemAcceptor {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_interface!(ItemInterface);
|
||||||
|
impl InterfaceAspect for ItemInterface {
|
||||||
|
#[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<()> {
|
||||||
|
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<()> {
|
||||||
|
create_item_acceptor_flex(
|
||||||
|
calling_client,
|
||||||
|
id,
|
||||||
|
parent,
|
||||||
|
transform,
|
||||||
|
&ITEM_TYPE_INFO_CAMERA,
|
||||||
|
field,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
use super::{Item, ItemSpecialization, ItemType};
|
|
||||||
use crate::{
|
|
||||||
core::{
|
|
||||||
client::{Client, INTERNAL_CLIENT},
|
|
||||||
registry::Registry,
|
|
||||||
},
|
|
||||||
nodes::{
|
|
||||||
items::TypeInfo,
|
|
||||||
spatial::{find_spatial_parent, parse_transform, Spatial},
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use color_eyre::eyre::{eyre, Result};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use nanoid::nanoid;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::{
|
|
||||||
schemas::flex::{deserialize, flexbuffers, serialize},
|
|
||||||
values::Transform,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub(super) static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
|
|
||||||
type_name: "environment",
|
|
||||||
aliased_local_signals: vec!["apply_sky_tex", "apply_sky_light"],
|
|
||||||
aliased_local_methods: vec![],
|
|
||||||
aliased_remote_signals: vec![],
|
|
||||||
ui: Default::default(),
|
|
||||||
items: Registry::new(),
|
|
||||||
acceptors: Registry::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EnvironmentItem {
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
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>, _data: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
|
|
||||||
return Err(eyre!("Wrong item type?"))
|
|
||||||
};
|
|
||||||
Ok(flexbuffers::singleton(environment_item.path.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ItemSpecialization for EnvironmentItem {
|
|
||||||
fn serialize_start_data(&self, id: &str) -> Vec<u8> {
|
|
||||||
serialize((id, self.path.as_str())).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn create_environment_item_flex(
|
|
||||||
_node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateEnvironmentItemInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
item_data: String,
|
|
||||||
}
|
|
||||||
let info: CreateEnvironmentItemInfo = deserialize(data)?;
|
|
||||||
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,45 +1,27 @@
|
|||||||
mod environment;
|
pub mod camera;
|
||||||
|
pub mod panel;
|
||||||
|
|
||||||
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
|
use self::camera::CameraItem;
|
||||||
use super::fields::Field;
|
use self::panel::PanelItemTrait;
|
||||||
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
|
use super::alias::AliasList;
|
||||||
use super::{Alias, Node};
|
use super::fields::{Field, FIELD_ALIAS_INFO};
|
||||||
|
use super::spatial::Spatial;
|
||||||
|
use super::{Alias, Aspect, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::node_collections::LifeLinkedNodeMap;
|
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::nodes::alias::AliasInfo;
|
use crate::nodes::alias::AliasInfo;
|
||||||
use crate::nodes::fields::find_field;
|
use crate::nodes::spatial::Transform;
|
||||||
#[cfg(feature = "wayland")]
|
use crate::nodes::spatial::SPATIAL_ASPECT_ALIAS_INFO;
|
||||||
use crate::wayland::panel_item::{PanelItem, ITEM_TYPE_INFO_PANEL};
|
use color_eyre::eyre::{ensure, Result};
|
||||||
use color_eyre::eyre::{ensure, eyre, Result};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use nanoid::nanoid;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use portable_atomic::Ordering;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
lazy_static! {
|
stardust_xr_server_codegen::codegen_item_protocol!();
|
||||||
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![];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
||||||
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
|
if item.captured_acceptor.lock().strong_count() > 0 {
|
||||||
release(item, Some(&acceptor));
|
release(item);
|
||||||
}
|
}
|
||||||
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
|
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
|
||||||
acceptor.handle_capture(item);
|
acceptor.handle_capture(item);
|
||||||
@@ -47,25 +29,25 @@ pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
|
|||||||
ui.handle_capture_item(item, acceptor);
|
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();
|
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();
|
*captured_acceptor = Weak::default();
|
||||||
acceptor.handle_release(item);
|
acceptor.handle_release(item);
|
||||||
if let Some(ui) = item.type_info.ui.lock().upgrade() {
|
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 struct TypeInfo {
|
||||||
pub type_name: &'static str,
|
pub type_name: &'static str,
|
||||||
pub aliased_local_signals: Vec<&'static str>,
|
pub alias_info: AliasInfo,
|
||||||
pub aliased_local_methods: Vec<&'static str>,
|
pub ui_node_id: u64,
|
||||||
pub aliased_remote_signals: Vec<&'static str>,
|
|
||||||
pub ui: Mutex<Weak<ItemUI>>,
|
pub ui: Mutex<Weak<ItemUI>>,
|
||||||
pub items: Registry<Item>,
|
pub items: Registry<Item>,
|
||||||
pub acceptors: Registry<ItemAcceptor>,
|
pub acceptors: Registry<ItemAcceptor>,
|
||||||
|
pub new_acceptor_fn: fn(node: &Node, acceptor: &Arc<Node>, acceptor_field: &Arc<Node>),
|
||||||
}
|
}
|
||||||
impl Hash for TypeInfo {
|
impl Hash for TypeInfo {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
@@ -80,8 +62,7 @@ impl PartialEq for TypeInfo {
|
|||||||
impl Eq for TypeInfo {}
|
impl Eq for TypeInfo {}
|
||||||
|
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
node: Weak<Node>,
|
spatial: Arc<Spatial>,
|
||||||
uid: String,
|
|
||||||
type_info: &'static TypeInfo,
|
type_info: &'static TypeInfo,
|
||||||
captured_acceptor: Mutex<Weak<ItemAcceptor>>,
|
captured_acceptor: Mutex<Weak<ItemAcceptor>>,
|
||||||
pub specialization: ItemType,
|
pub specialization: ItemType,
|
||||||
@@ -89,115 +70,99 @@ pub struct Item {
|
|||||||
impl Item {
|
impl Item {
|
||||||
pub fn add_to(
|
pub fn add_to(
|
||||||
node: &Arc<Node>,
|
node: &Arc<Node>,
|
||||||
uid: String,
|
|
||||||
type_info: &'static TypeInfo,
|
type_info: &'static TypeInfo,
|
||||||
specialization: ItemType,
|
specialization: ItemType,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let item = Item {
|
let item = Item {
|
||||||
node: Arc::downgrade(node),
|
spatial: node.aspects.get::<Spatial>().unwrap(),
|
||||||
uid,
|
|
||||||
type_info,
|
type_info,
|
||||||
captured_acceptor: Default::default(),
|
captured_acceptor: Default::default(),
|
||||||
specialization,
|
specialization,
|
||||||
};
|
};
|
||||||
let item = type_info.items.add(item);
|
let item = type_info.items.add(item);
|
||||||
|
|
||||||
node.add_local_signal("release", Item::release_flex);
|
<Item as ItemAspect>::add_node_members(node);
|
||||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||||
ui.handle_create_item(&item);
|
ui.handle_create_item(&item);
|
||||||
}
|
}
|
||||||
let _ = node.item.set(item.clone());
|
node.add_aspect_raw(item.clone());
|
||||||
|
|
||||||
if let Some(auto_acceptor) = node.get_client().and_then(|client| {
|
// if let Some(auto_acceptor) = node.get_client().and_then(|client| {
|
||||||
client
|
// client
|
||||||
.startup_settings
|
// .state
|
||||||
.as_ref()
|
// .as_ref()
|
||||||
.and_then(|settings| settings.acceptors.get(type_info))
|
// .and_then(|settings| settings.acceptors.get(type_info))
|
||||||
.and_then(|acceptor| acceptor.upgrade())
|
// .and_then(|acceptor| acceptor.upgrade())
|
||||||
}) {
|
// }) {
|
||||||
capture(&item, &auto_acceptor);
|
// capture(&item, &auto_acceptor);
|
||||||
}
|
// }
|
||||||
|
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
fn make_alias_named(
|
fn make_alias(&self, client: &Arc<Client>, alias_list: &AliasList) -> Result<Arc<Node>> {
|
||||||
&self,
|
|
||||||
client: &Arc<Client>,
|
|
||||||
parent: &str,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Arc<Node>> {
|
|
||||||
Alias::create(
|
Alias::create(
|
||||||
|
&self.spatial.node().unwrap(),
|
||||||
client,
|
client,
|
||||||
parent,
|
self.type_info.alias_info.clone() + ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||||
name,
|
Some(alias_list),
|
||||||
&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(),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> Result<Arc<Node>> {
|
}
|
||||||
self.make_alias_named(client, parent, &self.uid)
|
impl Aspect for Item {
|
||||||
}
|
const NAME: &'static str = "Item";
|
||||||
|
}
|
||||||
fn release_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
impl ItemAspect for Item {
|
||||||
let item = node.get_aspect("Item", "item", |n| &n.item)?;
|
fn release(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
release(item, None);
|
let item = node.get_aspect::<Item>()?;
|
||||||
|
release(&item);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Item {
|
impl Drop for Item {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.type_info.items.remove(self);
|
self.type_info.items.remove(self);
|
||||||
release(self, None);
|
release(self);
|
||||||
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||||
ui.handle_destroy_item(self);
|
ui.handle_destroy_item(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ItemSpecialization {
|
|
||||||
fn serialize_start_data(&self, id: &str) -> Vec<u8>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ItemType {
|
pub enum ItemType {
|
||||||
Environment(EnvironmentItem),
|
Camera(CameraItem),
|
||||||
#[cfg(feature = "wayland")]
|
Panel(Arc<dyn PanelItemTrait>),
|
||||||
Panel(Arc<PanelItem>),
|
|
||||||
}
|
}
|
||||||
impl Deref for ItemType {
|
impl ItemType {
|
||||||
type Target = dyn ItemSpecialization;
|
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
match self {
|
||||||
ItemType::Environment(item) => item,
|
ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||||
#[cfg(feature = "wayland")]
|
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
||||||
ItemType::Panel(item) => item.as_ref(),
|
}
|
||||||
|
}
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// impl Deref for ItemType {
|
||||||
|
// type Target = dyn ItemSpecialization;
|
||||||
|
|
||||||
|
// fn deref(&self) -> &Self::Target {
|
||||||
|
// match self {
|
||||||
|
// ItemType::Environment(item) => item,
|
||||||
|
// ItemType::Panel(item) => item.as_ref(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub struct ItemUI {
|
pub struct ItemUI {
|
||||||
node: Weak<Node>,
|
node: Weak<Node>,
|
||||||
type_info: &'static TypeInfo,
|
type_info: &'static TypeInfo,
|
||||||
item_aliases: LifeLinkedNodeMap<String>,
|
item_aliases: AliasList,
|
||||||
acceptor_aliases: LifeLinkedNodeMap<String>,
|
acceptor_aliases: AliasList,
|
||||||
acceptor_field_aliases: LifeLinkedNodeMap<String>,
|
acceptor_field_aliases: AliasList,
|
||||||
}
|
}
|
||||||
impl ItemUI {
|
impl ItemUI {
|
||||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
|
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
|
||||||
@@ -209,12 +174,12 @@ impl ItemUI {
|
|||||||
let ui = Arc::new(ItemUI {
|
let ui = Arc::new(ItemUI {
|
||||||
node: Arc::downgrade(node),
|
node: Arc::downgrade(node),
|
||||||
type_info,
|
type_info,
|
||||||
item_aliases: Default::default(),
|
item_aliases: AliasList::default(),
|
||||||
acceptor_aliases: Default::default(),
|
acceptor_aliases: AliasList::default(),
|
||||||
acceptor_field_aliases: Default::default(),
|
acceptor_field_aliases: AliasList::default(),
|
||||||
});
|
});
|
||||||
*type_info.ui.lock() = Arc::downgrade(&ui);
|
*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() {
|
for item in type_info.items.get_valid_contents() {
|
||||||
ui.handle_create_item(&item);
|
ui.handle_create_item(&item);
|
||||||
@@ -224,66 +189,104 @@ impl ItemUI {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn send_state(&self, state: &str, name: &str) {
|
|
||||||
let _ = self
|
|
||||||
.node
|
|
||||||
.upgrade()
|
|
||||||
.unwrap()
|
|
||||||
.send_remote_signal(state, flexbuffers::singleton(name).as_slice());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_create_item(&self, item: &Item) {
|
fn handle_create_item(&self, item: &Item) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
let Some(node) = self.node.upgrade() else {
|
||||||
let Some(client) = node.get_client() else { return };
|
return;
|
||||||
|
};
|
||||||
|
let Some(client) = node.get_client() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
|
let Ok(item_alias) = item.make_alias(&client, &self.item_aliases) else {
|
||||||
self.item_aliases.add(item.uid.clone(), &alias_node);
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let _ = node.send_remote_signal(
|
item.specialization.send_ui_item_created(&node, &item_alias);
|
||||||
"create_item",
|
|
||||||
&item.specialization.serialize_start_data(&item.uid),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
fn handle_destroy_item(&self, item: &Item) {
|
|
||||||
self.item_aliases.remove(&item.uid);
|
|
||||||
self.send_state("destroy_item", item.uid.as_str());
|
|
||||||
}
|
}
|
||||||
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
|
||||||
|
return;
|
||||||
let _ = node.send_remote_signal(
|
};
|
||||||
"capture_item",
|
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
|
||||||
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
|
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) {
|
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
let Some(item_alias) = self.item_aliases.get_from_aspect(item) else {
|
||||||
|
return;
|
||||||
let _ = node.send_remote_signal(
|
};
|
||||||
"release_item",
|
let Some(acceptor_alias) = self.acceptor_aliases.get_from_aspect(acceptor) else {
|
||||||
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
|
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) {
|
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
let Some(node) = self.node.upgrade() else {
|
||||||
let Some(client) = node.get_client() else { return };
|
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,
|
&client,
|
||||||
&format!("/item/{}/acceptor", self.type_info.type_name),
|
ITEM_ACCEPTOR_ASPECT_ALIAS_INFO.clone(),
|
||||||
) else {return};
|
Some(&self.acceptor_aliases),
|
||||||
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
|
) else {
|
||||||
self.acceptor_field_aliases
|
return;
|
||||||
.add(acceptor.uid.clone(), &field_alias);
|
};
|
||||||
let _ = node.send_remote_signal("create_acceptor", &serialize(&acceptor.uid).unwrap());
|
|
||||||
|
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) {
|
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
|
||||||
self.send_state("destroy_acceptor", acceptor.uid.as_str());
|
let acceptor_alias = self.acceptor_aliases.get_from_aspect(acceptor).unwrap();
|
||||||
self.acceptor_aliases.remove(&acceptor.uid);
|
let _ = item_ui_client::destroy_acceptor(&self.node.upgrade().unwrap(), acceptor_alias.id);
|
||||||
self.acceptor_field_aliases.remove(&acceptor.uid);
|
|
||||||
|
self.acceptor_aliases
|
||||||
|
.remove_aspect(acceptor.spatial.as_ref());
|
||||||
|
self.acceptor_field_aliases
|
||||||
|
.remove_aspect(acceptor.field.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Aspect for ItemUI {
|
||||||
|
const NAME: &'static str = "Item";
|
||||||
|
}
|
||||||
impl Drop for ItemUI {
|
impl Drop for ItemUI {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
*self.type_info.ui.lock() = Weak::new();
|
*self.type_info.ui.lock() = Weak::new();
|
||||||
@@ -291,93 +294,63 @@ impl Drop for ItemUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ItemAcceptor {
|
pub struct ItemAcceptor {
|
||||||
uid: String,
|
spatial: Arc<Spatial>,
|
||||||
node: Weak<Node>,
|
|
||||||
pub type_info: &'static TypeInfo,
|
pub type_info: &'static TypeInfo,
|
||||||
field: Arc<Field>,
|
field: Arc<Field>,
|
||||||
accepted_aliases: LifeLinkedNodeMap<String>,
|
accepted_aliases: AliasList,
|
||||||
accepted_registry: Registry<Item>,
|
accepted_registry: Registry<Item>,
|
||||||
}
|
}
|
||||||
impl ItemAcceptor {
|
impl ItemAcceptor {
|
||||||
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
|
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
|
||||||
let acceptor = type_info.acceptors.add(ItemAcceptor {
|
let acceptor = type_info.acceptors.add(ItemAcceptor {
|
||||||
uid: nanoid!(),
|
spatial: node.get_aspect::<Spatial>().unwrap(),
|
||||||
node: Arc::downgrade(node),
|
|
||||||
type_info,
|
type_info,
|
||||||
field,
|
field,
|
||||||
accepted_aliases: Default::default(),
|
accepted_aliases: AliasList::default(),
|
||||||
accepted_registry: Registry::new(),
|
accepted_registry: Registry::new(),
|
||||||
});
|
});
|
||||||
node.add_local_signal("capture", ItemAcceptor::capture_flex);
|
|
||||||
if let Some(ui) = type_info.ui.lock().upgrade() {
|
if let Some(ui) = type_info.ui.lock().upgrade() {
|
||||||
ui.handle_create_acceptor(&acceptor);
|
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>, data: &[u8]) -> Result<()> {
|
|
||||||
if !node.enabled.load(Ordering::Relaxed) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let acceptor = node.item_acceptor.get().unwrap();
|
|
||||||
let item_path: &str = deserialize(data)?;
|
|
||||||
let item_node = calling_client.get_node("Item", item_path)?;
|
|
||||||
let item = item_node.get_aspect("Item", "item", |n| &n.item)?;
|
|
||||||
capture(item, acceptor);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Result<(Arc<Node>, Arc<Node>)> {
|
|
||||||
let acceptor_node = &self.node.upgrade().unwrap();
|
|
||||||
let acceptor_alias = Alias::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>) {
|
fn handle_capture(&self, item: &Arc<Item>) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
let Some(node) = self.spatial.node() else {
|
||||||
let Some(client) = node.get_client() else { return };
|
return;
|
||||||
|
};
|
||||||
|
let Some(client) = node.get_client() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
self.accepted_registry.add_raw(item);
|
self.accepted_registry.add_raw(item);
|
||||||
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
|
let Ok(alias_node) = item.make_alias(&client, &self.accepted_aliases) else {
|
||||||
self.accepted_aliases.add(item.uid.clone(), &alias_node);
|
return;
|
||||||
}
|
};
|
||||||
let _ = node.send_remote_signal(
|
|
||||||
"capture",
|
item.specialization
|
||||||
&item.specialization.serialize_start_data(&item.uid),
|
.send_acceptor_item_created(&node, &alias_node);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
fn handle_release(&self, item: &Item) {
|
fn handle_release(&self, item: &Item) {
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
|
|
||||||
self.accepted_registry.remove(item);
|
self.accepted_registry.remove(item);
|
||||||
self.accepted_aliases.remove(&item.uid);
|
self.accepted_aliases.remove_aspect(item);
|
||||||
let _ = node.send_remote_signal("release", &serialize(&item.uid).unwrap());
|
|
||||||
|
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 Aspect for ItemAcceptor {
|
||||||
|
const NAME: &'static str = "ItemAcceptor";
|
||||||
|
}
|
||||||
|
impl ItemAcceptorAspect for ItemAcceptor {}
|
||||||
impl Drop for ItemAcceptor {
|
impl Drop for ItemAcceptor {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.type_info.acceptors.remove(self);
|
self.type_info.acceptors.remove(self);
|
||||||
for item in self.accepted_registry.get_valid_contents() {
|
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() {
|
if let Some(ui) = self.type_info.ui.lock().upgrade() {
|
||||||
ui.handle_destroy_acceptor(self);
|
ui.handle_destroy_acceptor(self);
|
||||||
@@ -385,62 +358,39 @@ impl Drop for ItemAcceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
pub fn register_item_ui_flex(
|
||||||
let node = Node::create(client, "", "item", false);
|
calling_client: Arc<Client>,
|
||||||
node.add_local_signal(
|
type_info: &'static TypeInfo,
|
||||||
"create_environment_item",
|
) -> Result<()> {
|
||||||
environment::create_environment_item_flex,
|
let ui = Node::from_id(&calling_client, type_info.ui_node_id, true).add_to_scenegraph()?;
|
||||||
);
|
|
||||||
node.add_local_signal("register_item_ui", register_item_ui_flex);
|
|
||||||
node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
|
|
||||||
node.add_to_scenegraph().map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_info(name: &str) -> Result<&'static TypeInfo> {
|
|
||||||
match name {
|
|
||||||
"environment" => Ok(&ITEM_TYPE_INFO_ENVIRONMENT),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
"panel" => Ok(&ITEM_TYPE_INFO_PANEL),
|
|
||||||
_ => Err(eyre!("Invalid item type")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_item_ui_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct RegisterItemUIInfo<'a> {
|
|
||||||
item_type: &'a str,
|
|
||||||
}
|
|
||||||
let info: RegisterItemUIInfo = deserialize(data)?;
|
|
||||||
let type_info = type_info(info.item_type)?;
|
|
||||||
let ui =
|
|
||||||
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
|
|
||||||
ItemUI::add_to(&ui, type_info)?;
|
ItemUI::add_to(&ui, type_info)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn create_item_acceptor_flex(
|
||||||
|
calling_client: Arc<Client>,
|
||||||
|
id: u64,
|
||||||
|
parent: Arc<Node>,
|
||||||
|
transform: Transform,
|
||||||
|
type_info: &'static TypeInfo,
|
||||||
|
field: Arc<Node>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let space = parent.get_aspect::<Spatial>()?;
|
||||||
|
let field = field.get_aspect::<Field>()?;
|
||||||
|
let transform = transform.to_mat4(true, true, false);
|
||||||
|
|
||||||
fn create_item_acceptor_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
|
||||||
#[derive(Deserialize)]
|
Spatial::add_to(&node, Some(space.clone()), transform, false);
|
||||||
struct CreateItemAcceptorInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
field_path: &'a str,
|
|
||||||
item_type: &'a str,
|
|
||||||
}
|
|
||||||
let info: CreateItemAcceptorInfo = deserialize(data)?;
|
|
||||||
let 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 node = Node::create(
|
|
||||||
&calling_client,
|
|
||||||
&format!("/item/{}/acceptor", type_info.type_name),
|
|
||||||
info.name,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(space), transform, false)?;
|
|
||||||
ItemAcceptor::add_to(&node, type_info, field);
|
ItemAcceptor::add_to(&node, type_info, field);
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItemInterface;
|
||||||
|
// create_interface!(ItemInterface);
|
||||||
|
|||||||
463
src/nodes/items/panel.rs
Normal file
463
src/nodes/items/panel.rs
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
use super::{create_item_acceptor_flex, register_item_ui_flex, ItemAcceptor, ItemInterface};
|
||||||
|
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::{
|
||||||
|
core::{
|
||||||
|
client::{get_env, state, Client, INTERNAL_CLIENT},
|
||||||
|
registry::Registry,
|
||||||
|
},
|
||||||
|
create_interface,
|
||||||
|
nodes::{
|
||||||
|
drawable::model::ModelPart,
|
||||||
|
items::{Item, ItemType, TypeInfo},
|
||||||
|
spatial::{Spatial, Transform},
|
||||||
|
Node,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use glam::Mat4;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use mint::Vector2;
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
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 ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
|
||||||
|
type_name: "panel",
|
||||||
|
alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(),
|
||||||
|
ui_node_id: INTERFACE_NODE_ID,
|
||||||
|
ui: Default::default(),
|
||||||
|
items: Registry::new(),
|
||||||
|
acceptors: Registry::new(),
|
||||||
|
new_acceptor_fn: |node, acceptor, acceptor_field| {
|
||||||
|
let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Backend: Send + Sync + 'static {
|
||||||
|
fn start_data(&self) -> Result<PanelItemInitData>;
|
||||||
|
|
||||||
|
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,
|
||||||
|
scroll_distance: Option<Vector2<f32>>,
|
||||||
|
scroll_steps: Option<Vector2<f32>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn keyboard_keys(&self, surface: &SurfaceId, keymap_id: u64, keys: Vec<i32>);
|
||||||
|
|
||||||
|
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.get_aspect::<Item>().ok()?.specialization else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(panel_item.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
pub node: Weak<Node>,
|
||||||
|
pub backend: Box<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| state(&env));
|
||||||
|
|
||||||
|
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(startup_settings.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
let panel_item = Arc::new(PanelItem {
|
||||||
|
node: Arc::downgrade(&node),
|
||||||
|
backend,
|
||||||
|
});
|
||||||
|
|
||||||
|
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
|
||||||
|
Item::add_to(
|
||||||
|
&node,
|
||||||
|
&ITEM_TYPE_INFO_PANEL,
|
||||||
|
ItemType::Panel(generic_panel_item),
|
||||||
|
);
|
||||||
|
<Self as PanelItemAspect>::add_node_members(&node);
|
||||||
|
|
||||||
|
(node, panel_item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make these stupid vectors u32 in the protocol somehow!!!!!!!1
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
surface: SurfaceId,
|
||||||
|
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_surface_material(surface, &model_part);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Request a resize of the surface (in pixels)."]
|
||||||
|
fn set_toplevel_size(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
size: mint::Vector2<u32>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
panel_item.backend().set_toplevel_size(size);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."]
|
||||||
|
fn keyboard_keys(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
surface: SurfaceId,
|
||||||
|
keymap_id: u64,
|
||||||
|
keys: Vec<i32>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(panel_item) = panel_item_from_node(&node) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
panel_item
|
||||||
|
.backend()
|
||||||
|
.keyboard_keys(&surface, keymap_id, keys);
|
||||||
|
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 PanelItemAcceptorAspect for ItemAcceptor {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_interface!(ItemInterface);
|
||||||
|
impl InterfaceAspect for ItemInterface {
|
||||||
|
#[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<()> {
|
||||||
|
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<()> {
|
||||||
|
create_item_acceptor_flex(
|
||||||
|
calling_client,
|
||||||
|
id,
|
||||||
|
parent,
|
||||||
|
transform,
|
||||||
|
&ITEM_TYPE_INFO_PANEL,
|
||||||
|
field,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
417
src/nodes/mod.rs
417
src/nodes/mod.rs
@@ -3,132 +3,97 @@ pub mod audio;
|
|||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod drawable;
|
pub mod drawable;
|
||||||
pub mod fields;
|
pub mod fields;
|
||||||
pub mod hmd;
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod items;
|
pub mod items;
|
||||||
pub mod root;
|
pub mod root;
|
||||||
pub mod spatial;
|
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::sync::{Arc, Weak};
|
|
||||||
use std::vec::Vec;
|
|
||||||
use tracing::{debug_span, instrument};
|
|
||||||
|
|
||||||
use crate::core::client::Client;
|
|
||||||
use crate::core::registry::Registry;
|
|
||||||
|
|
||||||
use self::alias::Alias;
|
use self::alias::Alias;
|
||||||
use self::audio::Sound;
|
use crate::core::client::Client;
|
||||||
use self::data::{PulseReceiver, PulseSender};
|
use crate::core::registry::Registry;
|
||||||
use self::drawable::Drawable;
|
use crate::core::scenegraph::MethodResponseSender;
|
||||||
use self::fields::Field;
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use self::input::{InputHandler, InputMethod};
|
use parking_lot::Mutex;
|
||||||
use self::items::{Item, ItemAcceptor, ItemUI};
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
use self::spatial::zone::Zone;
|
use rustc_hash::FxHashMap;
|
||||||
use self::spatial::Spatial;
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use self::startup::StartupSettings;
|
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::{Arc, Weak};
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
pub type Signal = fn(&Node, Arc<Client>, &[u8]) -> Result<()>;
|
#[derive(Default)]
|
||||||
pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>;
|
pub struct Message {
|
||||||
|
pub data: Vec<u8>,
|
||||||
pub struct Node {
|
pub fds: Vec<OwnedFd>,
|
||||||
pub enabled: Arc<AtomicBool>,
|
}
|
||||||
pub(super) uid: String,
|
impl From<Vec<u8>> for Message {
|
||||||
path: String,
|
fn from(data: Vec<u8>) -> Self {
|
||||||
client: Weak<Client>,
|
Message {
|
||||||
message_sender_handle: Option<MessageSenderHandle>,
|
data,
|
||||||
// trailing_slash_pos: usize,
|
fds: Vec::new(),
|
||||||
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>,
|
}
|
||||||
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>,
|
}
|
||||||
destroyable: bool,
|
}
|
||||||
|
impl AsRef<[u8]> for Message {
|
||||||
pub alias: OnceCell<Arc<Alias>>,
|
fn as_ref(&self) -> &[u8] {
|
||||||
aliases: Registry<Alias>,
|
&self.data
|
||||||
|
}
|
||||||
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>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
|
||||||
|
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
|
||||||
|
|
||||||
|
stardust_xr_server_codegen::codegen_node_protocol!();
|
||||||
|
|
||||||
|
pub struct OwnedNode(pub Arc<Node>);
|
||||||
|
impl Drop for OwnedNode {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Node {
|
||||||
|
enabled: AtomicBool,
|
||||||
|
id: u64,
|
||||||
|
client: Weak<Client>,
|
||||||
|
message_sender_handle: Option<MessageSenderHandle>,
|
||||||
|
|
||||||
|
local_signals: Mutex<FxHashMap<u64, Signal>>,
|
||||||
|
local_methods: Mutex<FxHashMap<u64, Method>>,
|
||||||
|
aliases: Registry<Alias>,
|
||||||
|
aspects: Aspects,
|
||||||
|
destroyable: bool,
|
||||||
|
}
|
||||||
impl Node {
|
impl Node {
|
||||||
pub fn get_client(&self) -> Option<Arc<Client>> {
|
pub fn get_client(&self) -> Option<Arc<Client>> {
|
||||||
self.client.upgrade()
|
self.client.upgrade()
|
||||||
}
|
}
|
||||||
// pub fn get_name(&self) -> &str {
|
pub fn get_id(&self) -> u64 {
|
||||||
// &self.path[self.trailing_slash_pos + 1..]
|
self.id
|
||||||
// }
|
|
||||||
pub fn get_path(&self) -> &str {
|
|
||||||
self.path.as_str()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self {
|
pub fn generate(client: &Arc<Client>, destroyable: bool) -> Self {
|
||||||
let mut path = parent.to_string();
|
Self::from_id(client, client.generate_id(), destroyable)
|
||||||
path.push('/');
|
}
|
||||||
path.push_str(name);
|
pub fn from_id(client: &Arc<Client>, id: u64, destroyable: bool) -> Self {
|
||||||
let node = Node {
|
let node = Node {
|
||||||
enabled: Arc::new(AtomicBool::new(true)),
|
enabled: AtomicBool::new(true),
|
||||||
uid: nanoid!(),
|
|
||||||
client: Arc::downgrade(client),
|
client: Arc::downgrade(client),
|
||||||
message_sender_handle: client.message_sender_handle.clone(),
|
message_sender_handle: client.message_sender_handle.clone(),
|
||||||
path,
|
id,
|
||||||
// trailing_slash_pos: parent.len(),
|
|
||||||
local_signals: Default::default(),
|
local_signals: Default::default(),
|
||||||
local_methods: Default::default(),
|
local_methods: Default::default(),
|
||||||
|
aliases: Default::default(),
|
||||||
|
aspects: Default::default(),
|
||||||
destroyable,
|
destroyable,
|
||||||
|
|
||||||
alias: OnceCell::new(),
|
|
||||||
aliases: Registry::new(),
|
|
||||||
|
|
||||||
spatial: OnceCell::new(),
|
|
||||||
field: OnceCell::new(),
|
|
||||||
zone: OnceCell::new(),
|
|
||||||
pulse_sender: OnceCell::new(),
|
|
||||||
pulse_receiver: OnceCell::new(),
|
|
||||||
drawable: OnceCell::new(),
|
|
||||||
input_method: OnceCell::new(),
|
|
||||||
input_handler: OnceCell::new(),
|
|
||||||
item: OnceCell::new(),
|
|
||||||
item_acceptor: OnceCell::new(),
|
|
||||||
item_ui: OnceCell::new(),
|
|
||||||
sound: OnceCell::new(),
|
|
||||||
startup_settings: OnceCell::new(),
|
|
||||||
};
|
};
|
||||||
node.add_local_signal("set_enabled", Node::set_enabled_flex);
|
<Node as OwnedAspect>::add_node_members(&node);
|
||||||
node.add_local_signal("destroy", Node::destroy_flex);
|
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
|
||||||
@@ -138,141 +103,229 @@ impl Node {
|
|||||||
.scenegraph
|
.scenegraph
|
||||||
.add_node(self))
|
.add_node(self))
|
||||||
}
|
}
|
||||||
|
pub fn add_to_scenegraph_owned(self) -> Result<OwnedNode> {
|
||||||
|
Ok(OwnedNode(
|
||||||
|
self.get_client()
|
||||||
|
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
|
||||||
|
.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) {
|
pub fn destroy(&self) {
|
||||||
if let Some(client) = self.get_client() {
|
if let Some(client) = self.get_client() {
|
||||||
client.scenegraph.remove_node(self.get_path());
|
client.scenegraph.remove_node(self.get_id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_enabled_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
// very much up for debate if we should allow this, as you can match objects using this
|
||||||
node.enabled.store(deserialize(data)?, Ordering::Relaxed);
|
// pub fn get_client_pid_flex(
|
||||||
Ok(())
|
// 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_local_signal(&self, id: u64, signal: Signal) {
|
||||||
|
self.local_signals.lock().insert(id, signal);
|
||||||
}
|
}
|
||||||
pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
pub fn add_local_method(&self, id: u64, method: Method) {
|
||||||
if node.destroyable {
|
self.local_methods.lock().insert(id, method);
|
||||||
node.destroy();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_local_signal(&self, name: &str, signal: Signal) {
|
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
|
||||||
self.local_signals.insert(name.to_string(), signal);
|
self.aspects.add(aspect)
|
||||||
}
|
}
|
||||||
pub fn add_local_method(&self, name: &str, method: Method) {
|
pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
||||||
self.local_methods.insert(name.to_string(), method);
|
self.aspects.add_raw(aspect)
|
||||||
}
|
}
|
||||||
|
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
|
||||||
pub fn get_aspect<F, T>(
|
self.aspects.get()
|
||||||
&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 send_local_signal(
|
pub fn send_local_signal(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
method: &str,
|
method: u64,
|
||||||
data: &[u8],
|
message: Message,
|
||||||
) -> Result<(), ScenegraphError> {
|
) -> Result<(), ScenegraphError> {
|
||||||
if let Some(alias) = self.alias.get() {
|
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||||
if !alias.info.server_signals.iter().any(|e| e == &method) {
|
if !alias.info.server_signals.iter().any(|e| *e == method) {
|
||||||
return Err(ScenegraphError::SignalNotFound);
|
return Err(ScenegraphError::SignalNotFound);
|
||||||
}
|
}
|
||||||
alias
|
alias
|
||||||
.original
|
.original
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(ScenegraphError::BrokenAlias)?
|
.ok_or(ScenegraphError::BrokenAlias)?
|
||||||
.send_local_signal(calling_client, method, data)
|
.send_local_signal(calling_client, method, message)
|
||||||
} else {
|
} else {
|
||||||
let signal = self
|
let signal = self
|
||||||
.local_signals
|
.local_signals
|
||||||
.get(method)
|
.lock()
|
||||||
|
.get(&method)
|
||||||
|
.cloned()
|
||||||
.ok_or(ScenegraphError::SignalNotFound)?;
|
.ok_or(ScenegraphError::SignalNotFound)?;
|
||||||
signal(self, calling_client, data).map_err(|error| ScenegraphError::SignalError {
|
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
|
||||||
error: error.to_string(),
|
error: error.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn execute_local_method(
|
pub fn execute_local_method(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
calling_client: Arc<Client>,
|
calling_client: Arc<Client>,
|
||||||
method: &str,
|
method: u64,
|
||||||
data: &[u8],
|
message: Message,
|
||||||
) -> Result<Vec<u8>, ScenegraphError> {
|
response: MethodResponseSender,
|
||||||
if let Some(alias) = self.alias.get() {
|
) {
|
||||||
if !alias.info.server_methods.iter().any(|e| e == &method) {
|
if let Ok(alias) = self.get_aspect::<Alias>() {
|
||||||
return Err(ScenegraphError::MethodNotFound);
|
if !alias.info.server_methods.iter().any(|e| *e == method) {
|
||||||
|
response.send(Err(ScenegraphError::MethodNotFound));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
alias
|
let Some(alias) = alias.original.upgrade() else {
|
||||||
.original
|
response.send(Err(ScenegraphError::BrokenAlias));
|
||||||
.upgrade()
|
return;
|
||||||
.ok_or(ScenegraphError::BrokenAlias)?
|
};
|
||||||
.execute_local_method(calling_client, method, data)
|
alias.execute_local_method(
|
||||||
|
calling_client,
|
||||||
|
method,
|
||||||
|
Message {
|
||||||
|
data: message.data.clone(),
|
||||||
|
fds: Vec::new(),
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let method = self
|
let Some(method) = self.local_methods.lock().get(&method).cloned() else {
|
||||||
.local_methods
|
response.send(Err(ScenegraphError::MethodNotFound));
|
||||||
.get(method)
|
return;
|
||||||
.ok_or(ScenegraphError::MethodNotFound)?;
|
};
|
||||||
|
method(self, calling_client, message, response);
|
||||||
debug_span!("Handle method").in_scope(|| {
|
|
||||||
method(self, calling_client, data).map_err(|error| ScenegraphError::MethodError {
|
|
||||||
error: error.to_string(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[instrument(level = "debug", skip_all)]
|
pub fn send_remote_signal(&self, method: u64, message: impl Into<Message>) -> Result<()> {
|
||||||
pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> {
|
let message = message.into();
|
||||||
self.aliases
|
self.aliases
|
||||||
.get_valid_contents()
|
.get_valid_contents()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
|
.filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
|
||||||
.filter_map(|alias| alias.node.upgrade())
|
.filter_map(|alias| alias.node.upgrade())
|
||||||
.for_each(|node| {
|
.for_each(|node| {
|
||||||
let _ = node.send_remote_signal(method, data);
|
// Beware! file descriptors will not be sent to aliases!!!
|
||||||
|
let _ = node.send_remote_signal(
|
||||||
|
method,
|
||||||
|
Message {
|
||||||
|
data: message.data.clone(),
|
||||||
|
fds: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
let path = self.path.clone();
|
|
||||||
let method = method.to_string();
|
|
||||||
let data = data.to_vec();
|
|
||||||
if let Some(handle) = self.message_sender_handle.as_ref() {
|
if let Some(handle) = self.message_sender_handle.as_ref() {
|
||||||
handle.signal(path.as_str(), method.as_str(), data.as_slice())?;
|
handle.signal(self.id, method, &message.data, message.fds)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// #[instrument(level = "debug", skip_all)]
|
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
|
||||||
// pub fn execute_remote_method(
|
&self,
|
||||||
// &self,
|
method: u64,
|
||||||
// method: &str,
|
input: S,
|
||||||
// data: Vec<u8>,
|
fds: Vec<OwnedFd>,
|
||||||
// ) -> Result<impl Future<Output = Result<Vec<u8>>>> {
|
) -> Result<(D, Vec<OwnedFd>)> {
|
||||||
// let message_sender_handle = self
|
let message_sender_handle = self
|
||||||
// .message_sender_handle
|
.message_sender_handle
|
||||||
// .as_ref()
|
.as_ref()
|
||||||
// .ok_or(eyre!("Messenger does not exist for this node"))?;
|
.ok_or(eyre!("Messenger does not exist for this node"))?;
|
||||||
|
|
||||||
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?;
|
let serialized = serialize(input)?;
|
||||||
|
let result = message_sender_handle
|
||||||
|
.method(self.id, method, &serialized, fds)?
|
||||||
|
.await
|
||||||
|
.map_err(|e| eyre!(e))?;
|
||||||
|
|
||||||
// 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 {
|
impl Debug for Node {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Node")
|
f.debug_struct("Node")
|
||||||
.field("uid", &self.uid)
|
.field("id", &self.id)
|
||||||
.field("path", &self.path)
|
.field("local_signals", &self.local_signals.lock().keys())
|
||||||
|
.field("local_methods", &self.local_methods.lock().keys())
|
||||||
|
.field("destroyable", &self.destroyable)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl OwnedAspect for Node {
|
||||||
|
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
|
||||||
|
node.set_enabled(enabled);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
if node.destroyable {
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Drop for Node {
|
impl Drop for Node {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Debug breakpoint
|
// Debug breakpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Aspect: Any + Send + Sync + 'static {
|
||||||
|
const NAME: &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
|
||||||
|
|
||||||
|
impl Aspects {
|
||||||
|
fn add<A: Aspect>(&self, t: A) -> Arc<A> {
|
||||||
|
let aspect = Arc::new(t);
|
||||||
|
self.add_raw(aspect.clone());
|
||||||
|
aspect
|
||||||
|
}
|
||||||
|
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
|
||||||
|
self.0.lock().insert(Self::type_key::<A>(), aspect);
|
||||||
|
}
|
||||||
|
fn get<A: Aspect>(&self) -> Result<Arc<A>> {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.get(&Self::type_key::<A>())
|
||||||
|
.and_then(|a| Arc::downcast(a.clone()).ok())
|
||||||
|
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_key<A: 'static>() -> TypeId {
|
||||||
|
TypeId::of::<A>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for Aspects {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.lock().clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,75 +1,102 @@
|
|||||||
use super::spatial::Spatial;
|
use super::spatial::Spatial;
|
||||||
use super::Node;
|
use super::Node;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
|
use crate::core::client_state::ClientStateParsed;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use color_eyre::eyre::Result;
|
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||||
|
use crate::session::connection_env;
|
||||||
|
use color_eyre::eyre::{bail, Result};
|
||||||
use glam::Mat4;
|
use glam::Mat4;
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
use std::path::PathBuf;
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
static ROOT_REGISTRY: Registry<Root> = Registry::new();
|
static ROOT_REGISTRY: Registry<Root> = Registry::new();
|
||||||
|
|
||||||
|
stardust_xr_server_codegen::codegen_root_protocol!();
|
||||||
|
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
send_frame_event: AtomicBool,
|
connect_instant: Instant,
|
||||||
}
|
}
|
||||||
impl Root {
|
impl Root {
|
||||||
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
|
pub fn create(client: &Arc<Client>, transform: Mat4) -> Result<Arc<Self>> {
|
||||||
let node = Node::create(client, "", "", false);
|
let node = Node::from_id(client, 0, false);
|
||||||
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
|
<Self as RootAspect>::add_node_members(&node);
|
||||||
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
let node = node.add_to_scenegraph()?;
|
||||||
let _ = Spatial::add_to(
|
let _ = Spatial::add_to(&node, None, transform, false);
|
||||||
&node,
|
|
||||||
None,
|
|
||||||
client
|
|
||||||
.startup_settings
|
|
||||||
.as_ref()
|
|
||||||
.map(|settings| settings.transform)
|
|
||||||
.unwrap_or(Mat4::IDENTITY),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(ROOT_REGISTRY.add(Root {
|
Ok(ROOT_REGISTRY.add(Root {
|
||||||
node,
|
node,
|
||||||
send_frame_event: AtomicBool::from(false),
|
connect_instant: Instant::now(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe_frame_flex(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
|
||||||
calling_client
|
|
||||||
.root
|
|
||||||
.get()
|
|
||||||
.unwrap()
|
|
||||||
.send_frame_event
|
|
||||||
.store(true, Ordering::Relaxed);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug")]
|
|
||||||
pub fn send_frame_events(delta: f64) {
|
pub fn send_frame_events(delta: f64) {
|
||||||
if let Ok(data) = serialize((delta, 0.0)) {
|
for root in ROOT_REGISTRY.get_valid_contents() {
|
||||||
for root in ROOT_REGISTRY.get_valid_contents() {
|
let _ = root_client::frame(
|
||||||
if root.send_frame_event.load(Ordering::Relaxed) {
|
&root.node,
|
||||||
let _ = root.node.send_remote_signal("frame", &data);
|
&FrameInfo {
|
||||||
}
|
delta: delta as f32,
|
||||||
}
|
elapsed: root.connect_instant.elapsed().as_secs_f32(),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_base_prefixes_flex(
|
pub fn set_transform(&self, transform: Mat4) {
|
||||||
_node: &Node,
|
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 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>,
|
calling_client: Arc<Client>,
|
||||||
data: &[u8],
|
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<()> {
|
) -> Result<()> {
|
||||||
*calling_client.base_resource_prefixes.lock() = deserialize(data)?;
|
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>) -> color_eyre::eyre::Result<()> {
|
||||||
|
calling_client.disconnect(Ok(()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Root {
|
impl Drop for Root {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
ROOT_REGISTRY.remove(self);
|
ROOT_REGISTRY.remove(self);
|
||||||
|
|||||||
@@ -1,49 +1,70 @@
|
|||||||
pub mod zone;
|
pub mod zone;
|
||||||
|
|
||||||
use self::zone::{create_zone_flex, Zone};
|
use self::zone::Zone;
|
||||||
use super::Node;
|
use super::alias::Alias;
|
||||||
|
use super::fields::{Field, FieldTrait};
|
||||||
|
use super::Aspect;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use color_eyre::eyre::{ensure, eyre, Result};
|
use crate::create_interface;
|
||||||
use glam::{vec3a, Mat4, Quat};
|
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
||||||
|
use color_eyre::eyre::{eyre, OptionExt, Result};
|
||||||
|
use glam::{vec3a, Mat4, Quat, Vec3};
|
||||||
use mint::Vector3;
|
use mint::Vector3;
|
||||||
use nanoid::nanoid;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use rustc_hash::FxHashMap;
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
|
||||||
use stardust_xr::values::Transform;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, OnceLock, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use stereokit::{bounds_grow_to_fit_box, Bounds};
|
use stereokit_rust::maths::Bounds;
|
||||||
use tracing::instrument;
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
|
||||||
|
|
||||||
pub struct Spatial {
|
pub struct Spatial {
|
||||||
uid: String,
|
pub node: Weak<Node>,
|
||||||
pub(super) node: Weak<Node>,
|
|
||||||
self_ref: Weak<Spatial>,
|
|
||||||
parent: Mutex<Option<Arc<Spatial>>>,
|
parent: Mutex<Option<Arc<Spatial>>>,
|
||||||
old_parent: Mutex<Option<Arc<Spatial>>>,
|
old_parent: Mutex<Option<Arc<Spatial>>>,
|
||||||
pub(super) transform: Mutex<Mat4>,
|
transform: Mutex<Mat4>,
|
||||||
zone: Mutex<Weak<Zone>>,
|
zone: Mutex<Weak<Zone>>,
|
||||||
children: Registry<Spatial>,
|
children: Registry<Spatial>,
|
||||||
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
|
pub bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spatial {
|
impl Spatial {
|
||||||
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
|
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
|
||||||
Arc::new_cyclic(|self_ref| Spatial {
|
Arc::new(Spatial {
|
||||||
uid: nanoid!(),
|
|
||||||
node,
|
node,
|
||||||
self_ref: self_ref.clone(),
|
|
||||||
parent: Mutex::new(parent),
|
parent: Mutex::new(parent),
|
||||||
old_parent: Mutex::new(None),
|
old_parent: Mutex::new(None),
|
||||||
transform: Mutex::new(transform),
|
transform: Mutex::new(transform),
|
||||||
zone: Mutex::new(Weak::new()),
|
zone: Mutex::new(Weak::new()),
|
||||||
children: Registry::new(),
|
children: Registry::new(),
|
||||||
bounding_box_calc: OnceLock::default(),
|
bounding_box_calc: OnceCell::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn add_to(
|
pub fn add_to(
|
||||||
@@ -51,36 +72,25 @@ impl Spatial {
|
|||||||
parent: Option<Arc<Spatial>>,
|
parent: Option<Arc<Spatial>>,
|
||||||
transform: Mat4,
|
transform: Mat4,
|
||||||
zoneable: bool,
|
zoneable: bool,
|
||||||
) -> Result<Arc<Spatial>> {
|
) -> Arc<Spatial> {
|
||||||
ensure!(
|
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
|
||||||
node.spatial.get().is_none(),
|
<Spatial as SpatialAspect>::add_node_members(node);
|
||||||
"Internal: Node already has a Spatial aspect!"
|
|
||||||
);
|
|
||||||
let spatial = Spatial::new(Arc::downgrade(node), parent, transform);
|
|
||||||
node.add_local_method("get_bounding_box", Spatial::get_bounding_box_flex);
|
|
||||||
node.add_local_method("get_transform", Spatial::get_transform_flex);
|
|
||||||
node.add_local_signal("set_transform", Spatial::set_transform_flex);
|
|
||||||
node.add_local_signal("set_spatial_parent", Spatial::set_spatial_parent_flex);
|
|
||||||
node.add_local_signal(
|
|
||||||
"set_spatial_parent_in_place",
|
|
||||||
Spatial::set_spatial_parent_in_place_flex,
|
|
||||||
);
|
|
||||||
node.add_local_signal("set_zoneable", Spatial::set_zoneable_flex);
|
|
||||||
node.add_local_method("field_distance", Spatial::field_distance_flex);
|
|
||||||
node.add_local_method("field_normal", Spatial::field_normal_flex);
|
|
||||||
node.add_local_method("field_closest_point", Spatial::field_closest_point_flex);
|
|
||||||
if zoneable {
|
if zoneable {
|
||||||
ZONEABLE_REGISTRY.add_raw(&spatial);
|
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||||
}
|
}
|
||||||
let _ = node.spatial.set(spatial.clone());
|
if let Some(parent) = parent {
|
||||||
Ok(spatial)
|
parent.children.add_raw(&spatial);
|
||||||
|
}
|
||||||
|
<Spatial as SpatialRefAspect>::add_node_members(node);
|
||||||
|
<Spatial as SpatialAspect>::add_node_members(node);
|
||||||
|
node.add_aspect_raw(spatial.clone());
|
||||||
|
spatial
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node(&self) -> Option<Arc<Node>> {
|
pub fn node(&self) -> Option<Arc<Node>> {
|
||||||
self.node.upgrade()
|
self.node.upgrade()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
|
||||||
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
|
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 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());
|
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
|
||||||
@@ -88,39 +98,35 @@ impl Spatial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the output bounds are probably way bigger than they need to be
|
// the output bounds are probably way bigger than they need to be
|
||||||
#[instrument(level = "debug")]
|
|
||||||
pub fn get_bounding_box(&self) -> Bounds {
|
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
|
let mut bounds = self
|
||||||
.bounding_box_calc
|
.bounding_box_calc
|
||||||
.get()
|
.get()
|
||||||
.map(|b| (b)(&node))
|
.map(|b| (b)(&node))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
for child in self.children.get_valid_contents() {
|
for child in self.children.get_valid_contents() {
|
||||||
bounds = bounds_grow_to_fit_box(
|
bounds.grown_box(child.get_bounding_box(), child.local_transform());
|
||||||
bounds,
|
|
||||||
child.get_bounding_box(),
|
|
||||||
Some(child.local_transform()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
|
||||||
pub fn local_transform(&self) -> Mat4 {
|
pub fn local_transform(&self) -> Mat4 {
|
||||||
*self.transform.lock()
|
*self.transform.lock()
|
||||||
}
|
}
|
||||||
pub fn global_transform(&self) -> Mat4 {
|
pub fn global_transform(&self) -> Mat4 {
|
||||||
match self.get_parent() {
|
let parent_transform = self
|
||||||
Some(value) => value.global_transform() * *self.transform.lock(),
|
.get_parent()
|
||||||
None => *self.transform.lock(),
|
.as_deref()
|
||||||
}
|
.map(Self::global_transform)
|
||||||
|
.unwrap_or_default();
|
||||||
|
parent_transform * self.local_transform()
|
||||||
}
|
}
|
||||||
#[instrument]
|
|
||||||
pub fn set_local_transform(&self, transform: Mat4) {
|
pub fn set_local_transform(&self, transform: Mat4) {
|
||||||
*self.transform.lock() = transform;
|
*self.transform.lock() = transform;
|
||||||
}
|
}
|
||||||
#[instrument(level = "debug", skip(self, reference_space))]
|
|
||||||
pub fn set_local_transform_components(
|
pub fn set_local_transform_components(
|
||||||
&self,
|
&self,
|
||||||
reference_space: Option<&Spatial>,
|
reference_space: Option<&Spatial>,
|
||||||
@@ -142,7 +148,7 @@ impl Spatial {
|
|||||||
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
|
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
|
||||||
local_transform_in_reference_space.to_scale_rotation_translation();
|
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()
|
reference_space_pos = pos.into()
|
||||||
}
|
}
|
||||||
if let Some(rot) = transform.rotation {
|
if let Some(rot) = transform.rotation {
|
||||||
@@ -164,7 +170,6 @@ impl Spatial {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
|
||||||
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
|
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
|
||||||
let mut current_ancestor = spatial;
|
let mut current_ancestor = spatial;
|
||||||
loop {
|
loop {
|
||||||
@@ -183,24 +188,21 @@ impl Spatial {
|
|||||||
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
||||||
self.parent.lock().clone()
|
self.parent.lock().clone()
|
||||||
}
|
}
|
||||||
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) {
|
fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) {
|
||||||
if let Some(parent) = self.get_parent() {
|
if let Some(parent) = self.get_parent() {
|
||||||
parent.children.remove(self);
|
parent.children.remove(self);
|
||||||
}
|
}
|
||||||
if let Some(new_parent) = &new_parent {
|
if let Some(new_parent) = &new_parent {
|
||||||
new_parent
|
new_parent.children.add_raw(self);
|
||||||
.children
|
|
||||||
.add_raw(&self.self_ref.upgrade().unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.parent.lock() = new_parent;
|
*self.parent.lock() = new_parent.cloned();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
pub fn set_spatial_parent(self: &Arc<Self>, parent: Option<&Arc<Spatial>>) -> Result<()> {
|
||||||
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
|
||||||
let is_ancestor = parent
|
let is_ancestor = parent
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if is_ancestor {
|
if is_ancestor {
|
||||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||||
@@ -209,12 +211,13 @@ impl Spatial {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn set_spatial_parent_in_place(
|
||||||
#[instrument(level = "debug", skip_all)]
|
self: &Arc<Self>,
|
||||||
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
|
parent: Option<&Arc<Spatial>>,
|
||||||
|
) -> Result<()> {
|
||||||
let is_ancestor = parent
|
let is_ancestor = parent
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|parent| self.is_ancestor_of(parent.clone()))
|
.map(|parent| self.is_ancestor_of((*parent).clone()))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if is_ancestor {
|
if is_ancestor {
|
||||||
return Err(eyre!("Setting spatial parent would cause a loop"));
|
return Err(eyre!("Setting spatial parent would cause a loop"));
|
||||||
@@ -222,61 +225,70 @@ impl Spatial {
|
|||||||
|
|
||||||
self.set_local_transform(Spatial::space_to_space_matrix(
|
self.set_local_transform(Spatial::space_to_space_matrix(
|
||||||
Some(self),
|
Some(self),
|
||||||
parent.as_deref(),
|
parent.map(AsRef::as_ref),
|
||||||
));
|
));
|
||||||
self.set_parent(parent);
|
self.set_parent(parent);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bounding_box_flex(
|
pub(self) fn zone_distance(&self) -> f32 {
|
||||||
node: &Node,
|
self.zone
|
||||||
calling_client: Arc<Client>,
|
.lock()
|
||||||
data: &[u8],
|
.upgrade()
|
||||||
) -> Result<Vec<u8>> {
|
.map(|zone| zone.field.clone())
|
||||||
let this_spatial = node
|
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
|
||||||
.spatial
|
.unwrap_or(f32::MAX)
|
||||||
.get()
|
}
|
||||||
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
|
}
|
||||||
let relative_spatial_path: Option<&str> = deserialize(data)?;
|
impl Aspect for Spatial {
|
||||||
let bounds = if let Some(relative_spatial_path) = relative_spatial_path {
|
const NAME: &'static str = "Spatial";
|
||||||
let relative_spatial = find_reference_space(&calling_client, relative_spatial_path)?;
|
}
|
||||||
let center =
|
impl SpatialRefAspect for Spatial {
|
||||||
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
async fn get_local_bounding_box(
|
||||||
.transform_point3([0.0; 3].into());
|
node: Arc<Node>,
|
||||||
let bounds: Bounds = Bounds {
|
_calling_client: Arc<Client>,
|
||||||
center,
|
) -> Result<BoundingBox> {
|
||||||
dimensions: [0.0; 3].into(),
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
};
|
let bounds = this_spatial.get_bounding_box();
|
||||||
bounds_grow_to_fit_box(
|
|
||||||
bounds,
|
|
||||||
this_spatial.get_bounding_box(),
|
|
||||||
Some(Spatial::space_to_space_matrix(
|
|
||||||
Some(&this_spatial),
|
|
||||||
Some(&relative_spatial),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this_spatial.get_bounding_box()
|
|
||||||
};
|
|
||||||
|
|
||||||
serialize((
|
Ok(BoundingBox {
|
||||||
mint::Vector3::from(bounds.center),
|
center: Vec3::from(bounds.center).into(),
|
||||||
mint::Vector3::from(bounds.dimensions),
|
size: Vec3::from(bounds.dimensions).into(),
|
||||||
))
|
})
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_transform_flex(
|
async fn get_relative_bounding_box(
|
||||||
node: &Node,
|
node: Arc<Node>,
|
||||||
calling_client: Arc<Client>,
|
_calling_client: Arc<Client>,
|
||||||
data: &[u8],
|
relative_to: Arc<Node>,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<BoundingBox> {
|
||||||
let this_spatial = node
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
.spatial
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
.get()
|
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||||
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
|
.transform_point3([0.0; 3].into());
|
||||||
let relative_spatial = find_reference_space(&calling_client, deserialize(data)?)?;
|
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(
|
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||||
Some(this_spatial.as_ref()),
|
Some(this_spatial.as_ref()),
|
||||||
@@ -284,152 +296,86 @@ impl Spatial {
|
|||||||
)
|
)
|
||||||
.to_scale_rotation_translation();
|
.to_scale_rotation_translation();
|
||||||
|
|
||||||
serialize((
|
Ok(Transform {
|
||||||
mint::Vector3::from(position),
|
translation: Some(position.into()),
|
||||||
mint::Quaternion::from(rotation),
|
rotation: Some(rotation.into()),
|
||||||
mint::Vector3::from(scale),
|
scale: Some(scale.into()),
|
||||||
))
|
})
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
}
|
||||||
pub fn set_transform_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
}
|
||||||
#[derive(Deserialize)]
|
impl SpatialAspect for Spatial {
|
||||||
struct TransformArgs<'a> {
|
fn set_local_transform(
|
||||||
reference_space_path: Option<&'a str>,
|
node: Arc<Node>,
|
||||||
transform: Transform,
|
_calling_client: Arc<Client>,
|
||||||
}
|
transform: Transform,
|
||||||
let transform_args: TransformArgs = deserialize(data)?;
|
) -> Result<()> {
|
||||||
let reference_space_transform = transform_args
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
.reference_space_path
|
this_spatial.set_local_transform_components(None, transform);
|
||||||
.map(|path| find_reference_space(&calling_client, path))
|
Ok(())
|
||||||
.transpose()?;
|
}
|
||||||
|
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>()?;
|
||||||
|
|
||||||
node.spatial.get().unwrap().set_local_transform_components(
|
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
|
||||||
reference_space_transform.as_deref(),
|
|
||||||
transform_args.transform,
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn set_spatial_parent_flex(
|
|
||||||
node: &Node,
|
fn set_spatial_parent(
|
||||||
calling_client: Arc<Client>,
|
node: Arc<Node>,
|
||||||
data: &[u8],
|
_calling_client: Arc<Client>,
|
||||||
|
parent: Arc<Node>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
|
let parent = parent.get_aspect::<Spatial>()?;
|
||||||
}
|
|
||||||
pub fn set_spatial_parent_in_place_flex(
|
this_spatial.set_spatial_parent(Some(&parent))?;
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
|
|
||||||
node.spatial
|
|
||||||
.get()
|
|
||||||
.unwrap()
|
|
||||||
.set_spatial_parent_in_place(Some(parent))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn set_zoneable_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let zoneable: bool = deserialize(data)?;
|
fn set_spatial_parent_in_place(
|
||||||
let spatial = node.spatial.get().unwrap();
|
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 {
|
if zoneable {
|
||||||
ZONEABLE_REGISTRY.add_raw(spatial);
|
ZONEABLE_REGISTRY.add_raw(&spatial);
|
||||||
} else {
|
} else {
|
||||||
ZONEABLE_REGISTRY.remove(spatial);
|
ZONEABLE_REGISTRY.remove(&spatial);
|
||||||
zone::release(spatial);
|
zone::release(&spatial);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_distance_flex(
|
// legit gotta find a way to remove old ones, this just keeps the node alive
|
||||||
node: &Node,
|
async fn export_spatial(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<u64> {
|
||||||
calling_client: Arc<Client>,
|
let id = rand::random();
|
||||||
data: &[u8],
|
EXPORTED_SPATIALS.lock().insert(id, node);
|
||||||
) -> Result<Vec<u8>> {
|
Ok(id)
|
||||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
|
||||||
let spatial = node.spatial.get().unwrap();
|
|
||||||
|
|
||||||
let output = fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
calling_client
|
|
||||||
.get_node("Field", f?)
|
|
||||||
.ok()?
|
|
||||||
.get_aspect("Field", "field", |n| &n.field)
|
|
||||||
.ok()
|
|
||||||
.cloned()
|
|
||||||
})
|
|
||||||
.map(|f| f.map(|f| f.distance(spatial, point.into())))
|
|
||||||
.collect::<Vec<Option<f32>>>();
|
|
||||||
|
|
||||||
Ok(serialize(output)?)
|
|
||||||
}
|
|
||||||
pub fn field_normal_flex(
|
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
|
||||||
let spatial = node.spatial.get().unwrap();
|
|
||||||
|
|
||||||
let output = fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
calling_client
|
|
||||||
.get_node("Field", f?)
|
|
||||||
.ok()?
|
|
||||||
.get_aspect("Field", "field", |n| &n.field)
|
|
||||||
.ok()
|
|
||||||
.cloned()
|
|
||||||
})
|
|
||||||
.map(|f| f.map(|f| Vector3::from(f.normal(spatial, point.into(), 0.001))))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(serialize(output)?)
|
|
||||||
}
|
|
||||||
pub fn field_closest_point_flex(
|
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(data)?;
|
|
||||||
let spatial = node.spatial.get().unwrap();
|
|
||||||
|
|
||||||
let output = fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
calling_client
|
|
||||||
.get_node("Field", f?)
|
|
||||||
.ok()?
|
|
||||||
.get_aspect("Field", "field", |n| &n.field)
|
|
||||||
.ok()
|
|
||||||
.cloned()
|
|
||||||
})
|
|
||||||
.map(|f| f.map(|f| Vector3::from(f.closest_point(spatial, point.into(), 0.001))))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(serialize(output)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument]
|
|
||||||
pub(self) fn zone_distance(&self) -> f32 {
|
|
||||||
self.zone
|
|
||||||
.lock()
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|zone| zone.field.upgrade())
|
|
||||||
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
|
|
||||||
.unwrap_or(f32::MAX)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PartialEq for Spatial {
|
impl PartialEq for Spatial {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.uid == other.uid
|
self.node.as_ptr() == other.node.as_ptr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Debug for Spatial {
|
impl Debug for Spatial {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Spatial")
|
f.debug_struct("Spatial")
|
||||||
.field("uid", &self.uid)
|
|
||||||
.field("parent", &self.parent)
|
.field("parent", &self.parent)
|
||||||
.field("old_parent", &self.old_parent)
|
.field("old_parent", &self.old_parent)
|
||||||
.field("transform", &self.transform)
|
.field("transform", &self.transform)
|
||||||
@@ -438,14 +384,14 @@ impl Debug for Spatial {
|
|||||||
}
|
}
|
||||||
impl Drop for Spatial {
|
impl Drop for Spatial {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
ZONEABLE_REGISTRY.remove(self);
|
|
||||||
zone::release(self);
|
zone::release(self);
|
||||||
|
ZONEABLE_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
|
||||||
let position = position
|
let position = position
|
||||||
.then_some(transform.position)
|
.then_some(transform.translation)
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
.unwrap_or_else(|| Vector3::from([0.0; 3]));
|
||||||
let rotation = rotation
|
let rotation = rotation
|
||||||
@@ -460,49 +406,59 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
|
|||||||
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_spatial(
|
pub struct SpatialInterface;
|
||||||
calling_client: &Arc<Client>,
|
impl InterfaceAspect for SpatialInterface {
|
||||||
node_name: &'static str,
|
fn create_spatial(
|
||||||
node_path: &str,
|
_node: Arc<Node>,
|
||||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
calling_client: Arc<Client>,
|
||||||
calling_client
|
id: u64,
|
||||||
.get_node(node_name, node_path)?
|
parent: Arc<Node>,
|
||||||
.get_aspect(node_name, "spatial", |n| &n.spatial)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
pub fn find_spatial_parent(
|
|
||||||
calling_client: &Arc<Client>,
|
|
||||||
node_path: &str,
|
|
||||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
|
||||||
find_spatial(calling_client, "Spatial parent", node_path)
|
|
||||||
}
|
|
||||||
pub fn find_reference_space(
|
|
||||||
calling_client: &Arc<Client>,
|
|
||||||
node_path: &str,
|
|
||||||
) -> color_eyre::eyre::Result<Arc<Spatial>> {
|
|
||||||
find_spatial(calling_client, "Reference space", node_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
|
|
||||||
let node = Node::create(client, "", "spatial", false);
|
|
||||||
node.add_local_signal("create_spatial", create_spatial_flex);
|
|
||||||
node.add_local_signal("create_zone", create_zone_flex);
|
|
||||||
node.add_to_scenegraph().map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_spatial_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateSpatialInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
zoneable: bool,
|
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>> {
|
||||||
|
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(data)?;
|
|
||||||
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, true);
|
|
||||||
let node = node.add_to_scenegraph()?;
|
|
||||||
Spatial::add_to(&node, Some(parent), transform, info.zoneable)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_interface!(SpatialInterface);
|
||||||
|
|||||||
@@ -1,169 +1,162 @@
|
|||||||
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY};
|
use super::{
|
||||||
|
Spatial, ZoneAspect, SPATIAL_ASPECT_ALIAS_INFO, SPATIAL_REF_ASPECT_ALIAS_INFO,
|
||||||
|
ZONEABLE_REGISTRY,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry},
|
core::{client::Client, registry::Registry},
|
||||||
nodes::{
|
nodes::{
|
||||||
alias::{Alias, AliasInfo},
|
alias::{get_original, Alias, AliasList},
|
||||||
fields::{find_field, Field},
|
fields::{Field, FieldTrait},
|
||||||
spatial::{find_spatial_parent, parse_transform},
|
Aspect, Node,
|
||||||
Node,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::vec3a;
|
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};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
|
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
|
||||||
let old_distance = spatial.zone_distance();
|
let old_distance = spatial.zone_distance();
|
||||||
let new_distance = zone
|
let new_distance = zone.field.distance(spatial, vec3a(0.0, 0.0, 0.0));
|
||||||
.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() {
|
if new_distance.abs() < old_distance.abs() {
|
||||||
release(spatial);
|
release(spatial);
|
||||||
*spatial.old_parent.lock() = spatial.get_parent();
|
*spatial.old_parent.lock() = spatial.get_parent();
|
||||||
*spatial.zone.lock() = Arc::downgrade(zone);
|
*spatial.zone.lock() = Arc::downgrade(zone);
|
||||||
zone.captured.add_raw(spatial);
|
let Some(zone_node) = zone.spatial.node.upgrade() else {
|
||||||
let node = zone.spatial.node.upgrade().unwrap();
|
return;
|
||||||
let _ = node.send_remote_signal("capture", &serialize(&spatial.uid).unwrap());
|
};
|
||||||
|
let Some(spatial_node) = spatial.node.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(spatial_alias) = Alias::create(
|
||||||
|
&spatial_node,
|
||||||
|
&zone_node.get_client().unwrap(),
|
||||||
|
SPATIAL_ASPECT_ALIAS_INFO.clone(),
|
||||||
|
Some(&zone.captured),
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = super::zone_client::capture(&zone_node, &spatial_alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn release(spatial: &Spatial) {
|
pub fn release(spatial: &Spatial) {
|
||||||
let _ = 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();
|
let mut spatial_zone = spatial.zone.lock();
|
||||||
|
|
||||||
if let Some(spatial_zone) = spatial_zone.upgrade() {
|
if let Some(spatial_zone) = spatial_zone.upgrade() {
|
||||||
let node = spatial_zone.spatial.node.upgrade().unwrap();
|
spatial_zone.captured.remove_aspect(spatial.as_ref());
|
||||||
spatial_zone.captured.remove(spatial);
|
let Some(node) = spatial_zone.spatial.node.upgrade() else {
|
||||||
let _ = node.send_remote_signal("release", &serialize(&spatial.uid).unwrap());
|
return;
|
||||||
|
};
|
||||||
|
let _ = super::zone_client::release(&node, spatial_node.id);
|
||||||
}
|
}
|
||||||
*spatial_zone = Weak::new();
|
*spatial_zone = Weak::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
spatial: Arc<Spatial>,
|
spatial: Arc<Spatial>,
|
||||||
pub field: Weak<Field>,
|
pub field: Arc<Field>,
|
||||||
zoneables: Mutex<FxHashMap<String, Arc<Node>>>,
|
intersecting_spatials: Registry<Spatial>,
|
||||||
captured: Registry<Spatial>,
|
intersecting: AliasList,
|
||||||
|
captured: AliasList,
|
||||||
}
|
}
|
||||||
impl Zone {
|
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 {
|
let zone = Arc::new(Zone {
|
||||||
spatial,
|
spatial,
|
||||||
field: Arc::downgrade(field),
|
field,
|
||||||
zoneables: Mutex::new(FxHashMap::default()),
|
intersecting_spatials: Registry::default(),
|
||||||
captured: Registry::new(),
|
intersecting: AliasList::default(),
|
||||||
|
captured: AliasList::default(),
|
||||||
});
|
});
|
||||||
node.add_local_signal("capture", Zone::capture_flex);
|
<Zone as ZoneAspect>::add_node_members(node);
|
||||||
node.add_local_signal("release", Zone::release_flex);
|
node.add_aspect_raw(zone.clone());
|
||||||
node.add_local_signal("update", Zone::update);
|
|
||||||
let _ = node.zone.set(zone.clone());
|
|
||||||
zone
|
zone
|
||||||
}
|
}
|
||||||
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
pub fn update(&self) -> Result<()> {
|
||||||
let zone = node.zone.get().unwrap();
|
let node = self.spatial.node().unwrap();
|
||||||
let capture_path: &str = deserialize(data)?;
|
|
||||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
let current_zoneables = Registry::new();
|
||||||
capture(&spatial, zone);
|
for zoneable in ZONEABLE_REGISTRY.get_valid_contents() {
|
||||||
Ok(())
|
let distance = self.field.distance(&zoneable, [0.0; 3].into());
|
||||||
}
|
if distance > 0.0 {
|
||||||
fn release_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
continue;
|
||||||
let capture_path: &str = deserialize(data)?;
|
}
|
||||||
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
|
if let Some(zone) = zoneable.zone.lock().upgrade() {
|
||||||
release(&spatial);
|
let zoneable_distance = zone.field.distance(&zoneable, [0.0; 3].into());
|
||||||
Ok(())
|
if zoneable_distance < distance {
|
||||||
}
|
continue;
|
||||||
fn update(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
|
|
||||||
let zone = node.zone.get().unwrap();
|
|
||||||
let Some(field) = zone.field.upgrade() else { return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed")) };
|
|
||||||
let Some((zone_client, zone_node)) = zone
|
|
||||||
.spatial
|
|
||||||
.node
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|n| n.get_client().zip(Some(n))) else { return Err(color_eyre::eyre::eyre!("No client on node?")) };
|
|
||||||
let mut old_zoneables = zone.zoneables.lock();
|
|
||||||
for (_uid, zoneable) in old_zoneables.iter() {
|
|
||||||
zoneable.destroy();
|
|
||||||
}
|
|
||||||
let captured = zone.captured.get_valid_contents();
|
|
||||||
let zoneables = ZONEABLE_REGISTRY
|
|
||||||
.get_valid_contents()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|zoneable| zoneable.node.upgrade().is_some())
|
|
||||||
.filter(|zoneable| {
|
|
||||||
if captured
|
|
||||||
.iter()
|
|
||||||
.any(|captured| Arc::ptr_eq(captured, zoneable))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
let spatial_zone_distance = zoneable.zone_distance();
|
}
|
||||||
let self_zone_distance = field.distance(zoneable, vec3a(0.0, 0.0, 0.0));
|
current_zoneables.add_raw(&zoneable);
|
||||||
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;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Aspect for Zone {
|
||||||
|
const NAME: &'static str = "Zone";
|
||||||
|
}
|
||||||
|
impl ZoneAspect for Zone {
|
||||||
|
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
|
let zone = node.get_aspect::<Zone>()?;
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Drop for Zone {
|
impl Drop for Zone {
|
||||||
fn drop(&mut self) {
|
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);
|
release(&captured);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateZoneInfo<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
parent_path: &'a str,
|
|
||||||
transform: Transform,
|
|
||||||
field_path: &'a str,
|
|
||||||
}
|
|
||||||
let info: CreateZoneInfo = deserialize(data)?;
|
|
||||||
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
|
|
||||||
let transform = parse_transform(info.transform, true, true, false);
|
|
||||||
let field = find_field(&calling_client, info.field_path)?;
|
|
||||||
|
|
||||||
let node =
|
|
||||||
Node::create(&calling_client, "/spatial/zone", info.name, true).add_to_scenegraph()?;
|
|
||||||
let space = Spatial::add_to(&node, Some(parent), transform, false)?;
|
|
||||||
Zone::add_to(&node, space, &field);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
items::{ItemAcceptor, TypeInfo},
|
|
||||||
spatial::find_spatial,
|
|
||||||
Node,
|
|
||||||
};
|
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use glam::Mat4;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use 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>, data: &[u8]) -> Result<()> {
|
|
||||||
let spatial = find_spatial(&calling_client, "Root spatial", deserialize(data)?)?;
|
|
||||||
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_automatic_acceptor_flex(
|
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let acceptor_node = calling_client.get_node("Item acceptor", deserialize(data)?)?;
|
|
||||||
let acceptor =
|
|
||||||
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
|
|
||||||
let mut startup_settings = node.startup_settings.get().unwrap().lock();
|
|
||||||
startup_settings
|
|
||||||
.acceptors
|
|
||||||
.insert(acceptor.type_info, Arc::downgrade(acceptor));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_startup_token_flex(
|
|
||||||
node: &Node,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
_data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let id = nanoid::nanoid!();
|
|
||||||
let data = serialize(&id)?;
|
|
||||||
STARTUP_SETTINGS
|
|
||||||
.lock()
|
|
||||||
.insert(id, node.startup_settings.get().unwrap().lock().clone());
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Debug for StartupSettings {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("StartupSettings")
|
|
||||||
.field("transform", &self.transform)
|
|
||||||
.field(
|
|
||||||
"acceptors",
|
|
||||||
&self
|
|
||||||
.acceptors
|
|
||||||
.iter()
|
|
||||||
.map(|(k, _)| k.type_name)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_interface(client: &Arc<Client>) -> 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>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let node = Node::create(
|
|
||||||
&calling_client,
|
|
||||||
"/startup/settings",
|
|
||||||
deserialize(data)?,
|
|
||||||
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>,
|
|
||||||
_data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
|
||||||
var_env_insert!(env, STARDUST_INSTANCE);
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
{
|
|
||||||
var_env_insert!(env, WAYLAND_DISPLAY);
|
|
||||||
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
|
|
||||||
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
|
|
||||||
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
|
|
||||||
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
|
|
||||||
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serialize(env)?)
|
|
||||||
}
|
|
||||||
BIN
src/objects/input/cursor.blend
Normal file
BIN
src/objects/input/cursor.blend
Normal file
Binary file not shown.
BIN
src/objects/input/cursor.glb
Normal file
BIN
src/objects/input/cursor.glb
Normal file
Binary file not shown.
@@ -1,19 +1,23 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
input::{pointer::Pointer, InputMethod, InputType},
|
fields::{FieldTrait, Ray},
|
||||||
|
input::{InputDataType, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
Node,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::Mat4;
|
use glam::{vec3, Mat4};
|
||||||
use nanoid::nanoid;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::Serialize;
|
use stardust_xr::values::Datamap;
|
||||||
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit::StereoKitMultiThread;
|
use stereokit_rust::system::Input;
|
||||||
use tracing::instrument;
|
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
pub struct EyeDatamap {
|
||||||
|
eye: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct KeyboardEvent {
|
pub struct KeyboardEvent {
|
||||||
@@ -24,33 +28,79 @@ pub struct KeyboardEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EyePointer {
|
pub struct EyePointer {
|
||||||
|
node: OwnedNode,
|
||||||
spatial: Arc<Spatial>,
|
spatial: Arc<Spatial>,
|
||||||
pointer: Arc<InputMethod>,
|
pointer: Arc<InputMethod>,
|
||||||
}
|
}
|
||||||
impl EyePointer {
|
impl EyePointer {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||||
let pointer =
|
let pointer = InputMethod::add_to(
|
||||||
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
|
&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) {
|
||||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
let ray = Input::get_eyes();
|
||||||
let ray = sk.input_eyes();
|
|
||||||
self.spatial
|
self.spatial
|
||||||
.set_local_transform(Mat4::from_rotation_translation(
|
.set_local_transform(Mat4::from_rotation_translation(
|
||||||
ray.orientation,
|
ray.orientation.into(),
|
||||||
ray.position,
|
ray.position.into(),
|
||||||
));
|
));
|
||||||
{
|
{
|
||||||
// Set pointer input datamap
|
// Set pointer input datamap
|
||||||
let mut fbb = flexbuffers::Builder::default();
|
*self.pointer.datamap.lock() = Datamap::from_typed(EyeDatamap { eye: 2 }).unwrap();
|
||||||
let mut map = fbb.start_map();
|
|
||||||
map.push("eye", 2);
|
|
||||||
map.end_map();
|
|
||||||
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,97 @@ pub mod eye_pointer;
|
|||||||
pub mod mouse_pointer;
|
pub mod mouse_pointer;
|
||||||
pub mod sk_controller;
|
pub mod sk_controller;
|
||||||
pub mod sk_hand;
|
pub mod sk_hand;
|
||||||
|
|
||||||
|
use crate::nodes::{
|
||||||
|
fields::{Field, FieldTrait, Ray},
|
||||||
|
input::{InputDataTrait, InputDataType, InputHandler, InputMethod, INPUT_HANDLER_REGISTRY},
|
||||||
|
spatial::Spatial,
|
||||||
|
};
|
||||||
|
use glam::vec3;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CaptureManager {
|
||||||
|
pub capture: Option<Arc<InputHandler>>,
|
||||||
|
}
|
||||||
|
impl CaptureManager {
|
||||||
|
pub fn update_capture(&mut self, pointer: &InputMethod) {
|
||||||
|
if let Some(capture) = &self.capture {
|
||||||
|
if !pointer
|
||||||
|
.internal_capture_requests
|
||||||
|
.get_valid_contents()
|
||||||
|
.contains(capture)
|
||||||
|
{
|
||||||
|
self.capture.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_new_capture(
|
||||||
|
&mut self,
|
||||||
|
pointer: &InputMethod,
|
||||||
|
distance_calculator: DistanceCalculator,
|
||||||
|
) {
|
||||||
|
if self.capture.is_none() {
|
||||||
|
self.capture = find_closest_capture(pointer, distance_calculator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn apply_capture(&self, method: &InputMethod) {
|
||||||
|
method.captures.clear();
|
||||||
|
if let Some(capture) = &self.capture {
|
||||||
|
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,
|
||||||
|
) -> Option<Arc<InputHandler>> {
|
||||||
|
method
|
||||||
|
.internal_capture_requests
|
||||||
|
.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, _)| handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sorted_handlers(
|
||||||
|
method: &InputMethod,
|
||||||
|
distance_calculator: DistanceCalculator,
|
||||||
|
) -> Vec<Arc<InputHandler>> {
|
||||||
|
INPUT_HANDLER_REGISTRY
|
||||||
|
.get_valid_contents()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handler| handler.spatial.node().map_or(false, |node| node.enabled()))
|
||||||
|
.filter(|handler| {
|
||||||
|
handler
|
||||||
|
.field
|
||||||
|
.spatial
|
||||||
|
.node()
|
||||||
|
.map_or(false, |node| node.enabled())
|
||||||
|
})
|
||||||
|
.filter_map(|handler| {
|
||||||
|
distance_calculator(&method.spatial, &method.data.lock(), &handler.field)
|
||||||
|
.map(|distance| (vec![handler], distance))
|
||||||
|
})
|
||||||
|
.filter(|(_, distance)| *distance > 0.0)
|
||||||
|
.reduce(|(mut handlers_a, distance_a), (handlers_b, distance_b)| {
|
||||||
|
if (distance_a - distance_b).abs() < 0.001 {
|
||||||
|
handlers_a.extend(handlers_b);
|
||||||
|
(handlers_a, distance_a)
|
||||||
|
} else if distance_a < distance_b {
|
||||||
|
(handlers_a, distance_a)
|
||||||
|
} else {
|
||||||
|
(handlers_b, distance_b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(handlers, _)| handlers)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,105 +1,164 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY},
|
data::{
|
||||||
fields::Ray,
|
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
|
||||||
input::{pointer::Pointer, InputMethod, InputType},
|
},
|
||||||
|
fields::{FieldTrait, Ray},
|
||||||
|
input::{InputDataType, InputHandler, InputMethod, Pointer, INPUT_HANDLER_REGISTRY},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
Node,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::{vec3, Mat4, Vec3};
|
use glam::{vec3, Mat4, Vec3};
|
||||||
use nanoid::nanoid;
|
use mint::Vector2;
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
|
use slotmap::{DefaultKey, Key as SlotKey};
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
use stardust_xr::values::Datamap;
|
||||||
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use stereokit_rust::system::{Input, Key};
|
||||||
|
use xkbcommon_rs::{xkb_keymap::CompileFlags, Context, Keymap, KeymapFormat};
|
||||||
|
|
||||||
const SK_KEYMAP: &str = include_str!("sk.kmp");
|
use super::{get_sorted_handlers, CaptureManager, DistanceCalculator};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct KeyboardEvent {
|
struct MouseEvent {
|
||||||
pub keyboard: String,
|
select: f32,
|
||||||
pub keymap: Option<String>,
|
middle: f32,
|
||||||
pub keys_up: Option<Vec<u32>>,
|
context: f32,
|
||||||
pub keys_down: Option<Vec<u32>>,
|
grab: f32,
|
||||||
|
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, Deserialize, Serialize, Default)]
|
||||||
|
pub struct KeyboardEvent {
|
||||||
|
pub keyboard: (),
|
||||||
|
pub xkbv1: (),
|
||||||
|
pub keymap_id: u64,
|
||||||
|
pub keys: Vec<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub struct MousePointer {
|
pub struct MousePointer {
|
||||||
node: Arc<Node>,
|
node: OwnedNode,
|
||||||
|
keymap: DefaultKey,
|
||||||
spatial: Arc<Spatial>,
|
spatial: Arc<Spatial>,
|
||||||
pointer: Arc<InputMethod>,
|
pointer: Arc<InputMethod>,
|
||||||
|
capture_manager: CaptureManager,
|
||||||
|
mouse_datamap: MouseEvent,
|
||||||
|
keyboard_datamap: KeyboardEvent,
|
||||||
keyboard_sender: Arc<PulseSender>,
|
keyboard_sender: Arc<PulseSender>,
|
||||||
}
|
}
|
||||||
impl MousePointer {
|
impl MousePointer {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
|
||||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
|
||||||
let pointer =
|
let pointer = InputMethod::add_to(
|
||||||
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
|
&node.0,
|
||||||
|
InputDataType::Pointer(Pointer::default()),
|
||||||
|
Datamap::from_typed(MouseEvent::default())?,
|
||||||
|
)?;
|
||||||
|
|
||||||
let keyboard_mask = {
|
let context = Context::new(0).unwrap();
|
||||||
let mut fbb = flexbuffers::Builder::default();
|
let keymap = KEYMAPS.lock().insert(
|
||||||
let mut map = fbb.start_map();
|
Keymap::new_from_names(context, None, CompileFlags::NO_FLAGS)
|
||||||
map.push("keyboard", "xkbv1");
|
.unwrap()
|
||||||
map.end_map();
|
.get_as_string(KeymapFormat::TextV1)
|
||||||
Mask(fbb.take_buffer())
|
.unwrap(),
|
||||||
};
|
);
|
||||||
let keyboard_sender = PulseSender::add_to(&node, keyboard_mask).unwrap();
|
|
||||||
|
let keyboard_sender = PulseSender::add_to(
|
||||||
|
&node.0,
|
||||||
|
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
Ok(MousePointer {
|
Ok(MousePointer {
|
||||||
node,
|
node,
|
||||||
spatial,
|
spatial,
|
||||||
pointer,
|
pointer,
|
||||||
|
capture_manager: CaptureManager::default(),
|
||||||
|
mouse_datamap: Default::default(),
|
||||||
|
keyboard_datamap: KeyboardEvent {
|
||||||
|
keyboard: (),
|
||||||
|
xkbv1: (),
|
||||||
|
keymap_id: keymap.data().as_ffi(),
|
||||||
|
keys: vec![],
|
||||||
|
},
|
||||||
|
keymap,
|
||||||
keyboard_sender,
|
keyboard_sender,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
|
pub fn update(&mut self) {
|
||||||
pub fn update(&self, sk: &impl StereoKitMultiThread) {
|
let mouse = Input::get_mouse();
|
||||||
let mouse = sk.input_mouse();
|
|
||||||
|
|
||||||
let ray = ray_from_mouse(mouse.pos).unwrap();
|
let ray = mouse.get_ray();
|
||||||
self.spatial.set_local_transform(
|
self.spatial.set_local_transform(
|
||||||
Mat4::look_to_rh(
|
Mat4::look_to_rh(
|
||||||
Vec3::from(ray.pos),
|
Vec3::from(ray.position),
|
||||||
Vec3::from(ray.dir),
|
Vec3::from(ray.direction),
|
||||||
vec3(0.0, 1.0, 0.0),
|
vec3(0.0, 1.0, 0.0),
|
||||||
)
|
)
|
||||||
.inverse(),
|
.inverse(),
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
// Set pointer input datamap
|
// Set pointer input datamap
|
||||||
let mut fbb = flexbuffers::Builder::default();
|
self.mouse_datamap = MouseEvent {
|
||||||
let mut map = fbb.start_map();
|
select: Input::key(Key::MouseLeft).is_active() as u32 as f32,
|
||||||
map.push(
|
middle: Input::key(Key::MouseCenter).is_active() as u32 as f32,
|
||||||
"select",
|
context: Input::key(Key::MouseRight).is_active() as u32 as f32,
|
||||||
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
|
grab: Input::key(Key::MouseBack).is_active() as u32 as f32,
|
||||||
1.0f32
|
scroll_continuous: [0.0, mouse.scroll_change / 120.0].into(),
|
||||||
} else {
|
scroll_discrete: [0.0, mouse.scroll_change / 120.0].into(),
|
||||||
0.0f32
|
raw_input_events: vec![],
|
||||||
},
|
};
|
||||||
);
|
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
|
||||||
map.push(
|
|
||||||
"grab",
|
|
||||||
if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
|
|
||||||
1.0f32
|
|
||||||
} else {
|
|
||||||
0.0f32
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let mut scroll_vec = map.start_vector("scroll");
|
|
||||||
scroll_vec.push(0_f32);
|
|
||||||
scroll_vec.push(mouse.scroll_change / 120.0);
|
|
||||||
scroll_vec.end_vector();
|
|
||||||
map.end_map();
|
|
||||||
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
|
||||||
}
|
}
|
||||||
self.send_keyboard_input(sk);
|
self.target_pointer_input();
|
||||||
|
self.send_keyboard_input();
|
||||||
|
}
|
||||||
|
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.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_handlers = get_sorted_handlers(&self.pointer, distance_calculator);
|
||||||
|
self.pointer.set_handler_order(sorted_handlers.iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_keyboard_input(&self, sk: &impl StereoKitMultiThread) {
|
fn send_keyboard_input(&mut self) {
|
||||||
let rx = PULSE_RECEIVER_REGISTRY
|
let rx = PULSE_RECEIVER_REGISTRY
|
||||||
.get_valid_contents()
|
.get_valid_contents()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -125,29 +184,131 @@ impl MousePointer {
|
|||||||
.map(|(rx, _)| rx);
|
.map(|(rx, _)| rx);
|
||||||
|
|
||||||
if let Some(rx) = rx {
|
if let Some(rx) = rx {
|
||||||
let mut keys_up = vec![];
|
|
||||||
let mut keys_down = vec![];
|
|
||||||
let keys = (8_u32..254)
|
let keys = (8_u32..254)
|
||||||
.filter_map(|i| Some((i, Key::try_from(i).ok()?)))
|
.map(|i| unsafe { std::mem::transmute(i) })
|
||||||
.map(|(i, k)| (i - 8, sk.input_key(k)));
|
.filter_map(|k| Some((map_key(k)?, Input::key(k))))
|
||||||
for (key, state) in keys {
|
.filter_map(|(i, k)| {
|
||||||
if state.contains(ButtonState::JUST_ACTIVE) {
|
if k.is_just_active() {
|
||||||
keys_down.push(key);
|
Some(i as i32)
|
||||||
} else if state.contains(ButtonState::JUST_INACTIVE) {
|
} else if k.is_just_inactive() {
|
||||||
keys_up.push(key);
|
Some(-(i as i32))
|
||||||
}
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let key_event = KeyboardEvent {
|
self.keyboard_datamap.keys = keys;
|
||||||
keyboard: "xkbv1".to_string(),
|
if !self.keyboard_datamap.keys.is_empty() {
|
||||||
keymap: Some(SK_KEYMAP.to_string()),
|
pulse_receiver_client::data(
|
||||||
keys_up: Some(keys_up),
|
&rx.node.upgrade().unwrap(),
|
||||||
keys_down: Some(keys_down),
|
&self.node.0,
|
||||||
};
|
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
|
||||||
let mut serializer = flexbuffers::FlexbufferSerializer::new();
|
)
|
||||||
let _ = key_event.serialize(&mut serializer);
|
|
||||||
rx.send_data(&self.node.uid, serializer.take_buffer())
|
|
||||||
.unwrap();
|
.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,57 +1,128 @@
|
|||||||
|
use super::{get_sorted_handlers, CaptureManager};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
input::{tip::Tip, InputMethod, InputType},
|
fields::{Field, FieldTrait},
|
||||||
|
input::{InputDataType, InputHandler, InputMethod, Tip, INPUT_HANDLER_REGISTRY},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
Node,
|
Node, OwnedNode,
|
||||||
},
|
},
|
||||||
|
objects::{ObjectHandle, SpatialRef},
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::Mat4;
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use nanoid::nanoid;
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::{
|
use stardust_xr::values::Datamap;
|
||||||
schemas::{flat::Datamap, flex::flexbuffers},
|
|
||||||
values::Transform,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit::{ButtonState, Handed, StereoKitMultiThread};
|
use stereokit_rust::{
|
||||||
use tracing::instrument;
|
material::Material,
|
||||||
|
model::Model,
|
||||||
|
sk::MainThreadToken,
|
||||||
|
system::{Handed, Input},
|
||||||
|
util::Color128,
|
||||||
|
};
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
|
struct ControllerDatamap {
|
||||||
|
select: f32,
|
||||||
|
middle: f32,
|
||||||
|
context: f32,
|
||||||
|
grab: f32,
|
||||||
|
scroll: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SkController {
|
pub struct SkController {
|
||||||
_node: Arc<Node>,
|
object_handle: ObjectHandle<SpatialRef>,
|
||||||
input: Arc<InputMethod>,
|
input: Arc<InputMethod>,
|
||||||
handed: Handed,
|
handed: Handed,
|
||||||
|
model: Model,
|
||||||
|
material: Material,
|
||||||
|
capture_manager: CaptureManager,
|
||||||
|
datamap: ControllerDatamap,
|
||||||
}
|
}
|
||||||
impl SkController {
|
impl SkController {
|
||||||
pub fn new(handed: Handed) -> Result<Self> {
|
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||||
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
let (spatial, object_handle) = SpatialRef::create(
|
||||||
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
|
connection,
|
||||||
let tip = InputType::Tip(Tip::default());
|
&("/org/stardustxr/Controller/".to_string()
|
||||||
let input = InputMethod::add_to(&_node, tip, None)?;
|
+ match handed {
|
||||||
|
Handed::Left => "left",
|
||||||
|
_ => "right",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
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 {
|
Ok(SkController {
|
||||||
_node,
|
object_handle,
|
||||||
input,
|
input,
|
||||||
handed,
|
handed,
|
||||||
|
model,
|
||||||
|
material,
|
||||||
|
capture_manager: CaptureManager::default(),
|
||||||
|
datamap: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
|
pub fn update(&mut self, token: &MainThreadToken) {
|
||||||
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
|
let controller = Input::controller(self.handed);
|
||||||
let controller = sk.input_controller(self.handed);
|
let input_node = self.input.spatial.node().unwrap();
|
||||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
|
input_node.set_enabled(controller.tracked.is_active());
|
||||||
if *self.input.enabled.lock() {
|
if input_node.enabled() {
|
||||||
self.input.spatial.set_local_transform_components(
|
let world_transform = Mat4::from_rotation_translation(
|
||||||
None,
|
controller.aim.orientation.into(),
|
||||||
Transform::from_position_rotation(
|
controller.aim.position.into(),
|
||||||
controller.pose.position,
|
|
||||||
controller.pose.orientation,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
self.material
|
||||||
|
.color_tint(if self.capture_manager.capture.is_none() {
|
||||||
|
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||||
|
} else {
|
||||||
|
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||||
|
});
|
||||||
|
self.model.draw(
|
||||||
|
token,
|
||||||
|
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.input.spatial.set_local_transform(world_transform);
|
||||||
}
|
}
|
||||||
let mut fbb = flexbuffers::Builder::default();
|
|
||||||
let mut map = fbb.start_map();
|
self.datamap = ControllerDatamap {
|
||||||
map.push("select", controller.trigger);
|
select: controller.trigger,
|
||||||
map.push("grab", controller.grip);
|
middle: controller.stick_click.is_active() as u32 as f32,
|
||||||
map.end_map();
|
context: controller.is_x2_pressed() as u32 as f32,
|
||||||
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
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.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||||
|
self.input.set_handler_order(sorted_handlers.iter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,102 @@
|
|||||||
use crate::{
|
use crate::core::client::INTERNAL_CLIENT;
|
||||||
core::client::INTERNAL_CLIENT,
|
use crate::nodes::fields::{Field, FieldTrait};
|
||||||
nodes::{
|
use crate::nodes::input::{InputDataType, InputHandler, INPUT_HANDLER_REGISTRY};
|
||||||
input::{hand::Hand, InputMethod, InputType},
|
use crate::nodes::OwnedNode;
|
||||||
spatial::Spatial,
|
use crate::nodes::{
|
||||||
Node,
|
input::{Hand, InputMethod, Joint},
|
||||||
},
|
spatial::Spatial,
|
||||||
|
Node,
|
||||||
};
|
};
|
||||||
|
use crate::objects::{ObjectHandle, SpatialRef};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::Mat4;
|
use glam::{Mat4, Quat, Vec3};
|
||||||
use nanoid::nanoid;
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::schemas::{
|
use stardust_xr::values::Datamap;
|
||||||
flat::{Datamap, Hand as FlatHand, Joint},
|
use std::f32::INFINITY;
|
||||||
flex::flexbuffers,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
|
use stereokit_rust::sk::{DisplayMode, MainThreadToken, Sk};
|
||||||
use tracing::instrument;
|
use stereokit_rust::system::{HandJoint, HandSource, Handed, Input, LinePoint, Lines};
|
||||||
|
use stereokit_rust::util::Color128;
|
||||||
|
use zbus::Connection;
|
||||||
|
|
||||||
|
use super::{get_sorted_handlers, CaptureManager};
|
||||||
|
|
||||||
fn convert_joint(joint: HandJoint) -> Joint {
|
fn convert_joint(joint: HandJoint) -> Joint {
|
||||||
Joint {
|
Joint {
|
||||||
position: joint.position.into(),
|
position: Vec3::from(joint.position).into(),
|
||||||
rotation: joint.orientation.into(),
|
rotation: Quat::from(joint.orientation).into(),
|
||||||
radius: joint.radius,
|
radius: joint.radius,
|
||||||
|
distance: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
struct HandDatamap {
|
||||||
|
pinch_strength: f32,
|
||||||
|
grab_strength: f32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SkHand {
|
pub struct SkHand {
|
||||||
_node: Arc<Node>,
|
_node: OwnedNode,
|
||||||
input: Arc<InputMethod>,
|
palm_spatial: Arc<Spatial>,
|
||||||
|
palm_object: ObjectHandle<SpatialRef>,
|
||||||
handed: Handed,
|
handed: Handed,
|
||||||
|
input: Arc<InputMethod>,
|
||||||
|
capture_manager: CaptureManager,
|
||||||
|
datamap: HandDatamap,
|
||||||
}
|
}
|
||||||
impl SkHand {
|
impl SkHand {
|
||||||
pub fn new(handed: Handed) -> Result<Self> {
|
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
||||||
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
let (palm_spatial, palm_object) = SpatialRef::create(
|
||||||
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
|
connection,
|
||||||
let hand = InputType::Hand(Box::new(Hand {
|
&("/org/stardustxr/Hand/".to_string()
|
||||||
base: FlatHand {
|
+ match handed {
|
||||||
right: handed == Handed::Right,
|
Handed::Left => "left",
|
||||||
..Default::default()
|
_ => "right",
|
||||||
},
|
} + "/palm"),
|
||||||
}));
|
);
|
||||||
let input = InputMethod::add_to(&_node, hand, None)?;
|
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, false);
|
||||||
Ok(SkHand {
|
Ok(SkHand {
|
||||||
_node,
|
_node,
|
||||||
input,
|
palm_spatial,
|
||||||
|
palm_object,
|
||||||
handed,
|
handed,
|
||||||
|
input,
|
||||||
|
capture_manager: CaptureManager::default(),
|
||||||
|
datamap: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[instrument(level = "debug", name = "Update Hand Input Method", skip_all)]
|
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
||||||
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
|
let sk_hand = Input::hand(self.handed);
|
||||||
let sk_hand = sk.input_hand(self.handed);
|
let real_hand = Input::hand_source(self.handed) as u32 == HandSource::Articulated as u32;
|
||||||
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
|
if let InputDataType::Hand(hand) = &mut *self.input.data.lock() {
|
||||||
let controller = sk.input_controller(self.handed);
|
let input_node = self.input.spatial.node().unwrap();
|
||||||
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE)
|
input_node.set_enabled(
|
||||||
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE);
|
(real_hand || sk.get_active_display_mode() == DisplayMode::Flatscreen)
|
||||||
if *self.input.enabled.lock() {
|
&& sk_hand.tracked.is_active(),
|
||||||
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
);
|
||||||
hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
|
if input_node.enabled() {
|
||||||
hand.base.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
|
hand.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
|
||||||
hand.base.thumb.metacarpal = convert_joint(sk_hand.fingers[0][1]);
|
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 [
|
for (finger, mut sk_finger) in [
|
||||||
(&mut hand.base.index, sk_hand.fingers[1]),
|
(&mut hand.index, sk_hand.fingers[1]),
|
||||||
(&mut hand.base.middle, sk_hand.fingers[2]),
|
(&mut hand.middle, sk_hand.fingers[2]),
|
||||||
(&mut hand.base.ring, sk_hand.fingers[3]),
|
(&mut hand.ring, sk_hand.fingers[3]),
|
||||||
(&mut hand.base.little, sk_hand.fingers[4]),
|
(&mut hand.little, sk_hand.fingers[4]),
|
||||||
] {
|
] {
|
||||||
|
sk_finger[4].radius = 0.0;
|
||||||
finger.tip = convert_joint(sk_finger[4]);
|
finger.tip = convert_joint(sk_finger[4]);
|
||||||
finger.distal = convert_joint(sk_finger[3]);
|
finger.distal = convert_joint(sk_finger[3]);
|
||||||
finger.intermediate = convert_joint(sk_finger[2]);
|
finger.intermediate = convert_joint(sk_finger[2]);
|
||||||
@@ -73,24 +104,134 @@ impl SkHand {
|
|||||||
finger.metacarpal = convert_joint(sk_finger[0]);
|
finger.metacarpal = convert_joint(sk_finger[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
hand.base.palm.position = sk_hand.palm.position.into();
|
hand.palm.position = Vec3::from(sk_hand.palm.position).into();
|
||||||
hand.base.palm.rotation = sk_hand.palm.orientation.into();
|
hand.palm.rotation = Quat::from(sk_hand.palm.orientation).into();
|
||||||
hand.base.palm.radius =
|
hand.palm.radius =
|
||||||
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
|
||||||
|
|
||||||
hand.base.wrist.position = sk_hand.wrist.position.into();
|
self.palm_spatial
|
||||||
hand.base.wrist.rotation = sk_hand.wrist.orientation.into();
|
.set_local_transform(Mat4::from_rotation_translation(
|
||||||
hand.base.wrist.radius =
|
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;
|
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
|
||||||
|
|
||||||
hand.base.elbow = None;
|
hand.elbow = None;
|
||||||
|
|
||||||
|
self.draw(
|
||||||
|
token,
|
||||||
|
if self.capture_manager.capture.is_none() {
|
||||||
|
Color128::new_rgb(1.0, 1.0, 1.0)
|
||||||
|
} else {
|
||||||
|
Color128::new_rgb(0.0, 1.0, 0.75)
|
||||||
|
},
|
||||||
|
hand,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut fbb = flexbuffers::Builder::default();
|
self.datamap.pinch_strength = sk_hand.pinch_activation;
|
||||||
let mut map = fbb.start_map();
|
self.datamap.grab_strength = sk_hand.grip_activation;
|
||||||
map.push("grab_strength", sk_hand.grip_activation);
|
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
||||||
map.push("pinch_strength", sk_hand.pinch_activation);
|
|
||||||
map.end_map();
|
let distance_calculator = |space: &Arc<Spatial>, data: &InputDataType, field: &Field| {
|
||||||
*self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
|
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.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
||||||
|
self.input.set_handler_order(sorted_handlers.iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, token: &MainThreadToken, color: Color128, hand: &Hand) {
|
||||||
|
// thumb
|
||||||
|
Lines::add_list(
|
||||||
|
token,
|
||||||
|
&[
|
||||||
|
joint_to_line_point(&hand.thumb.tip, color),
|
||||||
|
joint_to_line_point(&hand.thumb.distal, color),
|
||||||
|
joint_to_line_point(&hand.thumb.proximal, color),
|
||||||
|
joint_to_line_point(&hand.thumb.metacarpal, color),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// index
|
||||||
|
Lines::add_list(
|
||||||
|
token,
|
||||||
|
&[
|
||||||
|
joint_to_line_point(&hand.index.tip, color),
|
||||||
|
joint_to_line_point(&hand.index.distal, color),
|
||||||
|
joint_to_line_point(&hand.index.intermediate, color),
|
||||||
|
joint_to_line_point(&hand.index.proximal, color),
|
||||||
|
joint_to_line_point(&hand.index.metacarpal, color),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// middle
|
||||||
|
Lines::add_list(
|
||||||
|
token,
|
||||||
|
&[
|
||||||
|
joint_to_line_point(&hand.middle.tip, color),
|
||||||
|
joint_to_line_point(&hand.middle.distal, color),
|
||||||
|
joint_to_line_point(&hand.middle.intermediate, color),
|
||||||
|
joint_to_line_point(&hand.middle.proximal, color),
|
||||||
|
joint_to_line_point(&hand.middle.metacarpal, color),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
// ring
|
||||||
|
Lines::add_list(
|
||||||
|
token,
|
||||||
|
&[
|
||||||
|
joint_to_line_point(&hand.ring.tip, color),
|
||||||
|
joint_to_line_point(&hand.ring.distal, color),
|
||||||
|
joint_to_line_point(&hand.ring.intermediate, color),
|
||||||
|
joint_to_line_point(&hand.ring.proximal, color),
|
||||||
|
joint_to_line_point(&hand.ring.metacarpal, color),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// palm
|
||||||
|
Lines::add_list(
|
||||||
|
token,
|
||||||
|
&[
|
||||||
|
joint_to_line_point(&hand.wrist, color),
|
||||||
|
joint_to_line_point(&hand.thumb.metacarpal, color),
|
||||||
|
joint_to_line_point(&hand.index.metacarpal, color),
|
||||||
|
joint_to_line_point(&hand.middle.metacarpal, color),
|
||||||
|
joint_to_line_point(&hand.ring.metacarpal, color),
|
||||||
|
joint_to_line_point(&hand.little.metacarpal, color),
|
||||||
|
joint_to_line_point(&hand.wrist, color),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,274 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
core::client::INTERNAL_CLIENT,
|
||||||
|
nodes::{
|
||||||
|
fields::{Field, Shape, EXPORTED_FIELDS},
|
||||||
|
spatial::{Spatial, EXPORTED_SPATIALS},
|
||||||
|
Node, OwnedNode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use glam::{vec3, Mat4};
|
||||||
|
use input::{
|
||||||
|
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
||||||
|
sk_hand::SkHand,
|
||||||
|
};
|
||||||
|
use play_space::PlaySpaceBounds;
|
||||||
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
use stereokit_rust::{
|
||||||
|
sk::{DisplayMode, MainThreadToken, Sk},
|
||||||
|
system::{Handed, Input, Key, World},
|
||||||
|
util::Device,
|
||||||
|
};
|
||||||
|
use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection};
|
||||||
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod play_space;
|
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>)>,
|
||||||
|
inputs: Inputs,
|
||||||
|
disable_controllers: bool,
|
||||||
|
disable_hands: bool,
|
||||||
|
}
|
||||||
|
impl ServerObjects {
|
||||||
|
pub fn new(
|
||||||
|
connection: Connection,
|
||||||
|
sk: &Sk,
|
||||||
|
disable_controllers: bool,
|
||||||
|
disable_hands: bool,
|
||||||
|
) -> ServerObjects {
|
||||||
|
let hmd = SpatialRef::create(&connection, "/org/stardustxr/HMD");
|
||||||
|
|
||||||
|
let play_space = (World::has_bounds()
|
||||||
|
&& World::get_bounds_size().x != 0.0
|
||||||
|
&& World::get_bounds_size().y != 0.0)
|
||||||
|
.then(|| SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"));
|
||||||
|
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,
|
||||||
|
inputs,
|
||||||
|
disable_controllers,
|
||||||
|
disable_hands,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
||||||
|
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(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if !self.disable_hands {
|
||||||
|
hand_left.update(sk, token);
|
||||||
|
hand_right.update(sk, token);
|
||||||
|
}
|
||||||
|
if let Some(eye_pointer) = eye_pointer {
|
||||||
|
eye_pointer.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Inputs::MousePointer(mouse_pointer) => mouse_pointer.update(),
|
||||||
|
// Inputs::Controllers((left, right)) => {
|
||||||
|
// left.update(token);
|
||||||
|
// right.update(token);
|
||||||
|
// }
|
||||||
|
Inputs::Hands { left, right } => {
|
||||||
|
left.update(sk, token);
|
||||||
|
right.update(sk, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ObjectHandle<I: Interface>(Connection, OwnedObjectPath, PhantomData<I>);
|
||||||
|
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 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,26 @@
|
|||||||
use std::sync::Arc;
|
use stereokit_rust::system::World;
|
||||||
|
use zbus::{interface, Connection, ObjectServer};
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
pub struct PlaySpaceBounds;
|
||||||
use glam::Mat4;
|
impl PlaySpaceBounds {
|
||||||
use mint::Vector2;
|
pub async fn create(connection: &Connection) {
|
||||||
use nanoid::nanoid;
|
connection
|
||||||
use serde::{Deserialize, Serialize};
|
.object_server()
|
||||||
use stereokit::StereoKitMultiThread;
|
.at("/org/stardustxr/PlaySpace", Self)
|
||||||
|
.await
|
||||||
use crate::{
|
.unwrap();
|
||||||
core::client::INTERNAL_CLIENT,
|
|
||||||
nodes::{
|
|
||||||
data::{Mask, PulseReceiver},
|
|
||||||
fields::{r#box::BoxField, Field},
|
|
||||||
spatial::Spatial,
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
struct PlaySpaceMap {
|
|
||||||
play_space: (),
|
|
||||||
size: Vector2<f32>,
|
|
||||||
}
|
|
||||||
impl Default for PlaySpaceMap {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
play_space: (),
|
|
||||||
size: [0.0; 2].into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[interface(name = "org.stardustxr.PlaySpace")]
|
||||||
pub struct PlaySpace {
|
impl PlaySpaceBounds {
|
||||||
_node: Arc<Node>,
|
#[zbus(property)]
|
||||||
spatial: Arc<Spatial>,
|
fn bounds(&self) -> Vec<(f64, f64)> {
|
||||||
field: Arc<Field>,
|
let bounds = World::get_bounds_size();
|
||||||
_pulse_rx: Arc<PulseReceiver>,
|
vec![
|
||||||
}
|
((bounds.x).into(), (bounds.y).into()),
|
||||||
impl PlaySpace {
|
((bounds.x).into(), (-bounds.y).into()),
|
||||||
pub fn new() -> Result<Self> {
|
((-bounds.x).into(), (-bounds.y).into()),
|
||||||
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
|
((-bounds.x).into(), (bounds.y).into()),
|
||||||
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(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
src/session.rs
Normal file
114
src/session.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
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("GDK_BACKEND".to_string(), "wayland".to_string());
|
||||||
|
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
|
||||||
|
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
|
||||||
|
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
|
||||||
|
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
|
||||||
|
}
|
||||||
|
env
|
||||||
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
use crate::wayland::surface::CoreSurface;
|
use super::{
|
||||||
|
state::{ClientState, WaylandState},
|
||||||
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 portable_atomic::{AtomicU32, Ordering};
|
use portable_atomic::{AtomicU32, Ordering};
|
||||||
|
use rand::Rng;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::renderer::utils::{on_commit_buffer_handler, RendererSurfaceStateUserData},
|
||||||
delegate_compositor,
|
delegate_compositor,
|
||||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
|
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
|
||||||
wayland::compositor::{self, CompositorClientState, CompositorHandler, CompositorState},
|
wayland::compositor::{
|
||||||
|
self, add_post_commit_hook, CompositorClientState, CompositorHandler, CompositorState,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
@@ -17,29 +28,100 @@ impl CompositorHandler for WaylandState {
|
|||||||
|
|
||||||
fn commit(&mut self, surface: &WlSurface) {
|
fn commit(&mut self, surface: &WlSurface) {
|
||||||
debug!(?surface, "Surface commit");
|
debug!(?surface, "Surface commit");
|
||||||
|
|
||||||
|
on_commit_buffer_handler::<WaylandState>(surface);
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let core_surface = compositor::with_states(surface, |data| {
|
compositor::with_states(surface, |data| {
|
||||||
let count_new = data
|
let count_new = data
|
||||||
.data_map
|
.data_map
|
||||||
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
|
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
|
||||||
if !count_new {
|
if !count_new {
|
||||||
count = data
|
if let Some(stored_count) = data.data_map.get::<AtomicU32>() {
|
||||||
.data_map
|
count = stored_count.fetch_add(1, Ordering::Relaxed);
|
||||||
.get::<AtomicU32>()
|
}
|
||||||
.unwrap()
|
|
||||||
.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
||||||
});
|
});
|
||||||
if let Some(core_surface) = core_surface {
|
|
||||||
core_surface.commit(count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||||
&client.get_data::<ClientState>().unwrap().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 {
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
surf.insert_data(panel_item_weak.clone());
|
||||||
|
|
||||||
|
let Some(panel_item) = surface_panel_item(surf) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if info.geometry.size.x != view.dst.w as u32
|
||||||
|
&& info.geometry.size.y != view.dst.h as u32
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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() {
|
||||||
|
panel_item.backend.drop_child(surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate_compositor!(WaylandState);
|
delegate_compositor!(WaylandState);
|
||||||
|
|||||||
@@ -88,9 +88,10 @@ impl KdeDecorationHandler for WaylandState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
_surface: &WlSurface,
|
_surface: &WlSurface,
|
||||||
decoration: &OrgKdeKwinServerDecoration,
|
decoration: &OrgKdeKwinServerDecoration,
|
||||||
_mode: WEnum<KdeMode>,
|
mode: WEnum<KdeMode>,
|
||||||
) {
|
) {
|
||||||
decoration.mode(KdeMode::Server);
|
let Ok(mode) = mode.into_result() else { return };
|
||||||
|
decoration.mode(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delegate_kde_decoration!(WaylandState);
|
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::{
|
||||||
|
dmabuf::{Dmabuf, DmabufFlags},
|
||||||
|
Fourcc, Modifier,
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,46 @@
|
|||||||
mod compositor;
|
mod compositor;
|
||||||
mod data_device;
|
mod data_device;
|
||||||
mod decoration;
|
mod decoration;
|
||||||
pub mod panel_item;
|
|
||||||
mod seat;
|
mod seat;
|
||||||
mod shaders;
|
|
||||||
mod state;
|
mod state;
|
||||||
mod surface;
|
mod surface;
|
||||||
// mod xdg_activation;
|
// mod xdg_activation;
|
||||||
|
mod drm;
|
||||||
|
mod utils;
|
||||||
mod xdg_shell;
|
mod xdg_shell;
|
||||||
|
|
||||||
use self::{state::WaylandState, surface::CORE_SURFACES};
|
use self::{state::WaylandState, surface::CORE_SURFACES};
|
||||||
use crate::{core::task, wayland::state::ClientState};
|
use crate::{core::task, wayland::state::ClientState};
|
||||||
use color_eyre::eyre::{ensure, Result};
|
use color_eyre::eyre::{ensure, Result};
|
||||||
use global_counter::primitive::exact::CounterU32;
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use sk::StereoKitDraw;
|
|
||||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||||
use smithay::backend::egl::EGLContext;
|
use smithay::backend::egl::EGLContext;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::backend::renderer::ImportDma;
|
use smithay::backend::renderer::{ImportDma, Renderer};
|
||||||
use smithay::reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket};
|
use smithay::output::Output;
|
||||||
|
use smithay::reexports::wayland_server::backend::ClientId;
|
||||||
|
use smithay::reexports::wayland_server::DisplayHandle;
|
||||||
|
use smithay::reexports::wayland_server::{Display, ListeningSocket};
|
||||||
|
use smithay::wayland::dmabuf;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::fd::{IntoRawFd, OwnedFd};
|
||||||
use std::os::unix::prelude::AsRawFd;
|
use std::os::unix::prelude::AsRawFd;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
os::unix::{net::UnixListener, prelude::FromRawFd},
|
os::unix::{net::UnixListener, prelude::FromRawFd},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use stereokit as sk;
|
use stereokit_rust::system::{Backend, BackendGraphics};
|
||||||
|
use tokio::io::unix::AsyncFdReadyGuard;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
|
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tracing::{debug, debug_span, info, instrument};
|
use tracing::{debug_span, info, instrument};
|
||||||
|
|
||||||
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
|
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
|
||||||
|
|
||||||
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
|
|
||||||
|
|
||||||
struct EGLRawHandles {
|
struct EGLRawHandles {
|
||||||
display: *const c_void,
|
display: *const c_void,
|
||||||
config: *const c_void,
|
config: *const c_void,
|
||||||
@@ -45,29 +48,56 @@ struct EGLRawHandles {
|
|||||||
}
|
}
|
||||||
fn get_sk_egl() -> Result<EGLRawHandles> {
|
fn get_sk_egl() -> Result<EGLRawHandles> {
|
||||||
ensure!(
|
ensure!(
|
||||||
unsafe { sk::sys::backend_graphics_get() }
|
Backend::graphics() == BackendGraphics::OpenGLESEGL,
|
||||||
== sk::sys::backend_graphics__backend_graphics_opengles_egl,
|
|
||||||
"StereoKit is not running using EGL!"
|
"StereoKit is not running using EGL!"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(unsafe {
|
Ok(unsafe {
|
||||||
EGLRawHandles {
|
EGLRawHandles {
|
||||||
display: sk::sys::backend_opengl_egl_get_display() as *const c_void,
|
display: stereokit_rust::system::backend_opengl_egl_get_display() as *const c_void,
|
||||||
config: sk::sys::backend_opengl_egl_get_config() as *const c_void,
|
config: stereokit_rust::system::backend_opengl_egl_get_config() as *const c_void,
|
||||||
context: sk::sys::backend_opengl_egl_get_context() 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 DisplayWrapper(Mutex<Display<WaylandState>>, DisplayHandle);
|
||||||
|
impl DisplayWrapper {
|
||||||
|
pub fn handle(&self) -> DisplayHandle {
|
||||||
|
self.1.clone()
|
||||||
|
}
|
||||||
|
pub fn dispatch_clients(&self, state: &mut WaylandState) -> Result<usize, std::io::Error> {
|
||||||
|
self.0.lock().dispatch_clients(state)
|
||||||
|
}
|
||||||
|
pub fn flush_clients(&self, client: Option<ClientId>) {
|
||||||
|
if let Some(mut lock) = self.0.try_lock() {
|
||||||
|
let _ = lock.backend().flush(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn poll_fd(&self) -> Result<OwnedFd, std::io::Error> {
|
||||||
|
self.0.lock().backend().poll_fd().try_clone_to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnownedFd(Option<AsyncFd<OwnedFd>>);
|
||||||
|
impl UnownedFd {
|
||||||
|
async fn readable(&self) -> std::io::Result<AsyncFdReadyGuard<'_, OwnedFd>> {
|
||||||
|
self.0.as_ref().unwrap().readable().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for UnownedFd {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.take().unwrap().into_inner().into_raw_fd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Wayland {
|
pub struct Wayland {
|
||||||
display: Arc<Mutex<Display<WaylandState>>>,
|
display: Arc<DisplayWrapper>,
|
||||||
pub socket_name: String,
|
pub socket_name: Option<String>,
|
||||||
join_handle: JoinHandle<Result<()>>,
|
join_handle: JoinHandle<Result<()>>,
|
||||||
renderer: GlesRenderer,
|
renderer: GlesRenderer,
|
||||||
dmabuf_rx: UnboundedReceiver<Dmabuf>,
|
output: Output,
|
||||||
state: Arc<Mutex<WaylandState>>,
|
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
|
||||||
}
|
}
|
||||||
impl Wayland {
|
impl Wayland {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
@@ -84,99 +114,101 @@ impl Wayland {
|
|||||||
let display_handle = display.handle();
|
let display_handle = display.handle();
|
||||||
|
|
||||||
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
|
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
|
||||||
let display = Arc::new(Mutex::new(display));
|
let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone()));
|
||||||
let state = WaylandState::new(display.clone(), display_handle, &renderer, dmabuf_tx);
|
|
||||||
|
|
||||||
let (global_destroy_queue_in, global_destroy_queue) = mpsc::channel(8);
|
let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx);
|
||||||
GLOBAL_DESTROY_QUEUE.set(global_destroy_queue_in).unwrap();
|
let output = wayland_state.lock().output.clone();
|
||||||
|
|
||||||
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
|
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
|
||||||
let socket_name = socket.socket_name().unwrap().to_str().unwrap().to_string();
|
let socket_name = socket
|
||||||
WAYLAND_DISPLAY
|
.socket_name()
|
||||||
.set(socket_name.clone())
|
.and_then(OsStr::to_str)
|
||||||
.expect("seriously message nova this time they screwed up big time");
|
.map(ToString::to_string);
|
||||||
|
if let Some(socket_name) = &socket_name {
|
||||||
|
let _ = WAYLAND_DISPLAY.set(socket_name.clone());
|
||||||
|
}
|
||||||
info!(socket_name, "Wayland active");
|
info!(socket_name, "Wayland active");
|
||||||
|
|
||||||
let join_handle =
|
let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state)?;
|
||||||
Wayland::start_loop(display.clone(), socket, state.clone(), global_destroy_queue)?;
|
|
||||||
|
|
||||||
Ok(Wayland {
|
Ok(Wayland {
|
||||||
display,
|
display,
|
||||||
socket_name,
|
socket_name,
|
||||||
join_handle,
|
join_handle,
|
||||||
renderer,
|
renderer,
|
||||||
|
output,
|
||||||
dmabuf_rx,
|
dmabuf_rx,
|
||||||
state,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_loop(
|
fn start_loop(
|
||||||
display: Arc<Mutex<Display<WaylandState>>>,
|
display: Arc<DisplayWrapper>,
|
||||||
socket: ListeningSocket,
|
socket: ListeningSocket,
|
||||||
state: Arc<Mutex<WaylandState>>,
|
state: Arc<Mutex<WaylandState>>,
|
||||||
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
|
|
||||||
) -> Result<JoinHandle<Result<()>>> {
|
) -> Result<JoinHandle<Result<()>>> {
|
||||||
let listen_async =
|
let listen_async =
|
||||||
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
|
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
|
||||||
|
|
||||||
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?;
|
let dispatch_poll_fd = display.poll_fd()?;
|
||||||
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
|
let dispatch_poll_listener = UnownedFd(Some(AsyncFd::new(dispatch_poll_fd)?));
|
||||||
|
|
||||||
let dh1 = display.lock().handle();
|
let dh1 = display.handle();
|
||||||
let mut dh2 = dh1.clone();
|
let mut dh2 = dh1.clone();
|
||||||
|
|
||||||
Ok(task::new(|| "wayland loop", async move {
|
task::new(|| "wayland loop", async move {
|
||||||
let _socket = socket; // Keep the socket alive
|
let _socket = socket; // Keep the socket alive
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
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
|
acc = listen_async.accept() => { // New client connected
|
||||||
let (stream, _) = acc?;
|
let (stream, _) = acc?;
|
||||||
let client = dh2.insert_client(stream.into_std()?, Arc::new(ClientState::default()))?;
|
let client_state = Arc::new(ClientState {
|
||||||
|
pid: stream.peer_cred().ok().and_then(|c| c.pid()),
|
||||||
state.lock().new_client(client.id(), &dh2);
|
id: OnceCell::new(),
|
||||||
|
compositor_state: Default::default(),
|
||||||
|
seat: state.lock().seat.clone(),
|
||||||
|
});
|
||||||
|
let _client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
|
||||||
}
|
}
|
||||||
e = dispatch_poll_listener.readable() => { // Dispatch
|
e = dispatch_poll_listener.readable() => { // Dispatch
|
||||||
let mut guard = e?;
|
let mut guard = e?;
|
||||||
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
|
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
|
||||||
let mut display = display.lock();
|
display.dispatch_clients(&mut state.lock())?;
|
||||||
display.dispatch_clients(&mut *state.lock())?;
|
display.flush_clients(None);
|
||||||
display.flush_clients()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
guard.clear_ready();
|
guard.clear_ready();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
|
#[instrument(level = "debug", name = "Wayland frame", skip(self))]
|
||||||
pub fn update(&mut self, sk: &impl StereoKitDraw) {
|
pub fn update(&mut self) {
|
||||||
while let Ok(dmabuf) = self.dmabuf_rx.try_recv() {
|
while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
|
||||||
let _ = self.renderer.import_dmabuf(&dmabuf, None);
|
if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
|
||||||
|
if let Some(notifier) = notifier {
|
||||||
|
notifier.failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for core_surface in CORE_SURFACES.get_valid_contents() {
|
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.display.flush_clients(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
|
pub fn frame_event(&self) {
|
||||||
let state = self.state.lock();
|
|
||||||
|
|
||||||
for core_surface in CORE_SURFACES.get_valid_contents() {
|
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) {
|
pub fn make_context_current(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.renderer.egl_context().make_current().unwrap();
|
let _ = self.renderer.egl_context().make_current();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,663 +0,0 @@
|
|||||||
use super::{
|
|
||||||
seat::{Cursor, SeatData},
|
|
||||||
surface::CoreSurface,
|
|
||||||
xdg_shell::{PopupData, ToplevelData, XdgSurfaceData},
|
|
||||||
SERIAL_COUNTER,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
core::{
|
|
||||||
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
|
|
||||||
registry::Registry,
|
|
||||||
},
|
|
||||||
nodes::{
|
|
||||||
drawable::Drawable,
|
|
||||||
items::{self, Item, ItemSpecialization, ItemType, TypeInfo},
|
|
||||||
spatial::Spatial,
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
wayland::seat::{KeyboardEvent, PointerEvent},
|
|
||||||
};
|
|
||||||
use color_eyre::eyre::{bail, eyre, Result};
|
|
||||||
use glam::Mat4;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use mint::Vector2;
|
|
||||||
use nanoid::nanoid;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use serde::{
|
|
||||||
de::{Deserializer, Error, SeqAccess, Visitor},
|
|
||||||
ser::Serializer,
|
|
||||||
Deserialize, Serialize,
|
|
||||||
};
|
|
||||||
use smithay::{
|
|
||||||
reexports::{
|
|
||||||
wayland_protocols::xdg::shell::server::{
|
|
||||||
xdg_popup::XdgPopup,
|
|
||||||
xdg_surface::XdgSurface,
|
|
||||||
xdg_toplevel::{XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE},
|
|
||||||
},
|
|
||||||
wayland_server::{
|
|
||||||
backend::Credentials, protocol::wl_surface::WlSurface, Resource, Weak as WlWeak,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wayland::compositor,
|
|
||||||
};
|
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use tracing::debug;
|
|
||||||
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
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",
|
|
||||||
],
|
|
||||||
ui: Default::default(),
|
|
||||||
items: Registry::new(),
|
|
||||||
acceptors: Registry::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 struct PanelItem {
|
|
||||||
pub uid: String,
|
|
||||||
node: Weak<Node>,
|
|
||||||
cursor: Mutex<Option<WlWeak<WlSurface>>>,
|
|
||||||
pub seat_data: Arc<SeatData>,
|
|
||||||
toplevel: WlWeak<XdgToplevel>,
|
|
||||||
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
|
|
||||||
pointer_grab: Mutex<Option<SurfaceID>>,
|
|
||||||
keyboard_grab: Mutex<Option<SurfaceID>>,
|
|
||||||
}
|
|
||||||
impl PanelItem {
|
|
||||||
pub fn create(
|
|
||||||
toplevel: XdgToplevel,
|
|
||||||
wl_surface: WlSurface,
|
|
||||||
client_credentials: Option<Credentials>,
|
|
||||||
seat_data: Arc<SeatData>,
|
|
||||||
) -> (Arc<Node>, Arc<PanelItem>) {
|
|
||||||
debug!(?toplevel, ?client_credentials, "Create panel item");
|
|
||||||
|
|
||||||
let startup_settings = client_credentials
|
|
||||||
.and_then(|cred| get_env(cred.pid).ok())
|
|
||||||
.and_then(|env| startup_settings(&env));
|
|
||||||
|
|
||||||
let uid = nanoid!();
|
|
||||||
let node = Arc::new(Node::create(
|
|
||||||
&INTERNAL_CLIENT,
|
|
||||||
"/item/panel/item",
|
|
||||||
&uid,
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
|
|
||||||
let panel_item = Arc::new(PanelItem {
|
|
||||||
uid: uid.clone(),
|
|
||||||
node: Arc::downgrade(&node),
|
|
||||||
cursor: Mutex::new(None),
|
|
||||||
seat_data,
|
|
||||||
toplevel: toplevel.downgrade(),
|
|
||||||
popups: Mutex::new(FxHashMap::default()),
|
|
||||||
pointer_grab: Mutex::new(None),
|
|
||||||
keyboard_grab: Mutex::new(None),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(startup_settings) = &startup_settings {
|
|
||||||
spatial.set_local_transform(
|
|
||||||
spatial.global_transform().inverse() * startup_settings.transform,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
panel_item
|
|
||||||
.seat_data
|
|
||||||
.new_surface(&wl_surface, Arc::downgrade(&panel_item));
|
|
||||||
|
|
||||||
let item = Item::add_to(
|
|
||||||
&node,
|
|
||||||
uid,
|
|
||||||
&ITEM_TYPE_INFO_PANEL,
|
|
||||||
ItemType::Panel(panel_item.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
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",
|
|
||||||
PanelItem::apply_surface_material_flex,
|
|
||||||
);
|
|
||||||
node.add_local_signal("configure_toplevel", PanelItem::configure_toplevel_flex);
|
|
||||||
node.add_local_signal(
|
|
||||||
"set_toplevel_capabilities",
|
|
||||||
PanelItem::set_toplevel_capabilities_flex,
|
|
||||||
);
|
|
||||||
node.add_local_signal("pointer_scroll", PanelItem::pointer_scroll_flex);
|
|
||||||
node.add_local_signal("pointer_button", PanelItem::pointer_button_flex);
|
|
||||||
node.add_local_signal("pointer_motion", PanelItem::pointer_motion_flex);
|
|
||||||
|
|
||||||
node.add_local_signal(
|
|
||||||
"keyboard_set_keymap_string",
|
|
||||||
PanelItem::keyboard_set_keymap_string_flex,
|
|
||||||
);
|
|
||||||
node.add_local_signal(
|
|
||||||
"keyboard_set_keymap_names",
|
|
||||||
PanelItem::keyboard_set_keymap_names_flex,
|
|
||||||
);
|
|
||||||
node.add_local_signal("keyboard_key", PanelItem::keyboard_key_flex);
|
|
||||||
|
|
||||||
(node, panel_item)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_node(node: &Node) -> Option<Arc<PanelItem>> {
|
|
||||||
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
|
|
||||||
Some(panel_item.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toplevel(&self) -> XdgToplevel {
|
|
||||||
self.toplevel.upgrade().unwrap()
|
|
||||||
}
|
|
||||||
fn toplevel_xdg_surface(&self) -> XdgSurface {
|
|
||||||
let toplevel = self.toplevel();
|
|
||||||
let data = ToplevelData::get(&toplevel).lock();
|
|
||||||
data.xdg_surface()
|
|
||||||
}
|
|
||||||
fn toplevel_wl_surface(&self) -> WlSurface {
|
|
||||||
XdgSurfaceData::get(&self.toplevel_xdg_surface())
|
|
||||||
.lock()
|
|
||||||
.wl_surface()
|
|
||||||
}
|
|
||||||
fn core_surface(&self) -> Option<Arc<CoreSurface>> {
|
|
||||||
compositor::with_states(&self.toplevel_wl_surface(), |data| {
|
|
||||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn flush_clients(&self) {
|
|
||||||
if let Some(core_surface) = self.core_surface() {
|
|
||||||
core_surface.flush_clients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
|
|
||||||
match id {
|
|
||||||
SurfaceID::Cursor => self.cursor.lock().clone()?.upgrade().ok(),
|
|
||||||
SurfaceID::Toplevel => Some(self.toplevel_wl_surface()),
|
|
||||||
SurfaceID::Popup(popup) => {
|
|
||||||
let popups = self.popups.lock();
|
|
||||||
let popup = popups.get(popup)?.upgrade().ok()?;
|
|
||||||
let surf = PopupData::get(&popup).lock().wl_surface();
|
|
||||||
Some(surf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn wl_surface_from_id_result(&self, id: &SurfaceID) -> Result<WlSurface> {
|
|
||||||
self.wl_surface_from_id(id)
|
|
||||||
.ok_or(eyre!("Surface with ID not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_surface_material_flex(
|
|
||||||
node: &Node,
|
|
||||||
calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct SurfaceMaterialInfo<'a> {
|
|
||||||
surface: SurfaceID,
|
|
||||||
model_node_path: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
let info: SurfaceMaterialInfo = deserialize(data)?;
|
|
||||||
|
|
||||||
let Some(wl_surface) = panel_item.wl_surface_from_id(&info.surface) else { return Ok(()) };
|
|
||||||
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { return Ok(()) };
|
|
||||||
|
|
||||||
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_node)) = model_node.drawable.get() else {bail!("Node is not a model")};
|
|
||||||
debug!(?info, "Apply surface material");
|
|
||||||
|
|
||||||
core_surface.apply_material(model_node);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
|
|
||||||
let (surface_id, position): (SurfaceID, Vector2<f64>) = deserialize(data)?;
|
|
||||||
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
||||||
debug!(?surface_id, ?position, "Pointer deactivate");
|
|
||||||
|
|
||||||
panel_item
|
|
||||||
.seat_data
|
|
||||||
.pointer_event(&wl_surface, PointerEvent::Motion(position));
|
|
||||||
panel_item.flush_clients();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
|
|
||||||
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(data)?;
|
|
||||||
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
||||||
debug!(?surface_id, button, state, "Pointer button");
|
|
||||||
|
|
||||||
panel_item
|
|
||||||
.seat_data
|
|
||||||
.pointer_event(&wl_surface, PointerEvent::Button { button, state });
|
|
||||||
panel_item.flush_clients();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::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(data)?;
|
|
||||||
let wl_surface = panel_item.wl_surface_from_id_result(&info.surface_id)?;
|
|
||||||
|
|
||||||
debug!(?info, "Pointer scroll");
|
|
||||||
|
|
||||||
panel_item.seat_data.pointer_event(
|
|
||||||
&wl_surface,
|
|
||||||
PointerEvent::Scroll {
|
|
||||||
axis_continuous: info.axis_continuous,
|
|
||||||
axis_discrete: info.axis_discrete,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
panel_item.flush_clients();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboard_set_keymap_string_flex(
|
|
||||||
node: &Node,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let context = xkb::Context::new(0);
|
|
||||||
let keymap =
|
|
||||||
Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0)
|
|
||||||
.ok_or_else(|| eyre!("Keymap is not valid"))?;
|
|
||||||
|
|
||||||
PanelItem::keyboard_set_keymap_flex(node, &keymap)
|
|
||||||
}
|
|
||||||
fn keyboard_set_keymap_names_flex(
|
|
||||||
node: &Node,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> 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(data)?;
|
|
||||||
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: &Keymap) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
let toplevel = panel_item.toplevel_wl_surface();
|
|
||||||
debug!(?toplevel, "Keyboard set keymap");
|
|
||||||
|
|
||||||
let mut surfaces = vec![toplevel];
|
|
||||||
surfaces.extend(panel_item.popups.lock().values().filter_map(|p| {
|
|
||||||
let popup = p.upgrade().ok()?;
|
|
||||||
let popup_data = PopupData::get(&popup).lock();
|
|
||||||
let xdg_surface = popup_data.xdg_surface();
|
|
||||||
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
|
|
||||||
Some(xdg_surface_data.wl_surface())
|
|
||||||
}));
|
|
||||||
|
|
||||||
panel_item.seat_data.set_keymap(keymap, surfaces);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboard_key_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(data)?;
|
|
||||||
let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
|
|
||||||
debug!(key, state, "Set keyboard key state");
|
|
||||||
|
|
||||||
panel_item
|
|
||||||
.seat_data
|
|
||||||
.keyboard_event(&wl_surface, KeyboardEvent::Key { key, state });
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_toplevel_flex(
|
|
||||||
node: &Node,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
|
|
||||||
let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
|
|
||||||
let xdg_surface = panel_item.toplevel_xdg_surface();
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct ConfigureToplevelInfo {
|
|
||||||
size: Option<Vector2<u32>>,
|
|
||||||
states: Vec<u32>,
|
|
||||||
bounds: Option<Vector2<u32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let info: ConfigureToplevelInfo = deserialize(data)?;
|
|
||||||
debug!(info = ?&info, "Configure toplevel info");
|
|
||||||
if let Some(bounds) = info.bounds {
|
|
||||||
if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE {
|
|
||||||
xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let zero_size = Vector2::from([0; 2]);
|
|
||||||
let size = info.size.unwrap_or(zero_size);
|
|
||||||
// if size == zero_size && (info.states.contains(1) || info.states.contains(2)) {
|
|
||||||
// xdg_toplevel.configure(
|
|
||||||
// size.x as i32,
|
|
||||||
// size.y as i32,
|
|
||||||
// info.states
|
|
||||||
// .into_iter()
|
|
||||||
// .flat_map(|state| state.to_ne_bytes())
|
|
||||||
// .collect(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
xdg_toplevel.configure(
|
|
||||||
size.x as i32,
|
|
||||||
size.y as i32,
|
|
||||||
info.states.into_iter().flat_map(u32::to_ne_bytes).collect(),
|
|
||||||
);
|
|
||||||
xdg_surface.configure(SERIAL_COUNTER.inc());
|
|
||||||
core_surface.flush_clients();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_toplevel_capabilities_flex(
|
|
||||||
node: &Node,
|
|
||||||
_calling_client: Arc<Client>,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
|
|
||||||
let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
|
|
||||||
let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
|
|
||||||
if xdg_toplevel.version() < EVT_WM_CAPABILITIES_SINCE {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let xdg_surface = panel_item.toplevel_xdg_surface();
|
|
||||||
|
|
||||||
let capabilities: Vec<u8> = deserialize(data)?;
|
|
||||||
debug!("Set toplevel capabilities");
|
|
||||||
xdg_toplevel.wm_capabilities(capabilities);
|
|
||||||
xdg_surface.configure(SERIAL_COUNTER.inc());
|
|
||||||
core_surface.flush_clients();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit_toplevel(&self) {
|
|
||||||
// let mapped_size = self.core_surface().and_then(|c| c.size());
|
|
||||||
let toplevel = self.toplevel();
|
|
||||||
let state = ToplevelData::get(&toplevel);
|
|
||||||
let state = state.lock();
|
|
||||||
// let mut queued_state = state.queued_state.take().unwrap();
|
|
||||||
// queued_state.mapped = mapped_size.is_some();
|
|
||||||
// if let Some(size) = mapped_size {
|
|
||||||
// queued_state.size = size;
|
|
||||||
// queued_state.geometry.update_to_surface_size(size);
|
|
||||||
// }
|
|
||||||
// *state = (*queued_state).clone();
|
|
||||||
// state.queued_state = Some(queued_state);
|
|
||||||
|
|
||||||
debug!(state = ?&*state, "Commit toplevel");
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
let _ = node.send_remote_signal("commit_toplevel", &serialize(&*state).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_popup(&self, popup: &XdgPopup, data: &PopupData) {
|
|
||||||
let uid = data.uid.clone();
|
|
||||||
|
|
||||||
self.popups.lock().insert(uid.clone(), popup.downgrade());
|
|
||||||
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
let _ = node.send_remote_signal("new_popup", &serialize(&(&uid, data)).unwrap());
|
|
||||||
}
|
|
||||||
// pub fn commit_popup(&self, data: &PopupData) {
|
|
||||||
// let xdg_surf = data.xdg_surface.upgrade().unwrap();
|
|
||||||
// let surf = xdg_surf
|
|
||||||
// .data::<XdgSurfaceData>()
|
|
||||||
// .unwrap()
|
|
||||||
// .wl_surface
|
|
||||||
// .upgrade()
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// let core_surface =
|
|
||||||
// compositor::with_states(&surf, |s| s.data_map.get::<Arc<CoreSurface>>().cloned())
|
|
||||||
// .unwrap();
|
|
||||||
// let mut popup_state = data.state.lock();
|
|
||||||
// popup_state.mapped = core_surface.size().is_some();
|
|
||||||
// }
|
|
||||||
pub fn reposition_popup(&self, popup_state: &PopupData) {
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
|
|
||||||
let _ = node.send_remote_signal(
|
|
||||||
"reposition_popup",
|
|
||||||
&serialize(popup_state.positioner_data().unwrap()).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
pub fn drop_popup(&self, uid: &str) {
|
|
||||||
if let Some(popup) = self
|
|
||||||
.popups
|
|
||||||
.lock()
|
|
||||||
.remove(uid)
|
|
||||||
.and_then(|popup| popup.upgrade().ok()?.data::<Arc<PopupData>>().cloned())
|
|
||||||
{
|
|
||||||
self.seat_data.drop_surface(&popup.wl_surface());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
let _ = node.send_remote_signal("drop_popup", &serialize(uid).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
|
|
||||||
let _ = node.send_remote_signal("grab_keyboard", &serialize(sid).unwrap());
|
|
||||||
}
|
|
||||||
pub fn set_cursor(&self, surface: Option<&WlSurface>, hotspot_x: i32, hotspot_y: i32) {
|
|
||||||
let Some(node) = self.node.upgrade() else { return };
|
|
||||||
debug!(?surface, hotspot_x, hotspot_y, "Set cursor size");
|
|
||||||
let mut data = serialize(()).unwrap();
|
|
||||||
|
|
||||||
let cursor_size = surface
|
|
||||||
.and_then(|c| CoreSurface::from_wl_surface(c))
|
|
||||||
.and_then(|c| c.size());
|
|
||||||
|
|
||||||
if let Some(size) = cursor_size {
|
|
||||||
data = serialize((size, (hotspot_x, hotspot_y))).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = node.send_remote_signal("set_cursor", &data);
|
|
||||||
*self.cursor.lock() = surface.map(|surf| surf.downgrade());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_drop(&self) {
|
|
||||||
let toplevel = self.toplevel_wl_surface();
|
|
||||||
self.seat_data.drop_surface(&toplevel);
|
|
||||||
|
|
||||||
debug!("Drop panel item");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ItemSpecialization for PanelItem {
|
|
||||||
fn serialize_start_data(&self, id: &str) -> Vec<u8> {
|
|
||||||
let cursor = self.cursor.lock().as_ref().and_then(|c| c.upgrade().ok());
|
|
||||||
let cursor_size = cursor
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|c| CoreSurface::from_wl_surface(&c))
|
|
||||||
.and_then(|c| c.size());
|
|
||||||
let cursor_hotspot = cursor
|
|
||||||
.and_then(|c| {
|
|
||||||
compositor::with_states(&c, |data| data.data_map.get::<Arc<Cursor>>().cloned())
|
|
||||||
})
|
|
||||||
.map(|cursor| cursor.hotspot);
|
|
||||||
|
|
||||||
let toplevel = self.toplevel();
|
|
||||||
let toplevel_state = ToplevelData::get(&toplevel);
|
|
||||||
let toplevel_state = toplevel_state.lock().clone();
|
|
||||||
|
|
||||||
let popups = self
|
|
||||||
.popups
|
|
||||||
.lock()
|
|
||||||
.values()
|
|
||||||
.filter_map(|v| Some(v.upgrade().ok()?.data::<Mutex<PopupData>>()?.lock().clone()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let pointer_grab = self.pointer_grab.lock().clone();
|
|
||||||
let keyboard_grab = self.keyboard_grab.lock().clone();
|
|
||||||
|
|
||||||
serialize((
|
|
||||||
id,
|
|
||||||
(
|
|
||||||
cursor_size.zip(cursor_hotspot),
|
|
||||||
toplevel_state,
|
|
||||||
popups,
|
|
||||||
pointer_grab,
|
|
||||||
keyboard_grab,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for PanelItem {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Dropped panel item, basically just a debug breakpoint place
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,510 +1,307 @@
|
|||||||
use super::{
|
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
|
||||||
panel_item::PanelItem, state::WaylandState, surface::CoreSurface, GLOBAL_DESTROY_QUEUE,
|
use crate::{
|
||||||
SERIAL_COUNTER,
|
core::task,
|
||||||
};
|
nodes::{
|
||||||
use crate::core::task;
|
data::KEYMAPS,
|
||||||
use color_eyre::eyre::Result;
|
items::panel::{Backend, Geometry, PanelItem},
|
||||||
use mint::Vector2;
|
|
||||||
use nanoid::nanoid;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rand::{seq::IteratorRandom, thread_rng};
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
|
||||||
use smithay::{
|
|
||||||
input::keyboard::{KeymapFile, ModifiersState},
|
|
||||||
reexports::wayland_server::{
|
|
||||||
backend::{ClientId, GlobalId, 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,
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
use mint::Vector2;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use slotmap::KeyData;
|
||||||
|
use smithay::{
|
||||||
|
backend::input::{AxisRelativeDirection, ButtonState, KeyState},
|
||||||
|
delegate_seat,
|
||||||
|
input::{
|
||||||
|
keyboard::{FilterResult, LedState},
|
||||||
|
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent},
|
||||||
|
touch::{self, DownEvent, UpEvent},
|
||||||
|
Seat, SeatHandler,
|
||||||
|
},
|
||||||
|
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak as WlWeak},
|
||||||
|
utils::SERIAL_COUNTER,
|
||||||
wayland::compositor,
|
wayland::compositor,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::sync::{Arc, Weak};
|
||||||
collections::VecDeque,
|
use tokio::sync::watch;
|
||||||
sync::{Arc, Weak},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
use tracing::{debug, warn};
|
|
||||||
use xkbcommon::xkb::{self, Keymap};
|
|
||||||
|
|
||||||
pub struct KeyboardInfo {
|
impl SeatHandler for WaylandState {
|
||||||
keymap: KeymapFile,
|
type PointerFocus = WlSurface;
|
||||||
state: xkb::State,
|
type KeyboardFocus = WlSurface;
|
||||||
mods: ModifiersState,
|
type TouchFocus = WlSurface;
|
||||||
keys: FxHashSet<u32>,
|
|
||||||
|
fn seat_state(&mut self) -> &mut smithay::input::SeatState<Self> {
|
||||||
|
&mut self.seat_state
|
||||||
|
}
|
||||||
|
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(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
||||||
|
core_surface.set_material_offset(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
c.surface = Some(surface.downgrade())
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn led_state_changed(&mut self, _seat: &Seat<Self>, _led_state: LedState) {}
|
||||||
}
|
}
|
||||||
impl KeyboardInfo {
|
delegate_seat!(WaylandState);
|
||||||
pub fn new(keymap: &Keymap) -> Self {
|
|
||||||
KeyboardInfo {
|
pub fn handle_cursor<B: Backend>(
|
||||||
state: xkb::State::new(keymap),
|
panel_item: &Arc<PanelItem<B>>,
|
||||||
keymap: KeymapFile::new(keymap),
|
mut cursor: watch::Receiver<CursorInfo>,
|
||||||
mods: ModifiersState::default(),
|
) {
|
||||||
keys: FxHashSet::default(),
|
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: Option<WlWeak<WlSurface>>,
|
||||||
|
pub hotspot_x: i32,
|
||||||
|
pub hotspot_y: i32,
|
||||||
|
}
|
||||||
|
impl CursorInfo {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<usize> {
|
pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) {
|
||||||
let wl_key_state = match state {
|
let pointer = self.seat.get_pointer().unwrap();
|
||||||
0 => KeyState::Released,
|
if pointer.current_focus() == Some(surface.clone()) {
|
||||||
1 => KeyState::Pressed,
|
pointer.motion(
|
||||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pointer_motion(&self, surface: WlSurface, position: Vector2<f32>) {
|
||||||
|
let Some(state) = self.wayland_state.upgrade() else {
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
let xkb_key_state = match state {
|
let mut state = state.lock();
|
||||||
0 => xkb::KeyDirection::Up,
|
let Some(pointer) = self.seat.get_pointer() else {
|
||||||
1 => xkb::KeyDirection::Down,
|
return;
|
||||||
_ => color_eyre::eyre::bail!("Invalid key state!"),
|
|
||||||
};
|
};
|
||||||
let state_components = self.state.update_key(key + 8, xkb_key_state);
|
pointer.motion(
|
||||||
if state_components != 0 {
|
&mut state,
|
||||||
self.mods.update_with(&self.state);
|
Some((surface, (0.0, 0.0).into())),
|
||||||
keyboard.modifiers(
|
&MotionEvent {
|
||||||
0,
|
location: (position.x as f64, position.y as f64).into(),
|
||||||
self.mods.serialized.depressed,
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
self.mods.serialized.latched,
|
time: 0,
|
||||||
self.mods.serialized.locked,
|
},
|
||||||
|
);
|
||||||
|
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>>,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboard_keys(&self, surface: WlSurface, keymap_id: u64, keys: Vec<i32>) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
for key in keys {
|
||||||
|
keyboard.input(
|
||||||
|
&mut state.lock(),
|
||||||
|
key.unsigned_abs(),
|
||||||
|
if key > 0 {
|
||||||
|
KeyState::Pressed
|
||||||
|
} else {
|
||||||
|
KeyState::Released
|
||||||
|
},
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
0,
|
0,
|
||||||
|
|_, _, _| FilterResult::Forward::<()>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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 {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum PointerEvent {
|
|
||||||
Motion(Vector2<f64>),
|
|
||||||
Button {
|
|
||||||
button: u32,
|
|
||||||
state: u32,
|
|
||||||
},
|
|
||||||
Scroll {
|
|
||||||
axis_continuous: Option<Vector2<f32>>,
|
|
||||||
axis_discrete: Option<Vector2<f32>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum KeyboardEvent {
|
|
||||||
Keymap,
|
|
||||||
Key { key: u32, state: u32 },
|
|
||||||
}
|
|
||||||
|
|
||||||
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1);
|
|
||||||
struct SurfaceInfo {
|
|
||||||
wl_surface: WlWeak<WlSurface>,
|
|
||||||
panel_item: Weak<PanelItem>,
|
|
||||||
pointer_queue: VecDeque<PointerEvent>,
|
|
||||||
pointer_latest_event: Instant,
|
|
||||||
keyboard_queue: VecDeque<KeyboardEvent>,
|
|
||||||
keyboard_info: Option<KeyboardInfo>,
|
|
||||||
}
|
|
||||||
impl SurfaceInfo {
|
|
||||||
fn new(wl_surface: &WlSurface, panel_item: Weak<PanelItem>) -> Self {
|
|
||||||
SurfaceInfo {
|
|
||||||
wl_surface: wl_surface.downgrade(),
|
|
||||||
panel_item,
|
|
||||||
pointer_queue: VecDeque::new(),
|
|
||||||
pointer_latest_event: Instant::now(),
|
|
||||||
keyboard_queue: VecDeque::new(),
|
|
||||||
keyboard_info: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool {
|
|
||||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
|
||||||
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
|
|
||||||
let Some(focus_size) = core_surface.size() else { return false; };
|
|
||||||
|
|
||||||
if !self.pointer_queue.is_empty() {
|
|
||||||
self.pointer_latest_event = Instant::now();
|
|
||||||
}
|
|
||||||
while let Some(event) = self.pointer_queue.pop_front() {
|
|
||||||
match (locked, event) {
|
|
||||||
(false, PointerEvent::Motion(pos)) => {
|
|
||||||
pointer.enter(
|
|
||||||
SERIAL_COUNTER.inc(),
|
|
||||||
&focus,
|
|
||||||
pos.x.clamp(0.0, focus_size.x as f64),
|
|
||||||
pos.y.clamp(0.0, focus_size.y as f64),
|
|
||||||
);
|
|
||||||
locked = true;
|
|
||||||
}
|
|
||||||
(true, PointerEvent::Motion(pos)) => {
|
|
||||||
pointer.motion(
|
|
||||||
0,
|
|
||||||
pos.x.clamp(0.0, focus_size.x as f64),
|
|
||||||
pos.y.clamp(0.0, focus_size.y as f64),
|
|
||||||
);
|
|
||||||
pointer.frame();
|
|
||||||
}
|
|
||||||
(true, PointerEvent::Button { button, state }) => {
|
|
||||||
pointer.button(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
button,
|
|
||||||
match state {
|
|
||||||
0 => ButtonState::Released,
|
|
||||||
1 => ButtonState::Pressed,
|
|
||||||
_ => continue,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
pointer.frame();
|
|
||||||
}
|
|
||||||
(
|
|
||||||
true,
|
|
||||||
PointerEvent::Scroll {
|
|
||||||
axis_continuous,
|
|
||||||
axis_discrete,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if let Some(axis_continuous) = axis_continuous {
|
|
||||||
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
|
|
||||||
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64);
|
|
||||||
}
|
|
||||||
if let Some(axis_discrete) = axis_discrete {
|
|
||||||
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
|
|
||||||
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32);
|
|
||||||
}
|
|
||||||
if axis_discrete.is_none() && axis_continuous.is_none() {
|
|
||||||
pointer.axis_stop(0, Axis::HorizontalScroll);
|
|
||||||
pointer.axis_stop(0, Axis::VerticalScroll);
|
|
||||||
}
|
|
||||||
pointer.frame();
|
|
||||||
}
|
|
||||||
(locked, event) => {
|
|
||||||
warn!(locked, ?event, "Invalid pointer event!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.pointer_latest_event.elapsed() > POINTER_EVENT_TIMEOUT {
|
|
||||||
pointer.leave(SERIAL_COUNTER.inc(), &focus);
|
|
||||||
locked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
locked
|
|
||||||
}
|
|
||||||
fn handle_keyboard_events(&mut self, keyboard: &WlKeyboard, mut locked: bool) -> bool {
|
|
||||||
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
|
|
||||||
let Some(info) = self.keyboard_info.as_mut() else { return true; };
|
|
||||||
|
|
||||||
if !locked {
|
|
||||||
keyboard.enter(0, &focus, vec![]);
|
|
||||||
keyboard.repeat_info(0, 0);
|
|
||||||
locked = info.keymap.send(keyboard).is_ok();
|
|
||||||
}
|
|
||||||
while let Some(event) = self.keyboard_queue.pop_front() {
|
|
||||||
debug!(locked, ?event, "Process keyboard event");
|
|
||||||
match (locked, event) {
|
|
||||||
(true, KeyboardEvent::Keymap) => {
|
|
||||||
let _ = info.keymap.send(keyboard);
|
|
||||||
}
|
|
||||||
(true, KeyboardEvent::Key { key, state }) => {
|
|
||||||
if let Ok(key_count) = info.process(key, state, keyboard) {
|
|
||||||
if key_count == 0 {
|
|
||||||
keyboard.leave(SERIAL_COUNTER.inc(), &focus);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(locked, event) => {
|
|
||||||
warn!(locked, ?event, "Invalid keyboard event!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
locked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SeatData {
|
|
||||||
client: ClientId,
|
|
||||||
global_id: OnceCell<GlobalId>,
|
|
||||||
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
|
|
||||||
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
|
|
||||||
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
|
|
||||||
touch: OnceCell<WlTouch>,
|
|
||||||
}
|
|
||||||
impl SeatData {
|
|
||||||
pub fn new(dh: &DisplayHandle, client: ClientId) -> Arc<Self> {
|
|
||||||
let seat_data = Arc::new(SeatData {
|
|
||||||
client,
|
|
||||||
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(&self, keymap: &Keymap, surfaces: Vec<WlSurface>) {
|
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
|
||||||
let mut panels = self.surfaces.lock();
|
let Some(state) = self.wayland_state.upgrade() else {
|
||||||
let Some((_, focus)) = self.keyboard.get() else {return};
|
return;
|
||||||
for surface in surfaces {
|
};
|
||||||
let Some(surface_info) = panels.get_mut(&surface.id()) else {continue};
|
let Some(touch) = self.seat.get_touch() else {
|
||||||
surface_info
|
return;
|
||||||
.keyboard_info
|
};
|
||||||
.replace(KeyboardInfo::new(keymap));
|
touch.down(
|
||||||
|
&mut state.lock(),
|
||||||
if *focus.lock() == surface.id() {
|
Some((surface, (0.0, 0.0).into())),
|
||||||
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
|
&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>) {
|
||||||
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
|
let Some(state) = self.wayland_state.upgrade() else {
|
||||||
let mut surfaces = self.surfaces.lock();
|
return;
|
||||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
};
|
||||||
surface_info.pointer_queue.push_back(event);
|
let Some(surface) = self.touches.lock().get(&id).and_then(|c| c.upgrade().ok()) else {
|
||||||
drop(surfaces);
|
return;
|
||||||
self.handle_pointer_events();
|
};
|
||||||
|
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 keyboard_event(&self, surface: &WlSurface, event: KeyboardEvent) {
|
pub fn touch_up(&self, id: u32) {
|
||||||
let mut surfaces = self.surfaces.lock();
|
let Some(state) = self.wayland_state.upgrade() else {
|
||||||
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
|
return;
|
||||||
surface_info.keyboard_queue.push_back(event);
|
};
|
||||||
drop(surfaces);
|
let Some(touch) = self.seat.get_touch() else {
|
||||||
self.handle_keyboard_events();
|
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) {
|
||||||
fn handle_pointer_events(&self) {
|
for id in self.touches.lock().keys() {
|
||||||
let mut surfaces = self.surfaces.lock();
|
self.touch_up(*id)
|
||||||
let Some((pointer, pointer_focus)) = self.pointer.get() else {return};
|
|
||||||
let mut pointer_focus = pointer_focus.lock();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let locked = !pointer_focus.is_null();
|
|
||||||
// Pick a pointer to focus on if there is none
|
|
||||||
if pointer_focus.is_null() {
|
|
||||||
*pointer_focus = surfaces
|
|
||||||
.iter()
|
|
||||||
.filter(|(_k, v)| !v.pointer_queue.is_empty())
|
|
||||||
.map(|(k, _v)| k)
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(ObjectId::null());
|
|
||||||
}
|
|
||||||
if pointer_focus.is_null() {
|
|
||||||
// If there's still none, guess we're done with pointer events for the time being
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Some(surface_info) = surfaces.get_mut(&pointer_focus) else {break};
|
|
||||||
if surface_info.handle_pointer_events(pointer, locked) {
|
|
||||||
// We haven't gotten to a point where we can switch the focus
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
*pointer_focus = ObjectId::null();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn handle_keyboard_events(&self) {
|
|
||||||
let mut surfaces = self.surfaces.lock();
|
|
||||||
let Some((keyboard, keyboard_focus)) = self.keyboard.get() else {return};
|
|
||||||
let mut keyboard_focus = keyboard_focus.lock();
|
|
||||||
loop {
|
|
||||||
let locked = !keyboard_focus.is_null();
|
|
||||||
// Pick a keyboard to focus on if there is none
|
|
||||||
if keyboard_focus.is_null() {
|
|
||||||
*keyboard_focus = surfaces
|
|
||||||
.iter()
|
|
||||||
.filter(|(_k, v)| v.keyboard_info.is_some())
|
|
||||||
.filter(|(_k, v)| !v.keyboard_queue.is_empty())
|
|
||||||
.map(|(k, _v)| k)
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(ObjectId::null());
|
|
||||||
}
|
|
||||||
// If there's still none, guess we're done with keyboard events for the time being
|
|
||||||
let Some(surface_info) = surfaces.get_mut(&keyboard_focus) else {break};
|
|
||||||
if surface_info.handle_keyboard_events(keyboard, locked) {
|
|
||||||
// We haven't gotten to a point where we can switch the focus
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
*keyboard_focus = ObjectId::null();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_surface(&self, surface: &WlSurface, panel_item: Weak<PanelItem>) {
|
|
||||||
self.surfaces
|
|
||||||
.lock()
|
|
||||||
.insert(surface.id(), SurfaceInfo::new(surface, panel_item));
|
|
||||||
}
|
|
||||||
pub fn drop_surface(&self, surface: &WlSurface) {
|
|
||||||
self.surfaces.lock().remove(&surface.id());
|
|
||||||
if let Some((_, pointer_focus)) = self.pointer.get() {
|
|
||||||
let mut pointer_focus = pointer_focus.lock();
|
|
||||||
if *pointer_focus == surface.id() {
|
|
||||||
*pointer_focus = ObjectId::null();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some((_, keyboard_focus)) = self.keyboard.get() {
|
|
||||||
let mut keyboard_focus = keyboard_focus.lock();
|
|
||||||
if *keyboard_focus == surface.id() {
|
|
||||||
*keyboard_focus = ObjectId::null();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for 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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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!());
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.capabilities(Capability::Pointer | Capability::Keyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
|
|
||||||
client.id() == data.client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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())));
|
|
||||||
}
|
|
||||||
wl_seat::Request::GetKeyboard { id } => {
|
|
||||||
let keyboard = data_init.init(id, data.clone());
|
|
||||||
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!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Cursor {
|
|
||||||
pub hotspot: Vector2<i32>,
|
|
||||||
}
|
|
||||||
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
|
|
||||||
fn request(
|
|
||||||
state: &mut WaylandState,
|
|
||||||
_client: &Client,
|
|
||||||
_resource: &WlPointer,
|
|
||||||
request: wl_pointer::Request,
|
|
||||||
seat_data: &Arc<SeatData>,
|
|
||||||
dh: &DisplayHandle,
|
|
||||||
_data_init: &mut DataInit<'_, WaylandState>,
|
|
||||||
) {
|
|
||||||
match request {
|
|
||||||
wl_pointer::Request::SetCursor {
|
|
||||||
serial: _,
|
|
||||||
surface,
|
|
||||||
hotspot_x,
|
|
||||||
hotspot_y,
|
|
||||||
} => {
|
|
||||||
if let Some(surface) = surface.as_ref() {
|
|
||||||
CoreSurface::add_to(&state.display, dh.clone(), surface, || (), |_| ());
|
|
||||||
compositor::with_states(surface, |data| {
|
|
||||||
data.data_map.insert_if_missing_threadsafe(|| {
|
|
||||||
Arc::new(Mutex::new(Cursor {
|
|
||||||
hotspot: Vector2::from([hotspot_x, hotspot_y]),
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
let mut cursor = data.data_map.get::<Arc<Mutex<Cursor>>>().unwrap().lock();
|
|
||||||
cursor.hotspot = Vector2::from([hotspot_x, hotspot_y]);
|
|
||||||
|
|
||||||
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
|
|
||||||
core_surface.set_material_offset(1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((_, focus)) = seat_data.pointer.get() else {return};
|
|
||||||
let focus = focus.lock();
|
|
||||||
let surfaces = seat_data.surfaces.lock();
|
|
||||||
let Some(surface_info) = surfaces.get(&focus) else {return};
|
|
||||||
let Some(panel_item) = surface_info.panel_item.upgrade() else {return};
|
|
||||||
panel_item.set_cursor(surface.as_ref(), hotspot_x, hotspot_y);
|
|
||||||
}
|
|
||||||
wl_pointer::Request::Release => (),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,24 +1,29 @@
|
|||||||
use crate::wayland::seat::SeatData;
|
use super::seat::SeatWrapper;
|
||||||
|
use crate::wayland::drm::wl_drm::WlDrm;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
allocator::dmabuf::Dmabuf,
|
allocator::{dmabuf::Dmabuf, Fourcc},
|
||||||
egl::EGLDevice,
|
egl::EGLDevice,
|
||||||
renderer::{gles::GlesRenderer, ImportDma},
|
renderer::gles::GlesRenderer,
|
||||||
},
|
},
|
||||||
delegate_dmabuf, delegate_output, delegate_shm,
|
delegate_dmabuf, delegate_output, delegate_shm,
|
||||||
|
input::{keyboard::XkbConfig, SeatState},
|
||||||
output::{Mode, Output, Scale, Subpixel},
|
output::{Mode, Output, Scale, Subpixel},
|
||||||
reexports::{
|
reexports::{
|
||||||
wayland_protocols::xdg::{
|
wayland_protocols::xdg::{
|
||||||
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
|
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_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
|
||||||
wayland_server::{
|
wayland_server::{
|
||||||
backend::{ClientData, ClientId, DisconnectReason},
|
backend::{ClientData, ClientId, DisconnectReason},
|
||||||
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
|
protocol::{
|
||||||
Display, DisplayHandle,
|
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
|
||||||
|
wl_output::WlOutput,
|
||||||
|
},
|
||||||
|
DisplayHandle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{Size, Transform},
|
utils::{Size, Transform},
|
||||||
@@ -27,23 +32,29 @@ use smithay::{
|
|||||||
compositor::{CompositorClientState, CompositorState},
|
compositor::{CompositorClientState, CompositorState},
|
||||||
dmabuf::{
|
dmabuf::{
|
||||||
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
|
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
|
||||||
ImportError,
|
|
||||||
},
|
},
|
||||||
shell::kde::decoration::KdeDecorationState,
|
output::OutputHandler,
|
||||||
|
shell::{
|
||||||
|
kde::decoration::KdeDecorationState,
|
||||||
|
xdg::{WmCapabilitySet, XdgShellState},
|
||||||
|
},
|
||||||
shm::{ShmHandler, ShmState},
|
shm::{ShmHandler, ShmState},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
|
pub pid: Option<i32>,
|
||||||
|
pub id: OnceCell<ClientId>,
|
||||||
pub compositor_state: CompositorClientState,
|
pub compositor_state: CompositorClientState,
|
||||||
|
pub seat: Arc<SeatWrapper>,
|
||||||
}
|
}
|
||||||
impl ClientData for ClientState {
|
impl ClientData for ClientState {
|
||||||
fn initialized(&self, client_id: ClientId) {
|
fn initialized(&self, client_id: ClientId) {
|
||||||
info!("Wayland client {:?} connected", client_id);
|
info!("Wayland client {:?} connected", client_id);
|
||||||
|
let _ = self.id.set(client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
|
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
|
||||||
@@ -55,26 +66,24 @@ impl ClientData for ClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct WaylandState {
|
pub struct WaylandState {
|
||||||
pub weak_ref: Weak<Mutex<WaylandState>>,
|
|
||||||
pub display: Arc<Mutex<Display<WaylandState>>>,
|
|
||||||
pub display_handle: DisplayHandle,
|
|
||||||
|
|
||||||
pub compositor_state: CompositorState,
|
pub compositor_state: CompositorState,
|
||||||
// pub xdg_activation_state: XdgActivationState,
|
// pub xdg_activation_state: XdgActivationState,
|
||||||
pub kde_decoration_state: KdeDecorationState,
|
pub kde_decoration_state: KdeDecorationState,
|
||||||
pub shm_state: ShmState,
|
pub shm_state: ShmState,
|
||||||
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
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 output: Output,
|
pub output: Output,
|
||||||
pub seats: FxHashMap<ClientId, Arc<SeatData>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaylandState {
|
impl WaylandState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
display: Arc<Mutex<Display<WaylandState>>>,
|
|
||||||
display_handle: DisplayHandle,
|
display_handle: DisplayHandle,
|
||||||
renderer: &GlesRenderer,
|
renderer: &GlesRenderer,
|
||||||
dmabuf_tx: UnboundedSender<Dmabuf>,
|
dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
|
||||||
) -> Arc<Mutex<Self>> {
|
) -> Arc<Mutex<Self>> {
|
||||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||||
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
|
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
|
||||||
@@ -83,16 +92,18 @@ impl WaylandState {
|
|||||||
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
|
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
|
||||||
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
|
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
|
||||||
.and_then(|device| device.try_get_render_node());
|
.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 {
|
let dmabuf_default_feedback = match render_node {
|
||||||
Ok(Some(node)) => {
|
Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
|
||||||
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
|
.build()
|
||||||
let dmabuf_default_feedback =
|
.ok(),
|
||||||
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
Some(dmabuf_default_feedback)
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
warn!("failed to query render node, dmabuf will use v3");
|
warn!("failed to query render node, dmabuf will use v3");
|
||||||
None
|
None
|
||||||
@@ -112,13 +123,18 @@ impl WaylandState {
|
|||||||
);
|
);
|
||||||
(dmabuf_state, dmabuf_global, Some(default_feedback))
|
(dmabuf_state, dmabuf_global, Some(default_feedback))
|
||||||
} else {
|
} else {
|
||||||
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
|
|
||||||
let mut dmabuf_state = DmabufState::new();
|
let mut dmabuf_state = DmabufState::new();
|
||||||
let dmabuf_global =
|
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)
|
(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(
|
let output = Output::new(
|
||||||
"1x".to_owned(),
|
"1x".to_owned(),
|
||||||
smithay::output::PhysicalProperties {
|
smithay::output::PhysicalProperties {
|
||||||
@@ -130,7 +146,7 @@ impl WaylandState {
|
|||||||
);
|
);
|
||||||
let _output_global = output.create_global::<Self>(&display_handle);
|
let _output_global = output.create_global::<Self>(&display_handle);
|
||||||
let mode = Mode {
|
let mode = Mode {
|
||||||
size: (4096, 4096).into(),
|
size: (1024, 1024).into(),
|
||||||
refresh: 60000,
|
refresh: 60000,
|
||||||
};
|
};
|
||||||
output.change_current_state(
|
output.change_current_state(
|
||||||
@@ -140,34 +156,36 @@ impl WaylandState {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
output.set_preferred(mode);
|
output.set_preferred(mode);
|
||||||
|
|
||||||
|
let mut xdg_shell = XdgShellState::new::<Self>(&display_handle);
|
||||||
|
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, WlDataDeviceManager, _>(3, ());
|
||||||
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
|
|
||||||
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
|
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
|
||||||
|
display_handle.create_global::<Self, WlDrm, _>(2, ());
|
||||||
|
|
||||||
info!("Init Wayland compositor");
|
info!("Init Wayland compositor");
|
||||||
|
|
||||||
Arc::new_cyclic(|weak| {
|
Arc::new_cyclic(|weak| {
|
||||||
Mutex::new(WaylandState {
|
Mutex::new(WaylandState {
|
||||||
weak_ref: weak.clone(),
|
|
||||||
display,
|
|
||||||
display_handle,
|
|
||||||
|
|
||||||
compositor_state,
|
compositor_state,
|
||||||
// xdg_activation_state,
|
// xdg_activation_state,
|
||||||
kde_decoration_state,
|
kde_decoration_state,
|
||||||
shm_state,
|
shm_state,
|
||||||
|
drm_formats,
|
||||||
dmabuf_state,
|
dmabuf_state,
|
||||||
dmabuf_tx,
|
dmabuf_tx,
|
||||||
|
seat_state,
|
||||||
|
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
|
||||||
|
xdg_shell,
|
||||||
output,
|
output,
|
||||||
seats: FxHashMap::default(),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_client(&mut self, client: ClientId, dh: &DisplayHandle) {
|
|
||||||
let seat_data = SeatData::new(dh, client.clone());
|
|
||||||
self.seats.insert(client, seat_data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Drop for WaylandState {
|
impl Drop for WaylandState {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -191,10 +209,14 @@ impl DmabufHandler for WaylandState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
_global: &DmabufGlobal,
|
_global: &DmabufGlobal,
|
||||||
dmabuf: Dmabuf,
|
dmabuf: Dmabuf,
|
||||||
) -> Result<(), dmabuf::ImportError> {
|
notifier: dmabuf::ImportNotifier,
|
||||||
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed)
|
) {
|
||||||
|
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_dmabuf!(WaylandState);
|
||||||
delegate_shm!(WaylandState);
|
delegate_shm!(WaylandState);
|
||||||
delegate_output!(WaylandState);
|
delegate_output!(WaylandState);
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
|
use super::utils::WlSurfaceExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{delta::Delta, destroy_queue, registry::Registry},
|
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 once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use send_wrapper::SendWrapper;
|
use send_wrapper::SendWrapper;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::{
|
backend::renderer::{
|
||||||
gles::{GlesRenderer, GlesTexture},
|
gles::{GlesRenderer, GlesTexture},
|
||||||
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
|
utils::{import_surface_tree, RendererSurfaceStateUserData},
|
||||||
Renderer, Texture,
|
Renderer, Texture,
|
||||||
},
|
},
|
||||||
desktop::utils::send_frames_surface_tree,
|
desktop::utils::send_frames_surface_tree,
|
||||||
output::Output,
|
output::Output,
|
||||||
reexports::wayland_server::{
|
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, Resource},
|
||||||
self, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
|
|
||||||
},
|
|
||||||
wayland::compositor::{self, SurfaceData},
|
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{ffi::c_void, sync::Arc, time::Duration};
|
||||||
ffi::c_void,
|
use stereokit_rust::{
|
||||||
sync::{Arc, Weak},
|
material::{Material, Transparency},
|
||||||
time::Duration,
|
shader::Shader,
|
||||||
};
|
tex::{Tex, TexAddress, TexFormat, TexSample, TexType},
|
||||||
use stereokit::{
|
util::Time,
|
||||||
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
|
|
||||||
Transparency,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
|
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
|
||||||
|
|
||||||
pub struct CoreSurfaceData {
|
pub struct CoreSurfaceData {
|
||||||
wl_tex: Option<SendWrapper<GlesTexture>>,
|
wl_tex: Option<SendWrapper<GlesTexture>>,
|
||||||
pub size: Vector2<u32>,
|
|
||||||
}
|
}
|
||||||
impl Drop for CoreSurfaceData {
|
impl Drop for CoreSurfaceData {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -43,144 +42,133 @@ impl Drop for CoreSurfaceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct CoreSurface {
|
pub struct CoreSurface {
|
||||||
display: Weak<Mutex<Display<WaylandState>>>,
|
|
||||||
pub dh: DisplayHandle,
|
|
||||||
pub weak_surface: wayland_server::Weak<WlSurface>,
|
pub weak_surface: wayland_server::Weak<WlSurface>,
|
||||||
mapped_data: Mutex<Option<CoreSurfaceData>>,
|
mapped_data: Mutex<Option<CoreSurfaceData>>,
|
||||||
sk_tex: OnceCell<SendWrapper<Tex>>,
|
sk_tex: OnceCell<Mutex<TexWrapper>>,
|
||||||
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
|
sk_mat: OnceCell<Mutex<MaterialWrapper>>,
|
||||||
material_offset: Mutex<Delta<u32>>,
|
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>,
|
pub pending_material_applications: Registry<ModelPart>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoreSurface {
|
impl CoreSurface {
|
||||||
pub fn add_to(
|
pub fn add_to(surface: &WlSurface) {
|
||||||
display: &Arc<Mutex<Display<WaylandState>>>,
|
let core_surface = CORE_SURFACES.add(CoreSurface {
|
||||||
dh: DisplayHandle,
|
weak_surface: surface.downgrade(),
|
||||||
surface: &WlSurface,
|
mapped_data: Mutex::new(None),
|
||||||
on_mapped: impl Fn() + Send + Sync + 'static,
|
sk_tex: OnceCell::new(),
|
||||||
on_commit: impl Fn(u32) + Send + Sync + 'static,
|
sk_mat: OnceCell::new(),
|
||||||
) {
|
material_offset: Mutex::new(Delta::new(0)),
|
||||||
compositor::with_states(surface, |data| {
|
pending_material_applications: Registry::new(),
|
||||||
data.data_map.insert_if_missing_threadsafe(|| {
|
|
||||||
CORE_SURFACES.add(CoreSurface {
|
|
||||||
display: Arc::downgrade(display),
|
|
||||||
dh,
|
|
||||||
weak_surface: surface.downgrade(),
|
|
||||||
mapped_data: Mutex::new(None),
|
|
||||||
sk_tex: OnceCell::new(),
|
|
||||||
sk_mat: OnceCell::new(),
|
|
||||||
material_offset: Mutex::new(Delta::new(0)),
|
|
||||||
on_mapped: Box::new(on_mapped) as Box<dyn Fn() + Send + Sync>,
|
|
||||||
on_commit: Box::new(on_commit) as Box<dyn Fn(u32) + Send + Sync>,
|
|
||||||
pending_material_applications: Registry::new(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
surface.insert_data(core_surface);
|
||||||
|
|
||||||
pub fn commit(&self, count: u32) {
|
|
||||||
(self.on_commit)(count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
|
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
|
||||||
compositor::with_states(surf, |data| {
|
surf.get_data()
|
||||||
data.data_map.get::<Arc<CoreSurface>>().cloned()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
|
pub fn process(&self, renderer: &mut GlesRenderer) {
|
||||||
let Some(wl_surface) = self.wl_surface() else { return };
|
let Some(wl_surface) = self.wl_surface() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let sk_tex = self.sk_tex.get_or_init(|| {
|
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(|| {
|
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 {
|
// let _ = renderer.with_context(|c| unsafe {
|
||||||
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
|
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
|
||||||
// });
|
// });
|
||||||
|
|
||||||
let mat = sk.material_create(&shader);
|
let mut mat = Material::new(shader, None);
|
||||||
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
|
mat.diffuse_tex(&sk_tex.lock().0);
|
||||||
sk.material_set_transparency(&mat, Transparency::Blend);
|
mat.transparency(Transparency::Blend);
|
||||||
Arc::new(SendWrapper::new(mat))
|
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
|
// Import all surface buffers into textures
|
||||||
if import_surface_tree(renderer, &wl_surface).is_err() {
|
if import_surface_tree(renderer, &wl_surface).is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapped = compositor::with_states(&wl_surface, |data| {
|
self.update_textures(renderer);
|
||||||
data.data_map
|
self.apply_surface_materials();
|
||||||
.get::<RendererSurfaceStateUserData>()
|
}
|
||||||
.map(|surface_states| surface_states.borrow().buffer().is_some())
|
|
||||||
.unwrap_or(false)
|
pub fn update_textures(&self, renderer: &mut GlesRenderer) {
|
||||||
});
|
let Some(wl_surface) = self.wl_surface() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mapped = wl_surface
|
||||||
|
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
|
||||||
|
surface_states.lock().unwrap().buffer().is_some()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
if !mapped {
|
if !mapped {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mapped_data = self.mapped_data.lock();
|
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 Some(smithay_tex) = wl_surface
|
||||||
let sk_mat = self.sk_mat.get().unwrap();
|
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
|
||||||
unsafe {
|
surface_states
|
||||||
sk.tex_set_surface(
|
.lock()
|
||||||
sk_tex.as_ref(),
|
.unwrap()
|
||||||
smithay_tex.tex_id() as usize as *mut c_void,
|
.texture::<GlesRenderer>(renderer.id())
|
||||||
TextureType::IMAGE_NO_MIPS,
|
.cloned()
|
||||||
smithay::backend::renderer::gles::ffi::RGBA8.into(),
|
})
|
||||||
smithay_tex.width() as i32,
|
.flatten()
|
||||||
smithay_tex.height() as i32,
|
else {
|
||||||
1,
|
return;
|
||||||
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 Some(sk_tex) = self.sk_tex.get() else {
|
||||||
let new_mapped_data = CoreSurfaceData {
|
return;
|
||||||
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
|
};
|
||||||
wl_tex: Some(SendWrapper::new(smithay_tex)),
|
let Some(sk_mat) = self.sk_mat.get() else {
|
||||||
};
|
return;
|
||||||
*mapped_data = Some(new_mapped_data);
|
};
|
||||||
});
|
sk_tex
|
||||||
drop(mapped_data);
|
.lock()
|
||||||
if just_mapped {
|
.0
|
||||||
(self.on_mapped)();
|
.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);
|
||||||
}
|
}
|
||||||
self.apply_surface_materials();
|
|
||||||
|
let new_mapped_data = CoreSurfaceData {
|
||||||
|
wl_tex: Some(SendWrapper::new(smithay_tex)),
|
||||||
|
};
|
||||||
|
*mapped_data = Some(new_mapped_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
|
pub fn frame(&self, output: Output) {
|
||||||
let Some(wl_surface) = self.wl_surface() else { return };
|
let Some(wl_surface) = self.wl_surface() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
send_frames_surface_tree(
|
send_frames_surface_tree(
|
||||||
&wl_surface,
|
&wl_surface,
|
||||||
&output,
|
&output,
|
||||||
Duration::from_secs_f64(sk.time_get()),
|
Duration::from_secs_f64(Time::get_total_unscaled()),
|
||||||
None,
|
None,
|
||||||
|_, _| Some(output.clone()),
|
|_, _| Some(output.clone()),
|
||||||
);
|
);
|
||||||
@@ -190,41 +178,23 @@ impl CoreSurface {
|
|||||||
*self.material_offset.lock().value_mut() = material_offset;
|
*self.material_offset.lock().value_mut() = material_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_material(&self, model_node: &Arc<ModelPart>) {
|
pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
|
||||||
self.pending_material_applications.add_raw(model_node)
|
self.pending_material_applications.add_raw(model_part)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_surface_materials(&self) {
|
fn apply_surface_materials(&self) {
|
||||||
for model_node in self.pending_material_applications.get_valid_contents() {
|
if let Some(sk_mat) = self.sk_mat.get() {
|
||||||
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
|
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> {
|
pub fn wl_surface(&self) -> Option<WlSurface> {
|
||||||
self.weak_surface.upgrade().ok()
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flush_clients(&self) {
|
|
||||||
self.display
|
|
||||||
.upgrade()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.flush_clients()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Drop for CoreSurface {
|
impl Drop for CoreSurface {
|
||||||
fn drop(&mut self) {
|
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
Reference in New Issue
Block a user