226 Commits

Author SHA1 Message Date
Nova
0ae20b23c5 feat: initial openxr stuff
feat: openxr system


refactor: rename oxr_runtime to openxr_runtime


refactor: make openxr methods/signals snake case


feat(openxr): session

feat(openxr): action, action set

feat(openxr/action): suggested bindings
2023-07-31 23:44:48 -04:00
Nova
7282684104 feat: fd passing 2023-07-31 23:44:17 -04:00
Nova
dac3a3092a refactor(objects): overhaul input 2023-07-26 08:56:34 -04:00
Nova
4ffab6580d refactor (wayland): move seat to client 2023-07-25 14:46:03 -04:00
Nova
1047f5242b refactor: trait away panel item backends 2023-07-23 19:59:35 -04:00
Nova
eedf5446e8 feat(xwayland): xwayland feature 2023-07-23 09:04:22 -04:00
Nova
824b1bdd26 feat: todo fuure plans 2023-07-23 08:30:09 -04:00
Nova
814d631bb8 fix: deadlock on close stereokit 2023-07-23 08:30:09 -04:00
Nova
fe28d6670e refactor(wayland): less crashy 2023-07-23 08:30:09 -04:00
Nova
37413a3e74 feat(xwayland): x window capabilities 2023-07-23 08:30:09 -04:00
Nova
8ad5fc6584 feat(xwayland): first x window 2023-07-23 08:30:09 -04:00
Nova
06dfba48d7 feat(xwayland): serialize start 2023-07-23 08:30:09 -04:00
Nova
f7ad007d54 refactor(wayland): separate backend from panel item 2023-07-23 08:30:09 -04:00
Nova
7a556439ca feat(wayland): initial xwayland support 2023-07-23 08:30:09 -04:00
Nova
945514aa31 fix: data uses flexbuffer type instead of value for mask map 2023-07-23 08:29:53 -04:00
Nova
3b557cd851 fix: wayland stability 2023-07-23 08:29:04 -04:00
Nova
95ecef3426 feat: version bump 2023-07-22 18:31:09 -04:00
Nova
a85504a71a fix: states 2023-07-19 06:04:15 -07:00
Nova
eb2ace5d29 fix: surface not mapping 2023-07-19 06:04:08 -07:00
Nova
1923b71985 feat: formatting 2023-07-19 06:03:28 -07:00
Nova
a87823dcc8 feat: play space 2023-07-16 10:42:35 -07:00
technobaboo
1c0290aff5 feat: make readme more readable 2023-07-11 11:44:38 -07:00
technobaboo
e131b2b00f fix(ci): add semicolons 2023-07-11 11:16:15 -07:00
technobaboo
728573191f fix(ci): appimagetool 2023-07-11 11:11:07 -07:00
technobaboo
cee1137269 fix(ci): xcb glx 2023-07-11 10:59:26 -07:00
technobaboo
c6af3298a2 fix(ci): ninja-build instead of ninja 2023-07-11 10:55:08 -07:00
technobaboo
11074c9d05 feat: ci take 2 2023-07-11 10:53:31 -07:00
Nova
aa6ec1afd8 refactor: use dmabuf v4 instead of bind_display 2023-06-27 05:53:45 -04:00
Nova
a524f2020b refactor: disable shader injection 2023-06-26 20:49:21 -04:00
Nova
e2ce337036 feat: match stereokit to log level 2023-06-26 20:43:02 -04:00
Nova
34c57254a9 fix: unwrap in main fn 2023-06-26 20:33:04 -04:00
Nova
d7bf01c417 feat: hardware accelerated wayland apps 2023-06-26 20:31:38 -04:00
Nova
59d049e7bd feat: proper dmabuf import 2023-06-26 20:09:20 -04:00
Nova
c05921fd71 feat: shaders!! working!! 2023-06-26 19:49:10 -04:00
Nova
5cbd4d807f feat: it borken 2023-06-26 04:37:38 -04:00
Nova
23fe6ead25 feat: glsl simula text shaders 2023-06-25 10:05:18 -04:00
Nova
b3247f5ba8 fix: ctrl+c in tty 2023-06-21 01:47:10 -04:00
Nova
269e5afcc9 fix: mouse pointer keyboard ray direction 2023-06-14 23:00:17 -04:00
Nova
20070eb778 feat: version bump 2023-06-11 01:37:21 -04:00
Nova
c8206b14dd feat: spatial bounds 2023-06-11 00:38:05 -04:00
Nova
e9089e83a9 feat(input): custom pointers 2023-05-31 08:50:15 -04:00
Nova
34faa98dc8 fix(node): better send remote signal 2023-05-31 08:48:59 -04:00
Nova
187d6ee7d1 fix(pointer): proper direction 2023-05-31 08:48:24 -04:00
Nova
f7a9976760 fix(main): make eye pointer not work in flatscreen 2023-05-31 08:47:16 -04:00
Nova
f42c8963e7 fix(scenegraph): recurse through aliases 2023-05-31 08:47:02 -04:00
Nova
f17b4c8ee2 feat: readd dmabufs 2023-05-30 02:20:27 -04:00
Nova
47410fe63a fix: janky dmabuf hack 2023-05-27 09:48:34 -04:00
Nova
04d0a77093 feat: eye gaze support 2023-05-23 18:56:46 -04:00
matthewcroughan
97c2b82fa7 ci: print flatland revision for gnome-graphical-test in Discord message 2023-05-20 11:08:43 +01:00
matthewcroughan
343962fd6f github: remove workflows
Since Hercules CI is in use now, GitHub Actions are not necessarily required
2023-05-20 11:08:43 +01:00
matthewcroughan
2c07278c72 flake: add hercules-ci
This commit also adds a HCI Effect for posting to Discord the result of the gnome-graphical-test on every single commit
2023-05-20 11:08:43 +01:00
matthewcroughan
bdadc527d2 nix: add graphical-gnome-test
This VM integration test spawns Gnome, monado-service,
stardust-xr-server, flatland and weston-cliptest and tests that the
functionality works correctly. The result is a screenshot. If any
program in the test produces an exit code above 0 it will fail the test,
graphical rendering bugs should be visible in the resulting screenshot
2023-05-20 11:08:43 +01:00
matthewcroughan
7045904693 flake.lock: Update
Flake lock file updates:

• Updated input 'fenix':
    'github:nix-community/fenix/3a0b59a2ea946a533c62ac417596835779087f0e' (2023-04-20)
  → 'github:nix-community/fenix/5816c7bbcc385d2e65877631497df3f7d66b354a' (2023-05-11)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/2400b36a2ed40f68a26473f69ac208ba10d98af9' (2023-04-19)
  → 'github:rust-lang/rust-analyzer/b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf' (2023-05-09)
• Added input 'flatland':
    'github:StardustXR/flatland/24613a496841bdf38e5f136608d5295860a75fce' (2023-05-11)
• Added input 'flatland/fenix':
    'github:nix-community/fenix/ee59e1c769657b1e27e608f8b981fa8f6b715583' (2023-03-14)
• Added input 'flatland/fenix/nixpkgs':
    follows 'flatland/nixpkgs'
• Added input 'flatland/fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/95497533524537b1cc7a2870ce94b0b14503be8b' (2023-03-13)
• Added input 'flatland/nixpkgs':
    'github:NixOS/nixpkgs/67f26c1cfc5d5783628231e776a81c1ade623e0b' (2023-03-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/da45bf6ec7bbcc5d1e14d3795c025199f28e0de0' (2023-04-30)
  → 'github:NixOS/nixpkgs/897876e4c484f1e8f92009fd11b7d988a121a4e7' (2023-05-06)
2023-05-20 11:08:43 +01:00
matthewcroughan
c45dc36310 flake: filter nix code and README out of src
This means that changing the Nix code doesn't cause the Rust code to need to be recompiled when using nix build
2023-05-20 11:08:43 +01:00
Nova
ed724dcb0f feat: appimage support!! 2023-05-19 18:12:22 -04:00
Nova
c4234becb9 feat: clean up main function 2023-05-19 18:11:52 -04:00
Nova
cac2be7a4f feat: flat wayland display env var 2023-05-18 06:00:53 -04:00
Nova
84ba4b47ec fix: better pointer compare distance algorithm 2023-05-10 23:44:36 -04:00
Nova
aea681b5c6 fix(model): copy on create to make unique 2023-05-10 23:44:23 -04:00
Nova
7c17dbddb2 feat: instant model loading 2023-05-10 20:10:31 -04:00
Nova
3f56d1e029 fix(wayland): stop crash 2023-05-10 19:14:57 -04:00
Nova
2a145de758 fix: remove dbg statement 2023-05-10 19:02:09 -04:00
Nova
54ad2667c3 fix: upgrade smithay 2023-05-10 16:51:39 -04:00
Nova
fa6ea5adc5 fix: model nodes 2023-05-10 16:49:56 -04:00
Nova
ee5a226c5e feat: model nodes 2023-05-10 08:38:21 -04:00
Matthew Croughan
3e0860073d fix: nix flake smithay lock issues
* flake: use allowBuiltInFetchGit to prevent narHash reproducibility issues

using the builtin fetcher allows fetching git dependencies with only the ref and without storing the narHash for the fixed-output-derivation

* gitignore: add nix result symlinks
2023-05-06 17:37:46 +00:00
Nova
ec7f9a1681 feat: order inputs 2023-05-02 21:58:53 -04:00
Astavie
779431be04 fix: nix overlay
* nix flake

* workflow

* remove flake-utils

* update flake

* fix

* remove cargo hash

* fix overlay
2023-05-01 23:29:34 +00:00
Astavie
683b0c20a1 fix: nix flake
* nix flake

* workflow

* remove flake-utils

* update flake

* fix

* remove cargo hash
2023-05-01 21:56:49 +00:00
Nova King
341da038ea feat: FUNDING.yml 2023-05-01 17:31:03 +00:00
Nova
367066381d better panel item startup settings order 2023-05-01 12:59:49 -04:00
Nova
2f5760c902 fix: launch env vars to launch as much stuff in wayland as possible 2023-05-01 00:05:00 -04:00
Nova
9072c34d50 fix(objects/hand): hand enabled when controller not 2023-04-30 18:28:40 -04:00
Nova
aefa6dc62f feat: new stereokit 2023-04-30 13:25:13 -04:00
Saphira Kai
35f42559ac remove broken Debug derivation for XdgSurfaceData 2023-04-24 13:12:44 -03:00
Nova
63ead46a2c feat(startup): get environment 2023-04-24 09:53:20 -04:00
Nova
5682718713 feat: cargo lock update 2023-04-24 08:31:28 -04:00
Nova
58b9ee0f01 feat: dependency updates 2023-04-24 08:31:07 -04:00
Nova
57039a3ccb feat(wayland): popups, more compatibility, more stability
get_parent


grab


popups

fix head thingy


popup list


feat: remove set_active

feat(wayland): commit_popup

feat(wayland): cleanup


moar changess


actually fix the problem with everything oh my god


proper popup state


fix: multi thread event loop


fix: match popup surface ID


make wayland input system go over surfaces instead of toplevels


feat: massive refactor of all wayland things
2023-04-24 06:30:39 -04:00
Nova
0fec416731 fix: mouse pointer 2023-04-23 09:34:43 -04:00
Nova
f2c9f91a6b feat: custom startup script 2023-04-23 09:34:43 -04:00
Astavie
aef78f12b5 fix: nix flake
* nix flake

* workflow

* remove flake-utils

* update flake
2023-04-20 10:35:50 +00:00
Nova
c19a5bcd91 feat: desktop file 2023-03-25 03:06:50 -04:00
Nova
de3ada1b5d feat: upgrade stereokit-rs 2023-03-23 14:12:48 -04:00
Astavie
d95b3f55e5 feat: nix support & github workflow
* nix flake

* workflow

* remove flake-utils
2023-03-14 19:35:40 +00:00
Nova
643c4959fb fix: unignore cargo.lock 2023-03-13 13:44:12 -07:00
Nova
1b055c9406 fix(input/hand): correct serialization transform matrix order 2023-03-08 01:45:03 -05:00
Nova
42782d0f87 feat(input): new system 2023-02-25 16:39:30 -05:00
Nova
b5fc321e36 refactor(input): make all inputs have nodes 2023-02-24 11:43:06 -05:00
Nova
931f857458 feat: update everything, clean dependencies 2023-02-23 08:41:40 -05:00
Nova
8ecdd3acc6 refactor(model): remove shader use 2023-02-23 07:17:36 -05:00
Nova
951ca7c4db fix(model): make default pbr shader clip 2023-02-20 18:03:02 -05:00
Nova
fee12854ca fix(items): proper drop 2023-02-20 10:24:59 -05:00
Nova
14b9129fca fix(mouse pointer): keyboard 2023-02-18 02:06:17 -05:00
Nova
55ac288391 fix(cargo.toml): upgrade stereokit 2023-02-17 13:10:23 -05:00
Nova
0c15f54bdc fix(mouse pointer): proper pointer transform 2023-02-17 13:10:08 -05:00
Nova
c42f69cb69 fix: order of operations on wayland material properties 2023-02-16 14:03:13 -05:00
Nova
7818ae4add fix(item): send acceptors to new item ui 2023-02-16 14:02:43 -05:00
Nova
c09d565dc5 fix: update stereokit 2023-02-16 14:02:21 -05:00
Nova
9076a0b897 feat: disabled/enabled 2023-02-16 00:30:25 -05:00
Nova
37f283b393 fix: node aspect drawable 2023-02-09 03:58:56 -05:00
Nova
40e7c6cd20 refactor: node aspect drawable 2023-02-08 21:06:24 -05:00
Nova
4620e0ca09 fix: cargo fmt 2023-02-07 18:04:20 -05:00
Nova
33d12787c9 fix(dev profile): optimization level 0 2023-02-07 16:15:37 -05:00
Nova
d7b895f0f4 feat(model): pbr clip shader 2023-02-07 16:15:20 -05:00
awtterpip
fb027de390 fixed bug where sound wasn't stoppable 2023-02-07 10:34:10 -06:00
awtterpip
19c0ec42ac gave audio interface proper name 2023-02-07 09:13:55 -06:00
piper
baea2c71a3 feat: audio! 2023-02-04 20:39:08 -05:00
Nova
0694848c9b fix(stereokit): version 2023-02-04 20:38:10 -05:00
Nova
fd95988a42 refactor(cargo.toml): profile optimizations 2023-02-02 16:32:43 -05:00
Nova
24d8fefcaa feat(spatial): fields_distance, normal, closest_point 2023-02-01 19:21:35 -05:00
Nova
a434c2aa55 fix(items): make acceptor fields non-optional 2023-01-28 11:12:51 -05:00
Nova
c46e01e280 refactor: change logic_step to frame event 2023-01-26 09:08:40 -05:00
Nova
529b86fa91 refactor: remove many unwrap calls 2023-01-25 11:50:53 -05:00
Nova
b5e87d5911 feat(material): auto copy on change parameter 2023-01-25 09:23:01 -05:00
Nova
063be773e0 feat: make event loop multithreaded 2023-01-25 09:17:52 -05:00
Nova
3dadbccfff fix(spatial): moving object relative to itself 2023-01-25 05:40:37 -05:00
Nova
cbf5af88b9 fix(spatial): parse_transform returned result 2023-01-25 05:40:19 -05:00
Nova
f46e870864 refactor(wayland): remove SeatData wrapper 2023-01-22 02:38:40 -05:00
Nova
2358ee899a fix(wayland): update pointer scroll 2023-01-22 02:30:12 -05:00
Nova
4f50772b92 fix(panel_item): allow surfaces with size of 0,0 2023-01-22 00:58:25 -05:00
Nova
c52472fe3c refactor(spatial): get/set parent methods 2023-01-22 00:42:04 -05:00
Nova
52011bd92f fix(spatial): reference space can be self 2023-01-22 00:24:38 -05:00
Nova
be97de8a2e feat: update/clean dependencies 2023-01-22 00:24:13 -05:00
Nova
e9e4d3599d feat: input multiplexing 2023-01-21 18:07:25 -05:00
Nova
6d7d7be3f1 feat: startup script 2023-01-17 18:51:46 -05:00
Nova
d5a951d3c6 feat(spatial): keep track of children 2023-01-16 11:17:12 -05:00
Nova
1b0ef4b7df refactor(main): make arrays tuples 2023-01-16 11:03:46 -05:00
Nova
66879148c6 feat: update stereokit to have fancy tracing 2023-01-15 04:23:26 -05:00
Nova
7108cfa365 fix(input): grab issues 2023-01-15 04:04:09 -05:00
Nova
05e60c43ce feat(stereokit): log filtering 2023-01-15 04:03:21 -05:00
Nova
75359427c9 feat: even more tracing 2023-01-15 01:13:22 -05:00
Nova
ba938dfaf9 feat: adaptive sleep delay 2023-01-14 23:48:49 -05:00
Nova
c3721872e0 feat: spatial tracing 2023-01-14 22:59:00 -05:00
Nova
73f3d99877 feat: span tracing!!! 2023-01-14 22:32:41 -05:00
Nova
1a5edee751 refactor(input): more compact registry contains 2023-01-14 20:29:33 -05:00
Nova
27d75d3098 fix(event loop, client): better async 2023-01-14 12:38:05 -05:00
Nova
198b342dec refactor(cargo.toml): remove unneeded deps and features 2023-01-14 11:17:52 -05:00
Nova
457ff55904 fix(input): reduce latency by several frames 2023-01-14 11:03:47 -05:00
Nova
795780130c feat(tokio): profiling 2023-01-14 10:38:39 -05:00
Nova
7a69d64e46 refactor(wayland): remove commented out code\ 2023-01-07 10:15:56 -05:00
Nova
5350adef98 feat(model): use resource ID for texture 2023-01-06 09:05:30 -05:00
Nova
1ae7e4afaa fix: remove stereokit patch 2023-01-05 21:49:03 -05:00
Nova
e0e5f3b3bc feat(model): set material parameter 2023-01-05 21:46:25 -05:00
Nova
d1522a05e8 fix(drawable/lines): properly make cyclic point 2023-01-05 08:28:29 -05:00
Nova
4f1d03196a fix: remove opt level 3 for dev 2023-01-05 07:59:20 -05:00
Nova
3cf65e5c7b feat: update stereokit 2023-01-04 23:51:48 -05:00
Nova
3f08e1e212 feat(wayland/surface): geometry resizing, unused 2023-01-04 21:36:55 -05:00
Nova
a0d97c8385 feat(delta): mark_changed 2023-01-04 21:36:25 -05:00
Nova
95629422a7 fix(wayland): SSD all the things 2023-01-04 08:26:10 -05:00
Nova
46b3d21fca fix(wayland): toplevel states bytemucked to u8 2023-01-04 08:25:54 -05:00
Nova
2ce9bf8cd4 feat(wayland): proper surface geometry 2023-01-04 07:25:33 -05:00
Nova
1f745fddde feat(wayland): serial counter 2023-01-04 07:25:22 -05:00
Nova
45998df389 feat(delta): const 2023-01-04 07:23:23 -05:00
Nova
fe22451be5 feat(wayland): set toplevel capabilities 2023-01-03 10:08:39 -05:00
Nova
57ea51fb40 feat(wayland): recommended_state 2023-01-02 18:53:58 -05:00
Nova
2d0865e73c refactor(wayland): remove xdg output manager 2023-01-02 03:49:29 -05:00
Nova
8556046b25 refactor(wayland): comment out xdg activation protocol 2023-01-02 03:43:50 -05:00
Nova
c0c0dddf23 fix(wayland): drop panel item correctly 2023-01-01 14:37:11 -05:00
Nova
c2665938d9 feat(wayland): switch pointer focus dynamically 2023-01-01 14:36:46 -05:00
Nova
6b7a074fea fix(wayland): set seat cursor 2023-01-01 14:34:18 -05:00
Nova
bdd5cbe8b0 fix(wayland): xdg surface size when not set 2023-01-01 14:33:07 -05:00
Nova
6068ad5089 feat(wayland/xdg_shell): set surface states None 2022-12-26 11:15:37 -05:00
Nova
2de49d981b feat(wayland): make state fields optional 2022-12-26 08:22:09 -05:00
Nova
246cf5ceea fix(wayland): panel item configure toplevel 2022-12-25 16:19:22 -05:00
Nova
17583471bf feat(wayland): configure and commit for toplevel] 2022-12-25 16:01:23 -05:00
Nova
868621a68c feat(input): allow scaling input handlers 2022-12-21 05:34:34 -05:00
Nova
769fa24c1d feat: update stardust-xr 2022-12-17 02:29:32 -05:00
Nova
89f4aae1ff refactor(data): identical values for mask 2022-12-17 02:28:06 -05:00
Nova
aa79cb2147 refactor(startup): auto acceptor on item add to 2022-12-10 10:26:26 -05:00
Nova
0cb039c1be refactor(startup): generate startup token 2022-12-10 09:46:47 -05:00
Nova
697320d1c0 refactor(startup): rename to STARTUP_SETTINGS 2022-12-10 09:27:37 -05:00
Nova
9f5ad2b9af feat(startup): automatic acceptors 2022-12-10 09:18:02 -05:00
Nova
2315aca8b5 feat(startup settings): use /proc/{pid}/environ 2022-12-10 09:17:37 -05:00
Nova
004454d75e fix(wayland): dmabuf formats 2022-12-10 00:30:53 -05:00
Nova
4286bc119a refactor(wayland): remove manual dmabuf importing 2022-12-09 07:04:50 -05:00
Nova
e7f7f1f062 fix: drain all dmabufs 2022-12-08 05:40:54 -05:00
Nova
226d674365 feat: optimization level 3 for debug 2022-12-07 14:47:25 -05:00
Nova
0dc9dc23e9 feat(wayland): dmabuf 2022-12-07 14:30:48 -05:00
Nova
48719c9862 feat(resources): list of extensions to check 2022-12-05 22:44:04 -05:00
Nova
125ab4f74d refactor(sk_hand): use snake case for datamap keys 2022-12-03 17:18:27 -05:00
Nova
42d2a41d57 feat: tracing 2022-12-02 20:46:28 -05:00
Nova
692ee95500 switch to color_eyre instead of anyhow 2022-12-02 13:58:54 -05:00
Nova
9ec6d31d67 fix(input): O(n log n) instead of O(n^2) 2022-12-02 11:09:23 -05:00
Nova
e30ad3957a fix(items): give aliases a proper lifetime 2022-11-30 22:34:16 -05:00
Nova
35e6e3f1f9 fix(alias): make output optional 2022-11-30 07:04:06 -05:00
Nova
597cadd9f2 fix(lines): use sRGB colors 2022-11-28 00:32:41 -05:00
Nova
358aaa917a fix(lines): convert f32 to u8 colors correctly 2022-11-26 15:22:23 -05:00
Nova
6ef154e9b3 fix(lines): accept f32 colors 2022-11-26 00:43:04 -05:00
Nova
5874eaef78 fix: text not working 2022-11-26 00:34:37 -05:00
Nova
5b15df42c3 fix(wayland): update smithay version 2022-11-25 23:58:22 -05:00
Nova
c10735acd1 feat(fields): torus field 2022-11-24 18:57:11 -05:00
Nova
2cb000c445 refactor(fields): use let else for getting field 2022-11-24 18:56:59 -05:00
Nova
77bc08a78b feat(drawable): lines 2022-11-21 16:39:28 -05:00
Nova
fe4930e919 refactor(items): move capture to item acceptor 2022-11-20 13:34:15 -05:00
Nova
d3653e17cb feat(items): acceptor 2022-11-19 11:25:10 -05:00
Nova
418e9fdc54 fix(wayland): cursor material queue higher 2022-11-14 11:53:08 -05:00
Nova
bd62e7d342 refactor(stereokit): upgrade version 2022-11-14 09:05:36 -05:00
Nova
9da111a3e0 refactor(wayland): make code cleaner 2022-11-12 11:53:11 -05:00
Nova
b278a43d6d fix: upgrade rust version 2022-11-11 13:25:57 -05:00
Nova
892a4165f8 fix(wayland): pointer_motion works when inactive 2022-11-11 13:25:48 -05:00
Nova
088e8e2c9c fix(wayland): remove unwraps 2022-11-11 12:52:51 -05:00
Nova
043a1a1b29 refactor(wayland): s/ObjectId/Weak<WlSurface>/ 2022-11-09 13:05:21 -05:00
Nova
41e76c9d0a feat: wayland feature 2022-11-09 11:13:07 -05:00
Nova
6b578fe044 fix(wayland): account for surface data map panic 2022-11-09 11:02:48 -05:00
Nova
e2b00f23ee fix(panel item): set cursor full of snake 2022-11-08 21:16:03 -05:00
Nova
215b91a4bd refactor(items): genericize item acceptors/ui 2022-11-08 20:25:43 -05:00
Nova
9e0ba504ac fix: make aliased signals snake case 2022-11-08 20:17:15 -05:00
Nova
3cdb6da09c refactor: use snake case for method names 2022-11-08 06:10:03 -05:00
Nova
3821e14fbd fix(wayland): upgrade smithay version 2022-11-06 16:49:30 -05:00
Nova
e3e7714f1a feat(objects/input): add keyboard to mouse_pointer 2022-11-05 17:56:52 -04:00
Nova
cdbfc27792 refactor(data): simplify 2022-11-05 17:56:34 -04:00
Nova
cbb09a9d97 refactor: remove item alias remote_methods 2022-11-05 17:56:09 -04:00
Nova
12c2443e5d feat(field): ray_march 2022-11-05 17:55:27 -04:00
Nova
8b38f61e35 feat(field): expose ray marching to clients 2022-11-01 07:59:49 -04:00
Nova
fad9b7d50c refactor(client): use new messenger 2022-10-30 00:14:24 -04:00
Nova
60f642364f refactor(node): return Result<&T> from get aspect 2022-10-29 07:32:51 -04:00
Nova
454318b0af feat: terrible hack for moses 2022-10-26 05:19:20 -04:00
Nova
4e858e4212 fix(wayland): remove wayland crate pinning 2022-10-25 16:21:22 -04:00
Nova
e4d0159572 refactor: use master smithay branch 2022-10-25 16:07:36 -04:00
Nova
8ea05e998d fix(wayland): set default output size 2022-10-25 12:56:59 -04:00
Nova
af3833b263 fix(data): send "data" to receiver 2022-10-25 07:32:25 -04:00
Nova
94a78314bb feat: pulse sender/receiver 2022-10-21 06:21:56 -04:00
Nova
a572b215b9 fix: clippy 2022-10-21 06:21:49 -04:00
Nova
b189a6a73c feat: zones 2022-10-20 11:32:33 -04:00
Nova King
44b91bf0e8 Merge pull request #2 from philpax/fix-build
fix: remove path dependency for stereokit
2022-10-19 05:55:53 +00:00
88 changed files with 11419 additions and 2793 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [technobaboo]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

46
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Build
on:
push:
branches:
- '*'
jobs:
build_and_package:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install runtime dependencies
run: sudo apt install -y --no-install-recommends libxkbcommon-dev libstdc++6 libopenxr-dev libx11-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libfontconfig-dev libjsoncpp-dev libxcb1-dev libglx-dev libxcb-glx0-dev libdrm-dev libwayland-dev libfreetype-dev libpng-dev
- name: Install build dependencies
run: sudo apt install -y --no-install-recommends cmake ninja-build
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build server
run: cargo build --release
- name: Install appimagetool
run: |
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O /usr/local/bin/appimagetool; \
chmod +x /usr/local/bin/appimagetool; \
sed -i 's|AI\x02|\x00\x00\x00|' /usr/local/bin/appimagetool
- name: Install cargo-appimage
run: cargo install cargo-appimage
- name: Generate AppImage
run: cargo appimage
- name: Upload AppImage
uses: actions/upload-artifact@v2
with:
name: appimage
path: '*.AppImage'

11
.gitignore vendored
View File

@@ -2,11 +2,14 @@
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
.vscode/
# Ignore build results from Nix
*result*
/libs/
*.AppImage
*.blend1

2870
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,94 @@
[package]
edition = "2018"
edition = "2021"
name = "stardust-xr-server"
version = "0.10.1"
version = "0.42.1"
authors = ["Nova King <technobaboo@proton.me>"]
description = "Stardust XR reference display server"
license = "GPLv2"
repository = "https://github.com/StardustXR/stardust-xr-server/"
homepage = "https://stardustxr.org"
[[bin]]
name = "stardust-xr-server"
path = "src/main.rs"
[features]
default = ["wayland", "xwayland"]
openxr_runtime = []
wayland = ["dep:smithay", "dep:xkbcommon"]
xwayland = ["smithay/xwayland"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-chrome"]
[package.metadata.appimage]
auto_link = true
auto_link_exclude_list = [
"libc*",
"libdl*",
"libpthread*",
"ld-linux*",
"libGL*",
"libEGL*",
]
[profile.dev.package."*"]
opt-level = 3
[profile.release]
strip = true
lto = true
[dependencies]
anyhow = "1.0.57"
clap = { version = "4.0.8", features = ["derive"] }
ctrlc = "3.2.2"
dashmap = "5.3.4"
flatbuffers = "2.1.2"
flexbuffers = "2.0.0"
glam = {version = "0.21.3", features = ["mint"]}
color-eyre = { version = "0.6.2", default-features = false }
clap = { version = "4.2.4", features = ["derive"] }
dashmap = "5.4.0"
glam = { version = "0.23.0", features = ["mint"] }
lazy_static = "1.4.0"
mint = "0.5.9"
nanoid = "0.4.0"
once_cell = "1.12.0"
once_cell = "1.17.1"
parking_lot = "0.12.1"
portable-atomic = {version = "0.3.0", features = ["float", "std"]}
rccell = "0.1.3"
portable-atomic = { version = "1.2.0", features = ["float", "std"] }
rustc-hash = "1.1.0"
slab = "0.4.6"
tokio = { version = "1", features = ["full"] }
thiserror = "1.0.31"
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal"] }
send_wrapper = "0.6.0"
prisma = "0.1.1"
slog = "2.7.0"
slog-stdlog = "4.1.1"
xkbcommon = { version = "0.5.0", default-features = false }
stardust-xr = "0.5.2"
wayland-backend = "=0.1.0-beta.9"
wayland-scanner = "=0.30.0-beta.9"
directories = "4.0.1"
serde = { version = "1.0.145", features = ["derive"] }
xkbcommon = { version = "0.5.0", default-features = false, optional = true }
stardust-xr = "0.13.0"
directories = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
global_counter = "0.2.2"
rand = "0.8.5"
atty = "0.2.14"
[dependencies.stereokit]
default-features = false
features = ["linux-egl"]
version = "0.5.0"
version = "0.16.9"
[dependencies.smithay]
git = "https://github.com/technobaboo/smithay.git"
branch = "feature/public_input"
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
# path = "../smithay"
default-features = false
features = ["desktop", "renderer_gl", "wayland_frontend"]
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
version = "*"
optional = true
[dependencies.console-subscriber]
version = "0.1.8"
optional = true
[dependencies.tracing-chrome]
version = "0.7.1"
optional = true
# [patch.crates-io.stereokit]
# path = "../stereokit-rs"
# [patch.crates-io.stereokit-sys]
# path = "../stereokit-sys"
# [patch.crates-io.stardust-xr]
# path = "../core/core"
# [patch.crates-io.stardust-xr-schemas]
# path = "../core/schemas"

View File

@@ -15,8 +15,71 @@ This project is a usable Linux display server that reinvents human-computer inte
```bash
cargo build
```
The latest stable server is automatically built to an appimage at https://github.com/StardustXR/server/releases for easy testing.
## Install
```bash
cargo install
```
## Usage
First, try running `cargo run` in a terminal window. If a headset is plugged in and OpenXR is working no window will show up. However, the headset should show the same things as the window that opens:
![A pitch black void with a single bleach white hand in the middle](/img/xr_mode_windowed_blank.png)
Stardust won't do anything interesting without clients! Try some from https://github.com/StardustXR.
### Default Sky
You can set a default skytex/skylight by putting your favorite HDRI equirectangular sky in `~/.config/stardust/skytex.hdr`. Certain clients can override this.
Flatscreen mode when the default skybox is [Zhengyang Gate](https://polyhaven.com/a/zhengyang_gate):
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_3.png)
### Windowed Mode
If the stardust server can't connect to an OpenXR runtime or you force it into flatscreen mode with `-f`, the server will show in a window.
![A black void representing Stardust in XR mode with a hand skeleton in the middle](/img/flatscreen_2.png)
You can navigate around by right click + dragging to look around, Shift+W/A/S/D/Q/E to move. If you have a virtual hand, left click pinches, right click points, both make a fist.
### Flags
#### Flatscreen (-f)
The server will show up in windowed mode no matter what with your mouse pointer being turned into a 3D pointer. Keyboard input will be sent to whatever your mouse is hovering over like visionOS simulator.
Flatscreen mode upon initial startup:
![A pitch black window representing Stardust in flatscreen mode](/img/flatscreen_1.png)
#### Overlay (-o \<PRIORITY>)
The server will, if in XR mode, be overlaid using the OpenXR overlay extension with the given priority.
#### Disable controller (--disable-controller)
Some runtimes such as Monado may emulate a controller using a hand, and this messes with Stardust's input system. Set this flag to ignore the controllers that the OpenXR runtime provides.
#### Execute (-e </path/to/executable>)
When wayland and OpenXR and such are initialized, run the given executable (such as a bash script) with all the environment variables needed to connect all clients of any type to the server. If not set, the server will run the executable at `~/.config/stardust/startup` if it exists. This is how stardust desktop environments can be made.
#### Help (-h, --help)
help
## Test
##### Gnome Graphical Integration Test
- `nix build .#gnome-graphical-test`
This test uses Nix to reproducibly execute a QEMU virtual machine which
spawns a full Gnome desktop. It runs `monado-service`, `stardust-xr-server`
`flatland` underneath of Gnome and then attaches `weston-cliptest` to the
`flatland` process running underneath of `stardust-xr-server`, the result is
a screenshot in PNG format that should look like expected. If any process in
this test produces an exit code above 0, the test will fail, graphical bugs
should be visible in the screenshot. An example of the result is below.
###### Result
![image](https://github.com/StardustXR/server/assets/26458780/e21cd039-2528-4568-b20a-ce4abfab6d9b)
##### Everything
`nix flake check` will build every test underneath of the `checks` attribute in the `flake.nix`

405
flake.lock generated Normal file
View File

@@ -0,0 +1,405 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1683786056,
"narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=",
"owner": "nix-community",
"repo": "fenix",
"rev": "5816c7bbcc385d2e65877631497df3f7d66b354a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"fenix_2": {
"inputs": {
"nixpkgs": [
"flatland",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src_2"
},
"locked": {
"lastModified": 1678775037,
"narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1678379998,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1678379998,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flatland": {
"inputs": {
"fenix": "fenix_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1683766358,
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=",
"owner": "StardustXR",
"repo": "flatland",
"rev": "24613a496841bdf38e5f136608d5295860a75fce",
"type": "github"
},
"original": {
"owner": "StardustXR",
"repo": "flatland",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"pre-commit-hooks-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"haskell-flake": {
"locked": {
"lastModified": 1678138103,
"narHash": "sha256-D0lao82bV3t2gEFjHiU6RN233t+1MnkQV+bq8MEu2ic=",
"owner": "hercules-ci",
"repo": "haskell-flake",
"rev": "1e1660e6dd00838ba73bc7952e6e73be67da18d1",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"ref": "0.1-extraLibraries",
"repo": "haskell-flake",
"type": "github"
}
},
"hercules-ci-agent": {
"inputs": {
"flake-parts": "flake-parts_2",
"haskell-flake": "haskell-flake",
"nix-darwin": "nix-darwin",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
},
"locked": {
"lastModified": 1678446614,
"narHash": "sha256-Z6Gsba5ahn/N0QlF0vJfIEfnZgCs4qr1IZtXAqjbE7s=",
"owner": "hercules-ci",
"repo": "hercules-ci-agent",
"rev": "0b90d1a87c117a5861785cb85833dd1c9df0b6ef",
"type": "github"
},
"original": {
"id": "hercules-ci-agent",
"type": "indirect"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts",
"hercules-ci-agent": "hercules-ci-agent",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1681898675,
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1673295039,
"narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
"type": "github"
},
"original": {
"owner": "LnL7",
"repo": "nix-darwin",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1678703398,
"narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1678375444,
"narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1673800717,
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1678293141,
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1678891326,
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1683408522,
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1678376203,
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flatland": "flatland",
"hercules-ci-effects": "hercules-ci-effects",
"nixpkgs": "nixpkgs_4"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1683653808,
"narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-analyzer-src_2": {
"flake": false,
"locked": {
"lastModified": 1678695923,
"narHash": "sha256-rDhiiU8P6sf6mgj5IKgCuTRN9uYeqWr6xl4XLkKnMWg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "95497533524537b1cc7a2870ce94b0b14503be8b",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

133
flake.nix Normal file
View File

@@ -0,0 +1,133 @@
{
nixConfig = {
extra-substituters = [ "https://stardustxr.cachix.org" ];
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ];
};
# 22.11 does not include PR #218472, hence we use the unstable version
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
# Since we do not have a monorepo, we have to fetch Flatland in order to use
# it to create VM Tests
inputs.flatland.url = "github:StardustXR/flatland";
inputs.fenix.url = github:nix-community/fenix;
inputs.fenix.inputs.nixpkgs.follows = "nixpkgs";
inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }:
let
name = "server";
pkgs = system: import nixpkgs {
inherit system;
};
shell = pkgs: pkgs.mkShell {
inputsFrom = [ self.packages.${pkgs.system}.default ];
# ---- START package specific dev settings ----
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific dev settings ----
};
package = pkgs:
let
toolchain = fenix.packages.${pkgs.system}.minimal.toolchain;
in
(pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
}).buildRustPackage rec {
pname = "stardust-xr-${name}";
src = builtins.path {
name = "stardust-xr-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
};
# ---- START package specific settings ----
version = "0.10.2";
cargoLock = {
lockFile = ./Cargo.lock;
allowBuiltinFetchGit = true;
};
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
mkdir -p $sk/build/cpm
cp ${pkgs.fetchurl {
url = "https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.32.2/CPM.cmake";
hash = "sha256-yDHlpqmpAE8CWiwJRoWyaqbuBAg0090G8WJIC2KLHp8=";
}} $sk/build/cpm/CPM_0.32.2.cmake
'';
CPM_SOURCE_CACHE = "./build";
nativeBuildInputs = with pkgs; [
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = with pkgs; [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific settings ----
};
in
{
overlays.default = final: prev: {
stardust-xr = (prev.stardust-xr or {}) // {
${name} = package final;
};
};
packages."x86_64-linux".default = package (pkgs "x86_64-linux");
packages."aarch64-linux".default = package (pkgs "aarch64-linux");
packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test;
packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test;
checks."x86_64-linux".gnome-graphical-test = (pkgs "x86_64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "x86_64-linux"); inherit self; });
checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; });
devShells."x86_64-linux".default = shell (pkgs "x86_64-linux");
devShells."aarch64-linux".default = shell (pkgs "aarch64-linux");
herculesCI.ciSystems = [ "x86_64-linux" ];
effects = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
in { branch, rev, ... }: {
gnome-graphical-test = hci-effects.mkEffect {
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
effectScript = ''
readSecretString stardustxrDiscord .webhook > .webhook
readSecretString stardustxrIpfs .basicauth > .basicauth
set -x
export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
set +x
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
${pkgs.discord-sh}/bin/discord.sh \
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
--field "Branch;${branch}" \
--field "Commit ID;${rev}" \
--field "Flatland Revision;${flatland.rev}" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
--image "$ADDRESS"
'';
};
};
};
}

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
img/flatscreen_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
img/flatscreen_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/flatscreen_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,155 @@
{ pkgs, lib ? pkgs.lib, self, ... }:
# Some code is copy-pasted from https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/gnome.nix
# TODO: make this less boiler-platey and make a function like mkGnomeTest that does all this and upstream it to nixpkgs
{
name = "stardust-xr-server-gnome-vmtest";
meta = with lib; {
maintainers = [ maintainers.matthewcroughan ];
};
nodes.machine = { ... }: {
imports = [ "${pkgs.path}/nixos/tests/common/user-account.nix" ];
virtualisation.qemu.options = [
"-device virtio-gpu-pci"
];
environment.systemPackages = [ pkgs.monado ];
services.xserver = {
enable = true;
desktopManager.gnome = {
enable = true;
debug = true;
# Set a nice desktop background that is pleasing to the eyes :3
extraGSettingsOverrides = ''
[org.gnome.desktop.background]
picture-uri='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
'';
};
displayManager = {
gdm = {
enable = true;
debug = true;
};
autoLogin = {
enable = true;
user = "alice";
};
};
};
systemd.user.services = {
"monado" = {
after = [ "graphical-session.target" "default.target" "org.gnome.Shell@wayland.service" ];
environment = {
XRT_COMPOSITOR_FORCE_WAYLAND = "1";
WAYLAND_DISPLAY = "wayland-0";
};
serviceConfig = {
ExecStartPre = [
"${pkgs.writeShellScript "sleep" ''
sleep 3
''}"
];
ExecStart = let
# stdin disappears in NixOS test driver ( machine.succeed() ), requiring us to specify < /dev/ttyS0 to fake stdin
exec-monado-service = pkgs.writeShellScript "exec-monado-service" "${pkgs.monado}/bin/monado-service < /dev/ttyS0";
in [
"${exec-monado-service}"
];
};
};
"stardust-xr-server" = {
after = [ "monado.service" ];
serviceConfig = {
Type = "notify";
NotifyAccess = "all";
ExecStartPre = [
"${pkgs.writeShellScript "sleep" ''
sleep 3
''}"
];
ExecStart = let
notifyReady = pkgs.writeShellScript "notifyReady" "systemd-notify --ready";
exec-stardust-xr-server = pkgs.writeShellScript "exec-stardust-xr-server" "${self.packages.${pkgs.hostPlatform.system}.default}/bin/stardust-xr-server -e ${notifyReady}";
in [
"${exec-stardust-xr-server}"
];
};
};
"weston-cliptest" = {
after = [ "flatland.service" ];
environment.WAYLAND_DISPLAY = "wayland-1";
serviceConfig = {
ExecStart = [
"${pkgs.weston}/bin/weston-cliptest"
];
};
};
"flatland" = {
after = [ "stardust-xr-server.service" ];
serviceConfig = {
ExecStart = [
"${self.inputs.flatland.packages.${pkgs.hostPlatform.system}.default}/bin/flatland"
];
};
};
"org.gnome.Shell@wayland" = {
wants = [ "monado.service" "stardust-xr-server.service" "flatland.service" "weston-cliptest.service" ];
serviceConfig = {
ExecStart = [
# Clear the list before overriding it.
""
# Eval API is now internal so Shell needs to run in unsafe mode.
# TODO: improve test driver so that it supports openqa-like manipulation
# that would allow us to drop this mess.
"${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
];
};
};
};
};
testScript = { nodes, ... }: let
# Keep line widths somewhat managable
user = nodes.machine.config.users.users.alice;
uid = toString user.uid;
bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
gdbus = "${bus} gdbus";
su = command: "su ${user.name} -c '${command}'";
# Call javascript in gnome shell, returns a tuple (success, output), where
# `success` is true if the dbus call was successful and output is what the
# javascript evaluates to.
eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
# False when startup is done
startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
in ''
with subtest("Login to GNOME with GDM"):
# wait for gdm to start
machine.wait_for_unit("display-manager.service")
# wait for the wayland server
machine.wait_for_file("/run/user/${uid}/wayland-0")
# wait for alice to be logged in
machine.wait_for_unit("default.target", "${user.name}")
# check that logging in has given the user ownership of devices
assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
with subtest("Wait for GNOME Shell"):
# correct output should be (true, 'false')
machine.wait_until_succeeds(
"${startingUp} | grep -q 'true,..false'"
)
# To allow monado-service to use < /dev/ttyS0
machine.succeed("chown alice /dev/ttyS0")
with subtest("Open Monado and StardustXR"):
# Close the Activities view so that Shell can correctly track the focused window.
machine.send_key("esc")
machine.wait_for_unit("monado.service", "${user.name}")
machine.wait_for_unit("stardust-xr-server.service", "${user.name}")
machine.wait_for_unit("flatland.service", "${user.name}")
machine.wait_for_unit("weston-cliptest.service", "${user.name}")
machine.sleep(3)
machine.screenshot("screen")
'';
}

View File

@@ -1,136 +1,205 @@
use super::{eventloop::EventLoop, scenegraph::Scenegraph};
use super::scenegraph::Scenegraph;
#[cfg(feature = "oxr_runtime")]
use crate::openxr;
use crate::{
core::registry::Registry,
nodes::{data, drawable, fields, hmd, input, items, root::Root, spatial, startup, Node},
core::{registry::OwnedRegistry, task},
nodes::{
audio, data, drawable, fields, hmd, input, items,
root::Root,
spatial,
startup::{self, StartupSettings, STARTUP_SETTINGS},
Node,
},
};
use anyhow::{anyhow, Result};
use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use stardust_xr::messenger::Messenger;
use std::{
path::PathBuf,
sync::{Arc, Weak},
};
use tokio::{net::UnixStream, sync::Notify, task::JoinHandle};
use rustc_hash::FxHashMap;
use stardust_xr::messenger::{self, MessageSenderHandle};
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
use tokio::{net::UnixStream, task::JoinHandle};
use tracing::info;
lazy_static! {
pub static ref CLIENTS: Registry<Client> = Registry::new();
pub static ref CLIENTS: OwnedRegistry<Client> = OwnedRegistry::new();
pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client {
event_loop: Weak::new(),
index: 0,
pid: None,
// env: None,
exe: None,
stop_notifier: Default::default(),
join_handle: OnceCell::new(),
dispatch_join_handle: OnceCell::new(),
flush_join_handle: OnceCell::new(),
disconnect_status: OnceCell::new(),
messenger: None,
message_sender_handle: None,
scenegraph: Default::default(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
startup_settings: None,
});
}
pub struct Client {
event_loop: Weak<EventLoop>,
index: usize,
stop_notifier: Arc<Notify>,
join_handle: OnceCell<JoinHandle<Result<()>>>,
pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
let env = fs::read_to_string(format!("/proc/{pid}/environ"))?;
Ok(FxHashMap::from_iter(
env.split('\0')
.filter_map(|var| var.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string())),
))
}
pub fn startup_settings(env: &FxHashMap<String, String>) -> Option<StartupSettings> {
let token = env.get("STARDUST_STARTUP_TOKEN")?;
STARTUP_SETTINGS.lock().get(token).cloned()
}
pub messenger: Option<Messenger>,
pub scenegraph: Scenegraph,
pub struct Client {
pid: Option<i32>,
// env: Option<FxHashMap<String, String>>,
exe: Option<PathBuf>,
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
flush_join_handle: OnceCell<JoinHandle<Result<()>>>,
disconnect_status: OnceCell<Result<()>>,
pub message_sender_handle: Option<MessageSenderHandle>,
pub scenegraph: Arc<Scenegraph>,
pub root: OnceCell<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub startup_settings: Option<StartupSettings>,
}
impl Client {
pub fn from_connection(
index: usize,
event_loop: &Arc<EventLoop>,
connection: UnixStream,
) -> Arc<Self> {
println!("New client connected");
let client = CLIENTS.add(Client {
event_loop: Arc::downgrade(event_loop),
index,
stop_notifier: Default::default(),
join_handle: OnceCell::new(),
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
let pid = connection.peer_cred().ok().and_then(|c| c.pid());
let env = pid.and_then(|pid| get_env(pid).ok());
let exe = pid.and_then(|pid| fs::read_link(format!("/proc/{}/exe", pid)).ok());
info!(
pid,
exe = exe
.as_ref()
.and_then(|exe| exe.to_str().map(|s| s.to_string())),
"New client connected"
);
messenger: Some(Messenger::new(
tokio::runtime::Handle::current(),
connection,
)),
scenegraph: Default::default(),
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
let scenegraph = Arc::new(Scenegraph::default());
let startup_settings = env.as_ref().and_then(startup_settings);
let client = CLIENTS.add(Client {
pid,
// env,
exe: exe.clone(),
dispatch_join_handle: OnceCell::new(),
flush_join_handle: OnceCell::new(),
disconnect_status: OnceCell::new(),
message_sender_handle: Some(messenger_tx.handle()),
scenegraph: scenegraph.clone(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
startup_settings,
});
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client));
hmd::make_alias(&client);
spatial::create_interface(&client);
fields::create_interface(&client);
drawable::create_interface(&client);
data::create_interface(&client);
items::create_interface(&client);
input::create_interface(&client);
startup::create_interface(&client);
let _ = client.root.set(Root::create(&client)?);
hmd::make_alias(&client)?;
spatial::create_interface(&client)?;
fields::create_interface(&client)?;
drawable::create_interface(&client)?;
audio::create_interface(&client)?;
data::create_interface(&client)?;
items::create_interface(&client)?;
input::create_interface(&client)?;
startup::create_interface(&client)?;
#[cfg(feature = "openxr_runtime")]
openxr::create_interface(&client);
let _ = client.join_handle.set(tokio::spawn({
let client = client.clone();
async move {
let dispatch_loop = async {
loop {
client.dispatch().await?
let pid_printable = pid
.map(|pid| pid.to_string())
.unwrap_or_else(|| "??".to_string());
let exe_printable = exe
.and_then(|exe| {
exe.file_name()
.and_then(|exe| exe.to_str())
.map(|exe| exe.to_string())
})
.unwrap_or_else(|| "??".to_string());
let _ = client.dispatch_join_handle.get_or_try_init(|| {
task::new(
|| {
format!(
"client dispatch pid={} exe={}",
&pid_printable, &exe_printable,
)
},
{
let client = client.clone();
async move {
loop {
match messenger_rx.dispatch(&*scenegraph).await {
Err(e) => {
client.disconnect(Err(e.into()));
}
_ => (),
}
}
}
};
let flush_loop = async {
loop {
client.flush().await?
},
)
});
let _ = client.flush_join_handle.get_or_try_init(|| {
task::new(
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
{
let client = client.clone();
async move {
loop {
match messenger_tx.flush().await {
Err(e) => {
client.disconnect(Err(e.into()));
}
_ => (),
}
}
}
};
},
)
});
let result = tokio::select! {
_ = client.stop_notifier.notified() => Ok(()),
e = dispatch_loop => e,
e = flush_loop => e,
};
client.disconnect().await;
result
}
}));
client
Ok(client)
}
#[inline]
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
self.scenegraph
.get_node(path)
.ok_or_else(|| anyhow!("{} not found", name))
.ok_or_else(|| eyre!("{} not found", name))
}
pub async fn dispatch(&self) -> Result<(), std::io::Error> {
match &self.messenger {
Some(messenger) => messenger.dispatch(&self.scenegraph).await,
None => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)),
pub fn disconnect(&self, reason: Result<()>) {
let _ = self.disconnect_status.set(reason);
if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() {
dispatch_join_handle.abort();
}
}
pub async fn flush(&self) -> Result<(), std::io::Error> {
match &self.messenger {
Some(messenger) => messenger.flush().await,
None => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)),
}
}
pub async fn disconnect(&self) {
self.stop_notifier.notify_one();
if let Some(event_loop) = self.event_loop.upgrade() {
event_loop.clients.lock().await.remove(self.index);
if let Some(flush_join_handle) = self.flush_join_handle.get() {
flush_join_handle.abort();
}
CLIENTS.remove(self);
}
}
impl Drop for Client {
fn drop(&mut self) {
self.stop_notifier.notify_one();
CLIENTS.remove(self);
println!("Client disconnected");
info!(
pid = self.pid,
exe = self
.exe
.as_ref()
.and_then(|exe| exe.to_str().map(|s| s.to_string())),
disconnect_status = match self.disconnect_status.take() {
Some(Ok(_)) => "Graceful disconnect".to_string(),
Some(Err(e)) => format!("Error: {}", e.root_cause()),
None => "Unknown".to_string(),
},
"Client disconnected"
);
}
}

47
src/core/delta.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::ops::{Deref, DerefMut};
#[derive(Debug)]
pub struct Delta<T> {
value: T,
changed: bool,
}
#[allow(dead_code)]
impl<T> Delta<T> {
pub const fn new(value: T) -> Self {
Delta {
value,
changed: false,
}
}
pub fn peek_delta(&self) -> Option<&T> {
self.changed.then_some(&self.value)
}
pub fn delta(&mut self) -> Option<&mut T> {
let delta = self.changed.then_some(&mut self.value);
self.changed = false;
delta
}
pub fn mark_changed(&mut self) {
self.changed = true;
}
pub const fn value(&self) -> &T {
&self.value
}
pub fn value_mut(&mut self) -> &mut T {
self.mark_changed();
&mut self.value
}
}
impl<T> Deref for Delta<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> DerefMut for Delta<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.mark_changed();
&mut self.value
}
}

View File

@@ -1,61 +1,36 @@
use super::client::Client;
use anyhow::Result;
use slab::Slab;
use stardust_xr::server;
use std::sync::atomic::AtomicU64;
use super::task;
use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::net::UnixListener;
use tokio::sync::{Mutex, Notify};
use tokio::task::JoinHandle;
pub static FRAME: AtomicU64 = AtomicU64::new(0);
use tracing::error;
pub struct EventLoop {
pub socket_path: String,
stop_notifier: Arc<Notify>,
pub clients: Mutex<Slab<Arc<Client>>>,
join_handle: JoinHandle<()>,
}
impl EventLoop {
pub fn new() -> Result<(Arc<Self>, JoinHandle<Result<()>>)> {
let socket_path = server::get_free_socket_path()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::Other))?;
let socket = UnixListener::bind(socket_path.clone())?;
pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
let socket = UnixListener::bind(socket_path)?;
let event_loop = Arc::new(EventLoop {
socket_path,
stop_notifier: Default::default(),
clients: Mutex::new(Slab::new()),
});
let event_loop_join_handle = tokio::spawn({
let event_loop = event_loop.clone();
async move { EventLoop::event_loop(socket, event_loop).await }
});
Ok((event_loop, event_loop_join_handle))
}
async fn event_loop(socket: UnixListener, event_loop: Arc<EventLoop>) -> Result<()> {
let event_loop_async = async {
let join_handle = task::new(|| "event loop", async move {
loop {
let (socket, _) = socket.accept().await?;
let mut clients = event_loop.clients.lock().await;
let vacant_client = clients.vacant_entry();
let idx = vacant_client.key();
vacant_client.insert(Client::from_connection(idx, &event_loop, socket));
let Ok((socket, _)) = socket.accept().await else { continue };
if let Err(e) = Client::from_connection(socket) {
error!(?e, "Unable to create client from connection");
}
}
};
})?;
let event_loop = Arc::new(EventLoop { join_handle });
tokio::select! {
_ = event_loop.stop_notifier.notified() => Ok(()),
e = event_loop_async => e,
}
Ok(event_loop)
}
}
impl Drop for EventLoop {
fn drop(&mut self) {
self.stop_notifier.notify_one();
self.join_handle.abort();
}
}

View File

@@ -1,7 +1,10 @@
pub mod client;
pub mod delta;
pub mod destroy_queue;
pub mod eventloop;
pub mod nodelist;
pub mod node_collections;
pub mod registry;
pub mod resource;
pub mod scenegraph;
pub mod task;
pub mod typed_datamap;

View File

@@ -0,0 +1,84 @@
use crate::nodes::Node;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::{
borrow::Borrow,
hash::Hash,
sync::{Arc, Weak},
};
#[derive(Default)]
pub struct LifeLinkedNodeList {
nodes: Mutex<Vec<Weak<Node>>>,
}
impl LifeLinkedNodeList {
pub fn add(&self, node: Weak<Node>) {
self.nodes.lock().push(node);
}
pub fn clear(&self) {
self.nodes
.lock()
.iter()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
self.nodes.lock().clear();
}
}
impl Drop for LifeLinkedNodeList {
fn drop(&mut self) {
self.clear();
}
}
#[derive(Default)]
pub struct LifeLinkedNodeMap<K: Hash + Eq> {
nodes: Mutex<FxHashMap<K, Weak<Node>>>,
}
#[allow(dead_code)]
impl<K: Hash + Eq> LifeLinkedNodeMap<K> {
pub fn add(&self, key: K, node: &Arc<Node>) {
self.nodes.lock().insert(key, Arc::downgrade(node));
}
pub fn get<Q>(&self, key: &Q) -> Option<Arc<Node>>
where
Q: ?Sized,
K: Borrow<Q>,
Q: Hash + Eq,
{
self.nodes.lock().get(key).and_then(|n| n.upgrade())
}
pub fn nodes(&self) -> Vec<Arc<Node>> {
self.nodes
.lock()
.values()
.filter_map(|v| v.upgrade())
.collect()
}
pub fn remove<Q>(&self, key: &Q) -> Option<Arc<Node>>
where
Q: ?Sized,
K: Borrow<Q>,
Q: Hash + Eq,
{
self.nodes.lock().remove(key).and_then(|n| n.upgrade())
}
pub fn clear(&self) {
let mut nodes = self.nodes.lock();
nodes
.values()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
nodes.clear();
}
}
impl<K: Hash + Eq> Drop for LifeLinkedNodeMap<K> {
fn drop(&mut self) {
self.clear();
}
}

View File

@@ -1,29 +0,0 @@
use crate::nodes::Node;
use parking_lot::Mutex;
use std::sync::Weak;
#[derive(Default)]
pub struct LifeLinkedNodeList {
nodes: Mutex<Vec<Weak<Node>>>,
}
impl LifeLinkedNodeList {
pub fn add(&self, node: Weak<Node>) {
self.nodes.lock().push(node);
}
pub fn clear(&self) {
self.nodes
.lock()
.iter()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
self.nodes.lock().clear();
}
}
impl Drop for LifeLinkedNodeList {
fn drop(&mut self) {
self.clear();
}
}

View File

@@ -1,17 +1,20 @@
#![allow(dead_code)]
use parking_lot::{const_mutex, MappedMutexGuard, Mutex, MutexGuard};
use rustc_hash::FxHashMap;
use std::ptr;
use std::sync::{Arc, Weak};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
pub struct Registry<T: Send + Sync + ?Sized>(Lazy<Mutex<FxHashMap<usize, Weak<T>>>>);
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
impl<T: Send + Sync + ?Sized> Registry<T> {
pub const fn new() -> Self {
Registry(Lazy::new(|| Mutex::new(FxHashMap::default())))
Registry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Weak<T>>> {
MutexGuard::map(self.0.lock(), |r| {
r.get_or_insert_with(|| FxHashMap::default())
})
}
pub fn add(&self, t: T) -> Arc<T>
where
@@ -22,23 +25,81 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
t_arc
}
pub fn add_raw(&self, t: &Arc<T>) {
self.0
.lock()
self.lock()
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t));
}
pub fn contains(&self, t: &T) -> bool {
self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
}
pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.0
.lock()
self.lock()
.iter()
.filter_map(|pair| pair.1.upgrade())
.collect()
}
pub fn remove(&self, t: &T) {
pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
self.0
.lock()
.take()
.unwrap_or_default()
.into_iter()
.filter_map(|pair| pair.1.upgrade())
.collect()
}
pub fn remove(&self, t: &T) {
self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize));
}
pub fn clear(&self) {
self.0.lock().clear();
self.lock().clear();
}
}
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone()))
}
}
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
pub const fn new() -> Self {
OwnedRegistry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
MutexGuard::map(self.0.lock(), |r| {
r.get_or_insert_with(|| FxHashMap::default())
})
}
pub fn add(&self, t: T) -> Arc<T>
where
T: Sized,
{
let t_arc = Arc::new(t);
self.add_raw(t_arc.clone());
t_arc
}
pub fn add_raw(&self, t: Arc<T>) {
self.lock().insert(Arc::as_ptr(&t) as *const () as usize, t);
}
pub fn get_vec(&self) -> Vec<Arc<T>> {
self.lock().values().cloned().collect::<Vec<_>>()
}
pub fn contains(&self, t: &T) -> bool {
self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
}
pub fn remove(&self, t: &T) {
self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize));
}
pub fn clear(&self) {
self.lock().clear();
}
}
impl<T: Send + Sync + ?Sized> Clone for OwnedRegistry<T> {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone()))
}
}

View File

@@ -1,29 +1,43 @@
use anyhow::anyhow;
use color_eyre::eyre::eyre;
use serde::{de::Visitor, Deserialize};
use std::path::PathBuf;
use std::{ffi::OsStr, path::PathBuf};
#[derive(Debug)]
pub enum ResourceID {
File(PathBuf),
Namespaced { namespace: String, path: PathBuf },
}
impl ResourceID {
pub fn get_file(&self, prefixes: &[PathBuf]) -> Option<PathBuf> {
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
match self {
ResourceID::File(file) => (file.is_absolute() && file.exists()).then_some(file.clone()),
ResourceID::File(file) => (file.is_absolute()
&& file.exists() && Self::has_extension(file, extensions))
.then_some(file.clone()),
ResourceID::Namespaced { namespace, path } => {
for prefix in prefixes {
let mut test_path = prefix.clone();
test_path.push(namespace.clone());
test_path.push(path.clone());
if test_path.as_path().exists() {
return Some(test_path);
}
}
None
let file_name = path.file_name()?;
prefixes
.iter()
.filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
let parent = prefixed_path.parent()?;
std::fs::read_dir(parent).ok()
})
.flatten()
.filter_map(|item| item.ok())
.map(|dir_entry| dir_entry.path())
.filter(|path| path.file_stem() == Some(file_name))
.find(|path| Self::has_extension(path, extensions))
}
}
}
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension)
} else {
false
}
}
}
impl<'de> Deserialize<'de> for ResourceID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -56,9 +70,7 @@ impl<'de> Visitor<'de> for ResourceVisitor {
path: PathBuf::from(path),
}
} else {
return Err(serde::de::Error::custom(anyhow!(
"Invalid format for string"
)));
return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
})
}

View File

@@ -1,10 +1,12 @@
use crate::core::client::Client;
use crate::nodes::Node;
use anyhow::Result;
use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result;
use once_cell::sync::OnceCell;
use stardust_xr::scenegraph;
use stardust_xr::scenegraph::ScenegraphError;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use tracing::{debug, debug_span, instrument};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
@@ -17,8 +19,8 @@ pub struct Scenegraph {
}
impl Scenegraph {
pub fn get_client(&self) -> Arc<Client> {
self.client.get().unwrap().upgrade().unwrap()
pub fn get_client(&self) -> Option<Arc<Client>> {
self.client.get()?.upgrade()
}
pub fn add_node(&self, node: Node) -> Arc<Node> {
@@ -27,38 +29,70 @@ impl Scenegraph {
node_arc
}
pub fn add_node_raw(&self, node: Arc<Node>) {
debug!(node = ?&*node, "Add node");
let path = node.get_path().to_string();
self.nodes.insert(path, node);
}
#[instrument(level = "debug", skip(self))]
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
let mut node = self.nodes.get(path)?.clone();
if let Some(alias) = node.alias.get() {
while let Some(alias) = node.alias.get() {
node = alias.original.upgrade()?;
}
Some(node)
}
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
debug!(path, "Remove node");
let (_, node) = self.nodes.remove(path)?;
Some(node)
}
}
impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(&self, path: &str, method: &str, data: &[u8]) -> Result<(), ScenegraphError> {
self.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal(self.get_client(), method, data)
fn send_signal(
&self,
path: &str,
method: &str,
data: &[u8],
fds: Vec<OwnedFd>,
) -> Result<(), ScenegraphError> {
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)};
debug_span!("Handle signal", path, method).in_scope(|| {
self.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
.send_local_signal(
client,
method,
Message {
data: data.to_vec(),
fds,
},
)
})
}
fn execute_method(
&self,
path: &str,
method: &str,
data: &[u8],
) -> Result<Vec<u8>, ScenegraphError> {
self.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
.execute_local_method(self.get_client(), method, data)
fds: Vec<OwnedFd>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)};
debug_span!("Handle method", path, method).in_scope(|| {
let message = self
.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
.execute_local_method(
client,
method,
Message {
data: data.to_vec(),
fds,
},
)?;
Ok((message.data, message.fds))
})
}
}

24
src/core/task.rs Normal file
View File

@@ -0,0 +1,24 @@
use color_eyre::eyre::Result;
use std::future::Future;
use tokio::task::JoinHandle;
use tracing::instrument;
#[allow(unused_variables)]
#[instrument(level = "debug", skip_all)]
pub fn new<
F: FnOnce() -> S,
S: AsRef<str>,
A: Future<Output = O> + Send + 'static,
O: Send + 'static,
>(
name_fn: F,
async_future: A,
) -> Result<JoinHandle<O>> {
#[cfg(not(feature = "profile_tokio"))]
let result = Ok(tokio::task::spawn(async_future));
#[cfg(feature = "profile_tokio")]
let result = tokio::task::Builder::new()
.name(name_fn().as_ref())
.spawn(async_future);
result
}

56
src/core/typed_datamap.rs Normal file
View File

@@ -0,0 +1,56 @@
#![allow(unused)]
use std::ops::{Deref, DerefMut};
use color_eyre::eyre::Result;
use once_cell::sync::Lazy;
use serde::{de::DeserializeOwned, Serialize};
use stardust_xr::schemas::{
flat::Datamap,
flex::flexbuffers::{FlexbufferSerializer, Reader, ReaderError},
};
use crate::nodes::Message;
pub struct TypedDatamap<T: DeserializeOwned + Serialize>(T);
impl<T: DeserializeOwned + Serialize> TypedDatamap<T> {
pub fn new(data: T) -> Self {
TypedDatamap(data)
}
pub fn from_flex(message: Message) -> Result<Self> {
let root = Reader::get_root(message.as_ref())?;
T::deserialize(root).map(Self::new).map_err(|e| e.into())
}
pub fn to_datamap(&mut self) -> Result<Datamap> {
let mut serializer = FlexbufferSerializer::default();
self.0.serialize(&mut serializer)?;
Datamap::new(serializer.take_buffer()).map_err(|e| e.into())
}
pub fn serialize(&mut self) -> Option<Vec<u8>> {
let mut serializer = FlexbufferSerializer::default();
self.0.serialize(&mut serializer).ok()?;
// check if this is actually a map
Reader::get_root(serializer.view()).ok()?.get_map().ok()?;
Some(serializer.take_buffer())
}
}
impl<T: DeserializeOwned + Serialize> Default for TypedDatamap<T>
where
T: Default,
{
fn default() -> Self {
Self(T::default())
}
}
impl<T: DeserializeOwned + Serialize> Deref for TypedDatamap<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: DeserializeOwned + Serialize> DerefMut for TypedDatamap<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View File

@@ -1,27 +1,39 @@
mod core;
mod nodes;
mod objects;
#[cfg(feature = "openxr_runtime")]
mod openxr;
#[cfg(feature = "wayland")]
mod wayland;
use crate::core::destroy_queue;
use crate::nodes::{drawable, hmd, input};
use crate::nodes::{audio, drawable, hmd, input};
use crate::objects::input::eye_pointer::EyePointer;
use crate::objects::input::mouse_pointer::MousePointer;
use crate::objects::input::sk_controller::SkController;
use crate::objects::input::sk_hand::SkHand;
use crate::wayland::Wayland;
use crate::objects::play_space::PlaySpace;
use self::core::eventloop::EventLoop;
use anyhow::Result;
use clap::Parser;
use directories::ProjectDirs;
use slog::Drain;
use once_cell::sync::OnceCell;
use stardust_xr::server;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc;
use stereokit::input::Handed;
use stereokit::lifecycle::DepthMode;
use stereokit::render::SphericalHarmonics;
use stereokit::texture::Texture;
use stereokit::{lifecycle::DisplayMode, Settings};
use std::time::Duration;
use stereokit::{
named_colors::BLACK, DepthMode, DisplayMode, Handed, LogLevel, StereoKitMultiThread,
TextureFormat, TextureType,
};
use stereokit::{DisplayBlend, Sk};
use tokio::{runtime::Handle, sync::oneshot};
use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
pub static SK_INFO: OnceCell<SystemInfo> = OnceCell::new();
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
@@ -33,140 +45,326 @@ struct CliArgs {
/// Run Stardust XR as an overlay with given priority
#[clap(id = "PRIORITY", short = 'o', long = "overlay", action)]
overlay_priority: Option<u32>,
/// Don't create a tip input for controller because SOME RUNTIMES will lie
#[clap(long, action)]
disable_controller: bool,
/// Run a script when ready for clients to connect. If this is not set the script at $HOME/.config/stardust/startup will be ran if it exists.
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
startup_script: Option<PathBuf>,
}
fn main() -> Result<()> {
let project_dirs = ProjectDirs::from("", "", "stardust").unwrap();
let cli_args = Arc::new(CliArgs::parse());
let log = ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), slog::o!());
slog_stdlog::init()?;
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
let mut stereokit = Settings::default()
.app_name("Stardust XR")
.overlay_app(cli_args.overlay_priority.is_some())
.overlay_priority(cli_args.overlay_priority.unwrap_or(u32::MAX))
.disable_desktop_input_window(true)
.display_preference(if cli_args.flatscreen {
struct EventLoopInfo {
tokio_handle: Handle,
socket_path: PathBuf,
}
fn main() {
let registry = tracing_subscriber::registry();
#[cfg(feature = "profile_app")]
let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new()
.include_args(true)
.build();
#[cfg(feature = "profile_app")]
let registry = registry.with(chrome_layer);
#[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
#[cfg(feature = "profile_tokio")]
let registry = registry.with(console_layer);
let log_layer = fmt::Layer::new()
.with_thread_names(true)
.with_ansi(true)
.with_line_number(true)
.with_filter(EnvFilter::from_default_env());
registry.with(log_layer).init();
let project_dirs = ProjectDirs::from("", "", "stardust");
if project_dirs.is_none() {
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
}
let cli_args = Arc::new(CliArgs::parse());
let sk = stereokit::Settings {
app_name: "Stardust XR".to_string(),
display_preference: if cli_args.flatscreen {
DisplayMode::Flatscreen
} else {
DisplayMode::MixedReality
})
.depth_mode(DepthMode::D32)
.init()
.expect("StereoKit failed to initialize");
println!("Init StereoKit");
},
blend_preference: DisplayBlend::AnyTransparent,
depth_mode: DepthMode::D32,
log_filter: match EnvFilter::from_default_env().max_level_hint() {
Some(LevelFilter::ERROR) => LogLevel::Error,
Some(LevelFilter::WARN) => LogLevel::Warning,
Some(LevelFilter::INFO) => LogLevel::Inform,
Some(LevelFilter::DEBUG) => LogLevel::Diagnostic,
Some(LevelFilter::TRACE) => LogLevel::Diagnostic,
Some(LevelFilter::OFF) => LogLevel::None,
None => LogLevel::Warning,
},
overlay_app: cli_args.overlay_priority.is_some(),
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
disable_desktop_input_window: true,
..Default::default()
}
.init()
.expect("StereoKit failed to initialize");
let _ = SK_MULTITHREAD.set(sk.multithreaded());
info!("Init StereoKit");
SK_INFO.set(stereokit.system_info()).unwrap();
sk.material_set_shader(
sk.material_find("default/material_pbr").unwrap(),
sk.shader_find("default/shader_pbr_clip").unwrap(),
);
// Skytex/light stuff
{
let skytex_path = project_dirs.config_dir().join("skytex.hdr");
if let Some((tex, light)) = skytex_path
.exists()
.then(|| Texture::from_cubemap_equirectangular(&stereokit, &skytex_path, true, 100))
if let Some((light, tex)) = project_dirs
.as_ref()
.and_then(|dirs| {
let skytex_path = dirs.config_dir().join("skytex.hdr");
skytex_path
.exists()
.then(|| sk.tex_create_cubemap_file(&skytex_path, true, 100).ok())
})
.flatten()
{
stereokit.set_skytex(&tex);
stereokit.set_skylight(&light);
} else if let Some(tex) = Texture::cubemap_from_spherical_harmonics(
&stereokit,
&SphericalHarmonics::default(),
16,
0.0,
0.0,
) {
stereokit.set_skytex(&tex);
sk.render_set_skytex(&tex);
sk.render_set_skylight(light);
} else {
sk.render_set_skytex(sk.tex_gen_color(
BLACK,
1,
1,
TextureType::CUBEMAP,
TextureFormat::RGBA32,
));
}
}
let mouse_pointer = cli_args.flatscreen.then(MousePointer::new);
let mut hands =
(!cli_args.flatscreen).then(|| [SkHand::new(Handed::Left), SkHand::new(Handed::Right)]);
let mut controllers = (!cli_args.flatscreen).then(|| {
[
SkController::new(Handed::Left),
SkController::new(Handed::Right),
]
});
let mut mouse_pointer = cli_args
.flatscreen
.then(MousePointer::new)
.transpose()
.unwrap();
let mut hands = (!cli_args.flatscreen)
.then(|| {
let left = SkHand::new(Handed::Left).ok();
let right = SkHand::new(Handed::Right).ok();
left.zip(right)
})
.flatten();
let mut controllers = (!cli_args.flatscreen && !cli_args.disable_controller)
.then(|| {
let left = SkController::new(&sk, Handed::Left).ok();
let right = SkController::new(&sk, Handed::Right).ok();
left.zip(right)
})
.flatten();
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze())
.then(EyePointer::new)
.transpose()
.unwrap();
if hands.is_none() {
unsafe {
stereokit::sys::input_hand_visible(stereokit::sys::handed__handed_left, false as i32);
stereokit::sys::input_hand_visible(stereokit::sys::handed__handed_right, false as i32);
}
sk.input_hand_visible(Handed::Left, false);
sk.input_hand_visible(Handed::Right, false);
}
let play_space = sk
.world_has_bounds()
.then(|| PlaySpace::new().ok())
.flatten();
let (event_stop_tx, event_stop_rx) = oneshot::channel::<()>();
let (handle_sender, handle_receiver) = oneshot::channel::<Handle>();
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
let event_thread = std::thread::Builder::new()
.name("event_loop".to_owned())
.spawn(move || event_loop(handle_sender, event_stop_rx))?;
let _tokio_handle = handle_receiver.blocking_recv()?.enter();
.spawn(move || event_loop(info_sender, event_stop_rx))
.unwrap();
let event_loop_info = info_receiver.blocking_recv().unwrap();
let _tokio_handle = event_loop_info.tokio_handle.enter();
let mut wayland = Wayland::new(log)?;
println!("Stardust ready!");
stereokit.run(
|sk, draw_ctx| {
hmd::frame(sk);
wayland.frame(sk);
destroy_queue::clear();
#[cfg(feature = "wayland")]
let mut wayland = Some(wayland::Wayland::new().expect("Could not initialize wayland"));
info!("Stardust ready!");
if let Some(mouse_pointer) = &mouse_pointer {
mouse_pointer.update(sk);
}
if let Some(hands) = &mut hands {
hands[0].update(sk);
hands[1].update(sk);
}
if let Some(controllers) = &mut controllers {
controllers[0].update(sk);
controllers[1].update(sk);
}
input::process_input();
nodes::root::Root::logic_step(sk.time_elapsed());
drawable::draw(sk, draw_ctx);
let mut startup_child = if let Some(project_dirs) = project_dirs.as_ref() {
let startup_script_path = cli_args
.startup_script
.clone()
.and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
let mut startup_command = Command::new(startup_script_path);
wayland.make_context_current();
},
|_| {
println!("Cleanly shut down StereoKit");
},
);
startup_command.stdin(Stdio::null());
startup_command.env(
"FLAT_WAYLAND_DISPLAY",
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
);
startup_command.env(
"STARDUST_INSTANCE",
event_loop_info
.socket_path
.file_name()
.expect("Stardust socket path not found"),
);
#[cfg(feature = "wayland")]
{
startup_command.env("WAYLAND_DISPLAY", &wayland.as_ref().unwrap().socket_name);
#[cfg(feature = "xwayland")]
startup_command.env(
"DISPLAY",
format!(":{}", wayland.as_ref().unwrap().xwayland_state.display),
);
startup_command.env("GDK_BACKEND", "wayland");
startup_command.env("QT_QPA_PLATFORM", "wayland");
startup_command.env("MOZ_ENABLE_WAYLAND", "1");
startup_command.env("CLUTTER_BACKEND", "wayland");
startup_command.env("SDL_VIDEODRIVER", "wayland");
}
startup_command.spawn().ok()
} else {
None
};
drop(wayland);
let mut last_frame_delta = Duration::ZERO;
let mut sleep_duration = Duration::ZERO;
debug_span!("StereoKit").in_scope(|| {
sk.run_stateful(
&mut wayland,
move |wayland, _, sk| {
let _span = debug_span!("StereoKit step");
let _span = _span.enter();
hmd::frame(sk);
#[cfg(feature = "wayland")]
wayland.as_mut().unwrap().frame_event(sk);
destroy_queue::clear();
if let Some(mouse_pointer) = &mut mouse_pointer {
mouse_pointer.update(sk);
}
if let Some((left_hand, right_hand)) = &mut hands {
left_hand.update(sk);
right_hand.update(sk);
}
if let Some((left_controller, right_controller)) = &mut controllers {
left_controller.update(sk);
right_controller.update(sk);
}
if let Some(eye_pointer) = &eye_pointer {
eye_pointer.update(sk);
}
if let Some(play_space) = &play_space {
play_space.update(sk);
}
input::process_input();
nodes::root::Root::send_frame_events(sk.time_elapsed_unscaled());
adaptive_sleep(
sk,
&mut last_frame_delta,
&mut sleep_duration,
Duration::from_micros(250),
);
#[cfg(feature = "wayland")]
wayland.as_mut().unwrap().update(sk);
drawable::draw(sk);
audio::update(sk);
#[cfg(feature = "wayland")]
wayland.as_mut().unwrap().make_context_current();
},
|wayland, _sk| {
info!("Cleanly shut down StereoKit");
if let Some(mut startup_child) = startup_child.take() {
let _ = startup_child.kill();
}
#[cfg(feature = "wayland")]
wayland.take();
},
)
});
let _ = event_stop_tx.send(());
event_thread
.join()
.expect("Failed to cleanly shut down event loop")?;
println!("Cleanly shut down Stardust");
Ok(())
.expect("Failed to cleanly shut down event loop")
.unwrap();
// #[cfg(feature = "wayland")]
// let _wayland = ManuallyDrop::new(wayland);
info!("Cleanly shut down Stardust");
}
// #[tokio::main]
#[tokio::main(flavor = "current_thread")]
fn adaptive_sleep(
sk: &impl StereoKitMultiThread,
last_frame_delta: &mut Duration,
sleep_duration: &mut Duration,
sleep_duration_increase: Duration,
) {
let frame_delta = Duration::from_secs_f64(sk.time_elapsed_unscaled());
if *last_frame_delta < frame_delta {
if let Some(frame_delta_delta) = frame_delta.checked_sub(*last_frame_delta) {
if let Some(new_sleep_duration) = sleep_duration.checked_sub(frame_delta_delta) {
*sleep_duration = new_sleep_duration;
}
}
} else {
*sleep_duration += sleep_duration_increase;
}
debug_span!("Sleep", ?sleep_duration, ?frame_delta, ?last_frame_delta).in_scope(|| {
*last_frame_delta = frame_delta;
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
});
}
#[tokio::main]
// #[tokio::main(flavor = "current_thread")]
async fn event_loop(
handle_sender: oneshot::Sender<Handle>,
info_sender: oneshot::Sender<EventLoopInfo>,
stop_rx: oneshot::Receiver<()>,
) -> anyhow::Result<()> {
let _ = handle_sender.send(Handle::current());
// console_subscriber::init();
) -> color_eyre::eyre::Result<()> {
let socket_path =
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
let _event_loop = EventLoop::new(socket_path.clone()).expect("Couldn't create server socket");
info!("Init event loop");
info!(
socket_path = ?socket_path.display(),
"Stardust socket created"
);
let _ = info_sender.send(EventLoopInfo {
tokio_handle: Handle::current(),
socket_path,
});
let (event_loop, event_loop_join_handle) =
EventLoop::new().expect("Couldn't create server socket");
println!("Init event loop");
println!("Stardust socket created at {}", event_loop.socket_path);
if atty::is(atty::Stream::Stdin) {
stop_rx.await?;
} else {
tokio::select! {
biased;
_ = tokio::signal::ctrl_c() => (),
_ = stop_rx => (),
};
}
let result = tokio::select! {
biased;
_ = tokio::signal::ctrl_c() => Ok(()),
_ = stop_rx => Ok(()),
e = event_loop_join_handle => e?,
};
println!("Cleanly shut down event loop");
info!("Cleanly shut down event loop");
unsafe {
stereokit::sys::sk_quit();
}
result
Ok(())
}

View File

@@ -1,13 +1,13 @@
use crate::core::client::Client;
use super::Node;
use crate::core::client::Client;
use color_eyre::eyre::{ensure, Result};
use std::sync::{Arc, Weak};
#[derive(Debug, Default, Clone)]
pub struct AliasInfo {
pub(super) local_signals: Vec<&'static str>,
pub(super) local_methods: Vec<&'static str>,
pub(super) remote_signals: Vec<&'static str>,
pub(super) server_signals: Vec<&'static str>,
pub(super) server_methods: Vec<&'static str>,
pub(super) client_signals: Vec<&'static str>,
}
#[allow(dead_code)]
@@ -18,14 +18,22 @@ pub struct Alias {
pub info: AliasInfo,
}
impl Alias {
pub fn new(
pub fn create(
client: &Arc<Client>,
parent: &str,
name: &str,
original: &Arc<Node>,
info: AliasInfo,
) -> Arc<Node> {
let node = Node::create(client, parent, name, true).add_to_scenegraph();
) -> Result<Arc<Node>> {
ensure!(
client
.scenegraph
.get_node(&(parent.to_string() + "/" + name))
.is_none(),
"Node already exists"
);
let node = Node::create(client, parent, name, true).add_to_scenegraph()?;
let alias = Alias {
node: Arc::downgrade(&node),
original: Arc::downgrade(original),
@@ -33,6 +41,6 @@ impl Alias {
};
let alias = original.aliases.add(alias);
let _ = node.alias.set(alias);
node
Ok(node)
}
}

136
src/nodes/audio.rs Normal file
View File

@@ -0,0 +1,136 @@
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::registry::Registry;
use crate::core::resource::ResourceID;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use color_eyre::eyre::{ensure, eyre, Result};
use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::ops::DerefMut;
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
pub struct Sound {
space: Arc<Spatial>,
volume: f32,
pending_audio_path: PathBuf,
sk_sound: OnceCell<SendWrapper<SkSound>>,
instance: Mutex<Option<SoundInstance>>,
stop: Mutex<Option<()>>,
play: Mutex<Option<()>>,
}
impl Sound {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let pending_audio_path = resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("wav"), OsStr::new("mp3")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let sound = Sound {
space: node.spatial.get().unwrap().clone(),
volume: 1.0,
pending_audio_path,
sk_sound: OnceCell::new(),
instance: Mutex::new(None),
stop: Mutex::new(None),
play: Mutex::new(None),
};
let sound_arc = SOUND_REGISTRY.add(sound);
node.add_local_signal("play", Sound::play_flex);
node.add_local_signal("stop", Sound::stop_flex);
let _ = node.sound.set(sound_arc.clone());
Ok(sound_arc)
}
fn update(&self, sk: &impl StereoKitDraw) {
let sound = self.sk_sound.get_or_init(|| {
SendWrapper::new(sk.sound_create(self.pending_audio_path.clone()).unwrap())
});
if self.stop.lock().take().is_some() {
if let Some(instance) = self.instance.lock().take() {
sk.sound_inst_stop(instance);
}
}
if self.play.lock().is_some() && self.instance.lock().is_none() {
self.instance.lock().replace(sk.sound_play(
sound.as_ref(),
vec3(0.0, 0.0, 0.0),
self.volume,
));
}
if let Some(instance) = self.instance.lock().deref_mut() {
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
}
}
fn play_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let sound = node.sound.get().unwrap();
sound.play.lock().replace(());
Ok(())
}
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let sound = node.sound.get().unwrap();
sound.stop.lock().replace(());
Ok(())
}
}
pub fn update(sk: &impl StereoKitDraw) {
for sound in SOUND_REGISTRY.get_valid_contents() {
sound.update(sk)
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "audio", false);
node.add_local_signal("create_sound", create_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateSoundInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
resource: ResourceID,
}
let info: CreateSoundInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/audio/sound", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Sound::add_to(&node, info.resource)?;
Ok(())
}
impl Drop for Sound {
fn drop(&mut self) {
if let Some(instance) = self.instance.lock().take() {
destroy_queue::add(instance);
}
SOUND_REGISTRY.remove(self);
}
}

View File

@@ -1,29 +1,30 @@
use super::alias::AliasInfo;
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Node};
use super::{Alias, Message, Node};
use crate::core::client::Client;
use crate::core::nodelist::LifeLinkedNodeList;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::nodes::fields::find_field;
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO};
use crate::nodes::spatial::find_spatial_parent;
use anyhow::{anyhow, ensure, Result};
use color_eyre::eyre::{ensure, eyre, Result};
use glam::vec3a;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use mint::{Quaternion, Vector3};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
use stardust_xr::values::Transform;
use std::sync::{Arc, Weak};
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
(|| -> Result<_> {
for key in mask_map_lesser.get_mask()?.iter_keys() {
let lesser_key_type = mask_map_lesser.get_mask()?.index(key)?.flexbuffer_type();
let greater_key_type = mask_map_greater.get_mask()?.index(key)?.flexbuffer_type();
if lesser_key_type != greater_key_type {
let lesser_key = mask_map_lesser.get_mask()?.index(key)?;
let greater_key = mask_map_greater.get_mask()?.index(key)?;
if lesser_key.flexbuffer_type() != greater_key.flexbuffer_type() {
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
}
}
@@ -32,120 +33,145 @@ fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
.is_ok()
}
type MaskMapGetFn = fn(&[u8]) -> Result<flexbuffers::MapReader<&[u8]>>;
pub struct Mask {
binary: Vec<u8>,
get_fn: MaskMapGetFn,
}
pub struct Mask(pub Vec<u8>);
impl Mask {
pub fn from_struct<T: Default + Serialize>() -> Self {
let mut serializer = flexbuffers::FlexbufferSerializer::new();
T::default().serialize(&mut serializer).unwrap();
Mask(serializer.take_buffer())
}
pub fn get_mask(&self) -> Result<flexbuffers::MapReader<&[u8]>> {
(self.get_fn)(self.binary.as_slice())
flexbuffers::Reader::get_root(self.0.as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
pub fn set_mask(&mut self, binary: Vec<u8>, get_fn: MaskMapGetFn) {
self.binary = binary;
self.get_fn = get_fn;
}
}
impl Default for Mask {
fn default() -> Self {
Mask {
binary: Default::default(),
get_fn: mask_get_err,
}
}
}
fn mask_get_err(_binary: &[u8]) -> Result<flexbuffers::MapReader<&[u8]>> {
Err(anyhow!("You need to call setMask to set the mask!"))
}
fn mask_get_map_at_root(binary: &[u8]) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(binary)
.map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| anyhow!("Mask is not a valid map"))
}
#[derive(Default)]
#[derive(Serialize, Deserialize)]
struct SendDataInfo<'a> {
uid: &'a str,
data: Vec<u8>,
}
pub struct PulseSender {
mask: Mutex<Mask>,
aliases: LifeLinkedNodeList,
uid: String,
node: Weak<Node>,
pub mask: Mask,
aliases: LifeLinkedNodeMap<String>,
}
impl PulseSender {
pub fn add_to(node: &Arc<Node>) -> Result<()> {
pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let sender = Default::default();
let sender = PulseSender {
uid: nanoid!(),
node: Arc::downgrade(node),
mask,
aliases: LifeLinkedNodeMap::default(),
};
let sender = PULSE_SENDER_REGISTRY.add(sender);
let _ = node.pulse_sender.set(sender);
node.add_local_signal("setMask", PulseSender::set_mask_flex);
node.add_local_method("getReceivers", PulseSender::get_receivers_flex);
Ok(())
let _ = node.pulse_sender.set(sender.clone());
node.add_local_signal("send_data", PulseSender::send_data_flex);
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
Ok(sender.clone())
}
pub fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
ensure!(
node.pulse_sender.get().is_some(),
"Internal: Node does not have a pulse sender aspect"
fn handle_new_receiver(&self, receiver: &PulseReceiver) {
if !mask_matches(&self.mask, &receiver.mask) {
return;
}
let Some(tx_node) = self.node.upgrade() else {return};
let Some(tx_client) = tx_node.get_client() else {return};
let Some(rx_node) = receiver.node.upgrade() else {return};
// Receiver itself
let rx_alias = Alias::create(
&tx_client,
tx_node.get_path(),
receiver.uid.as_str(),
&rx_node,
AliasInfo {
server_methods: vec!["sendData", "getTransform"],
..Default::default()
},
);
node.pulse_sender
.get()
.unwrap()
.mask
.lock()
.set_mask(data.to_vec(), mask_get_map_at_root);
Ok(())
}
fn get_receivers_flex(
node: &Node,
calling_client: Arc<Client>,
_data: &[u8],
) -> Result<Vec<u8>> {
let sender_spatial = node
.spatial
.get()
.ok_or_else(|| anyhow!("Node does not have a spatial aspect!"))?;
let sender = node
.pulse_sender
.get()
.ok_or_else(|| anyhow!("Node does not have a sender aspect!"))?;
let valid_receivers = PULSE_RECEIVER_REGISTRY.get_valid_contents();
let mut distance_sorted_receivers: Vec<(f32, &PulseReceiver)> = valid_receivers
.iter()
.filter(|receiver| receiver.get_field().is_some())
.filter(|receiver| mask_matches(&*sender.mask.lock(), &*receiver.mask.lock()))
.map(|receiver| {
(
receiver
.get_field()
.unwrap()
.distance(sender_spatial, vec3a(0_f32, 0_f32, 0_f32)),
receiver.as_ref(),
)
})
.collect();
distance_sorted_receivers.sort_by(|(d1, _), (d2, _)| d1.partial_cmp(d2).unwrap());
sender.aliases.clear();
let uids: Vec<String> = distance_sorted_receivers
.into_iter()
.map(|(_, receiver)| {
let receiver_alias = Alias::new(
&calling_client,
node.get_path(),
receiver.uid.as_str(),
receiver.node.upgrade().as_ref().unwrap(),
AliasInfo {
local_methods: vec!["sendData"],
..Default::default()
},
if let Ok(rx_alias) = rx_alias {
self.aliases.add(receiver.uid.clone(), &rx_alias);
if let Some(rx_field_node) = receiver.field.spatial_ref().node.upgrade() {
// Receiver's field
let rx_field_alias = Alias::create(
&tx_client,
rx_alias.get_path(),
"field",
&rx_field_node,
FIELD_ALIAS_INFO.clone(),
);
sender.aliases.add(Arc::downgrade(&receiver_alias));
if let Ok(rx_field_alias) = rx_field_alias {
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
}
}
}
receiver.uid.clone()
})
.collect();
#[derive(Serialize)]
struct NewReceiverInfo<'a> {
uid: &'a str,
distance: f32,
position: Vector3<f32>,
rotation: Quaternion<f32>,
}
serialize(uids).map_err(|e| e.into())
let (_, rotation, position) = Spatial::space_to_space_matrix(
rx_node.spatial.get().map(|s| s.as_ref()),
tx_node.spatial.get().map(|s| s.as_ref()),
)
.to_scale_rotation_translation();
let info = NewReceiverInfo {
uid: &receiver.uid,
distance: receiver
.field
.distance(tx_node.spatial.get().unwrap(), vec3a(0.0, 0.0, 0.0)),
position: position.into(),
rotation: rotation.into(),
};
let Ok(data) = serialize(info) else {return};
let _ = tx_node.send_remote_signal("new_receiver", data);
}
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str();
self.aliases.remove(uid);
self.aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {return};
let Ok(data) = serialize(&uid) else {return};
let _ = tx_node.send_remote_signal("drop_receiver", data);
}
fn send_data_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let info: SendDataInfo = deserialize(message.as_ref())?;
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
let receiver =
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
let receiver_mask = &receiver_node
.get_aspect("Pulse receiver", "pulse receiver", |node| {
&node.pulse_receiver
})?
.mask;
let data_mask = Mask(info.data);
data_mask.get_mask()?;
ensure!(
mask_matches(receiver_mask, &data_mask),
"Message does not contain the same keys as the receiver's mask"
);
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
}
}
impl Drop for PulseSender {
@@ -156,62 +182,36 @@ impl Drop for PulseSender {
pub struct PulseReceiver {
uid: String,
node: Weak<Node>,
pub mask: Mutex<Mask>,
field: Weak<Field>,
pub node: Weak<Node>,
pub field: Arc<Field>,
pub mask: Mask,
}
impl PulseReceiver {
pub fn add_to(node: &Arc<Node>, field: Arc<Field>) -> Result<()> {
pub fn add_to(node: &Arc<Node>, field: Arc<Field>, mask: Mask) -> Result<Arc<PulseReceiver>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let receiver = PulseReceiver {
uid: node.uid.clone(),
uid: nanoid!(),
node: Arc::downgrade(node),
field: Arc::downgrade(&field),
mask: Default::default(),
field,
mask,
};
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
let _ = node.pulse_receiver.set(receiver);
node.add_local_signal("setMask", PulseReceiver::set_mask_flex);
node.add_local_signal("sendData", PulseReceiver::send_data_flex);
Ok(())
}
fn get_field(&self) -> Option<Arc<Field>> {
self.field.upgrade()
}
fn send_data_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
ensure!(
node.pulse_receiver.get().is_some(),
"Internal: Node does not have a pulse receiver aspect"
);
let receiver_mask = node.pulse_receiver.get().unwrap().mask.lock();
let data_mask = Mask {
binary: data.to_vec(),
get_fn: mask_get_map_at_root,
};
if !mask_matches(&receiver_mask, &data_mask) {
return Err(anyhow!(
"Message does not contain the same keys as the receiver mask"
));
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
drop(receiver_mask);
node.send_remote_signal("pulse", data)?;
Ok(())
let _ = node.pulse_receiver.set(receiver.clone());
Ok(receiver)
}
fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
ensure!(
node.pulse_receiver.get().is_some(),
"Internal: Node does not have a pulse receiver aspect"
);
node.pulse_receiver
.get()
.unwrap()
.mask
.lock()
.set_mask(data.to_vec(), mask_get_map_at_root);
pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> {
if let Some(node) = self.node.upgrade() {
node.send_remote_signal("data", serialize(SendDataInfo { uid, data })?)?;
}
Ok(())
}
}
@@ -219,59 +219,49 @@ impl PulseReceiver {
impl Drop for PulseReceiver {
fn drop(&mut self) {
PULSE_RECEIVER_REGISTRY.remove(self);
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_drop_receiver(self);
}
}
}
pub fn create_interface(client: &Arc<Client>) {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "data", false);
node.add_local_signal("createPulseSender", create_pulse_sender_flex);
node.add_local_signal("createPulseReceiver", create_pulse_receiver_flex);
node.add_to_scenegraph();
node.add_local_signal("create_pulse_sender", create_pulse_sender_flex);
node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex);
node.add_to_scenegraph().map(|_| ())
}
// pub fn mask_get_map_pulse_sender_create_args(mask: &Mask) -> Result<flexbuffers::MapReader<&[u8]>> {
// flexbuffers::Reader::get_root(mask.binary.as_slice())
// .map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
// .get_vector()?
// .index(4)?
// .get_map()
// .map_err(|_| anyhow!("Mask is not a valid map"))
// }
pub fn create_pulse_sender_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseSenderInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
mask: Vec<u8>,
}
let info: CreatePulseSenderInfo = deserialize(data)?;
let info: CreatePulseSenderInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/data/sender", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
PulseSender::add_to(&node)?;
let transform = parse_transform(info.transform, true, true, false);
let mask = Mask(info.mask);
mask.get_mask()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
PulseSender::add_to(&node, mask)?;
Ok(())
}
// pub fn mask_get_map_pulse_receiver_create_args(
// mask: &Mask,
// ) -> Result<flexbuffers::MapReader<&[u8]>> {
// flexbuffers::Reader::get_root(mask.binary.as_slice())
// .map_err(|_| anyhow!("Mask is not a valid flexbuffer"))?
// .get_vector()?
// .index(5)?
// .get_map()
// .map_err(|_| anyhow!("Mask is not a valid map"))
// }
pub fn create_pulse_receiver_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseReceiverInfo<'a> {
@@ -279,15 +269,18 @@ pub fn create_pulse_receiver_flex(
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
mask: Vec<u8>,
}
let info: CreatePulseReceiverInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/data/sender", info.name, true);
let info: CreatePulseReceiverInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/data/receiver", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let mask = Mask(info.mask);
mask.get_mask()?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
PulseReceiver::add_to(&node, field)?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
PulseReceiver::add_to(&node, field, mask)?;
Ok(())
}

165
src/nodes/drawable/lines.rs Normal file
View File

@@ -0,0 +1,165 @@
use crate::{
core::{client::Client, registry::Registry},
nodes::{
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
},
};
use color_eyre::eyre::{bail, ensure, Result};
use glam::Vec3A;
use mint::Vector3;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Lerp, Rgba};
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{collections::VecDeque, sync::Arc};
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
use super::Drawable;
static LINES_REGISTRY: Registry<Lines> = Registry::new();
#[derive(Debug, Clone, Deserialize)]
struct LinePointRaw {
point: Vector3<f32>,
thickness: f32,
color: [f32; 4],
}
#[derive(Debug, Clone)]
struct LineData {
points: Vec<LinePointRaw>,
cyclic: bool,
}
pub struct Lines {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
data: Mutex<LineData>,
}
impl Lines {
fn add_to(node: &Arc<Node>, points: Vec<LinePointRaw>, cyclic: bool) -> Result<Arc<Lines>> {
ensure!(
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
let mut bounds = Bounds::default();
let Some(Drawable::Lines(lines)) = node.drawable.get() else {return bounds};
for point in &lines.data.lock().points {
bounds = bounds_grow_to_fit_pt(bounds, point.point);
}
bounds
});
let lines = LINES_REGISTRY.add(Lines {
enabled: node.enabled.clone(),
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(),
data: Mutex::new(LineData { points, cyclic }),
});
node.add_local_signal("set_points", Lines::set_points_flex);
node.add_local_signal("set_cyclic", Lines::set_cyclic_flex);
let _ = node.drawable.set(Drawable::Lines(lines.clone()));
Ok(lines)
}
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
let transform_mat = self.space.global_transform();
let data = self.data.lock().clone();
let mut points: VecDeque<SkLinePoint> = data
.points
.iter()
.map(|p| SkLinePoint {
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
thickness: p.thickness,
color: p.color.map(|c| (c * 255.0) as u8).into(),
})
.collect();
if data.cyclic && !points.is_empty() {
let first = data.points.first().unwrap();
let last = data.points.last().unwrap();
let color = Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5);
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5))
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: Color128::from([color.red(), color.green(), color.blue(), color.alpha()])
.into(),
};
points.push_front(connect_point.clone());
points.push_back(connect_point);
}
draw_ctx.line_add_listv(points.make_contiguous());
}
pub fn set_points_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
let mut points: Vec<LinePointRaw> = deserialize(message.as_ref())?;
for p in &mut points {
p.color[0] = p.color[0].powf(2.2);
p.color[1] = p.color[1].powf(2.2);
p.color[2] = p.color[2].powf(2.2);
}
lines.data.lock().points = points;
Ok(())
}
pub fn set_cyclic_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
lines.data.lock().cyclic = deserialize(message.as_ref())?;
Ok(())
}
}
impl Drop for Lines {
fn drop(&mut self) {
LINES_REGISTRY.remove(self);
}
}
pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
for lines in LINES_REGISTRY.get_valid_contents() {
if lines.enabled.load(Ordering::Relaxed) {
lines.draw(draw_ctx);
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
points: Vec<LinePointRaw>,
cyclic: bool,
}
let mut info: CreateTextInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/lines", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
for p in &mut info.points {
p.color[0] = p.color[0].powf(2.2);
p.color[1] = p.color[1].powf(2.2);
p.color[2] = p.color[2].powf(2.2);
}
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Lines::add_to(&node, info.points, info.cyclic)?;
Ok(())
}

View File

@@ -1,47 +1,54 @@
pub mod lines;
pub mod model;
pub mod shaders;
pub mod text;
use super::Node;
use self::{
lines::Lines,
model::{Model, ModelPart},
text::Text,
};
use super::{Message, Node};
use crate::core::client::Client;
use anyhow::Result;
use color_eyre::eyre::Result;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::{path::PathBuf, sync::Arc};
use stereokit::{lifecycle::DrawContext, texture::Texture, StereoKit};
use stereokit::StereoKitDraw;
use tracing::instrument;
pub fn create_interface(client: &Arc<Client>) {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "drawable", false);
node.add_local_signal("createModel", model::create_flex);
node.add_local_signal("createText", text::create_flex);
node.add_local_signal("setSkyFile", set_sky_file_flex);
node.add_to_scenegraph();
node.add_local_signal("create_lines", lines::create_flex);
node.add_local_signal("create_model", model::create_flex);
node.add_local_signal("create_text", text::create_flex);
node.add_local_signal("set_sky_file", set_sky_file_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn draw(sk: &mut StereoKit, draw_ctx: &DrawContext) {
model::draw_all(sk, draw_ctx);
text::draw_all(sk, draw_ctx);
pub enum Drawable {
Lines(Arc<Lines>),
Model(Arc<Model>),
ModelPart(Arc<ModelPart>),
Text(Arc<Text>),
}
let new_skytex = QUEUED_SKYTEX.lock().take();
let mut new_skylight = QUEUED_SKYLIGHT.lock().take();
let same_file = new_skytex == new_skylight;
#[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) {
lines::draw_all(sk);
model::draw_all(sk);
text::draw_all(sk);
if let Some(skytex) = new_skytex {
if let Some((skytex, skylight)) =
Texture::from_cubemap_equirectangular(sk, &skytex, true, i32::MAX)
{
sk.set_skytex(&skytex);
if same_file {
sk.set_skylight(&skylight);
new_skylight = None;
}
if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
sk.render_set_skytex(&skytex);
}
}
if let Some(skylight) = new_skylight {
if let Some((_, skylight)) =
Texture::from_cubemap_equirectangular(sk, &skylight, true, i32::MAX)
{
sk.set_skylight(&skylight);
if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
sk.render_set_skylight(skylight);
}
}
}
@@ -49,14 +56,14 @@ pub fn draw(sk: &mut StereoKit, draw_ctx: &DrawContext) {
static QUEUED_SKYLIGHT: 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<()> {
fn set_sky_file_flex(_node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct SkyFileInfo {
path: PathBuf,
skytex: Option<bool>,
skylight: Option<bool>,
}
let info: SkyFileInfo = deserialize(data)?;
let info: SkyFileInfo = deserialize(message.as_ref())?;
info.path.metadata()?;
if info.skytex.unwrap_or_default() {
QUEUED_SKYTEX.lock().replace(info.path.clone());

View File

@@ -1,44 +1,274 @@
use super::Node;
use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::core::resource::ResourceID;
use crate::nodes::drawable::Drawable;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use anyhow::{anyhow, ensure, Result};
use crate::nodes::Message;
use crate::SK_MULTITHREAD;
use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::Mat4;
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use prisma::{Rgb, Rgba};
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::fmt::Error;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::Arc;
use stereokit::lifecycle::DrawContext;
use stereokit::material::Material;
use stereokit::model::Model as SKModel;
use stereokit::render::RenderLayer;
use stereokit::texture::Texture;
use stereokit::StereoKit;
use std::sync::{Arc, Weak};
use stereokit::named_colors::WHITE;
use stereokit::{
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
StereoKitMultiThread,
};
static MODEL_REGISTRY: Registry<Model> = Registry::new();
#[derive(Deserialize, Debug)]
#[serde(untagged)]
#[serde(tag = "t", content = "c")]
pub enum MaterialParameter {
Texture(PathBuf),
Float(f32),
Vector2(Vector2<f32>),
Vector3(Vector3<f32>),
Vector4(Vector4<f32>),
Color([f32; 4]),
Int(i32),
Int2(Vector2<i32>),
Int3(Vector3<i32>),
Int4(Vector4<i32>),
Bool(bool),
UInt(u32),
UInt2(Vector2<u32>),
UInt3(Vector3<u32>),
UInt4(Vector4<u32>),
Matrix(ColumnMatrix4<f32>),
Texture(ResourceID),
}
impl MaterialParameter {
fn apply_to_material(
&self,
client: &Client,
sk: &impl StereoKitMultiThread,
material: &Material,
parameter_name: &str,
) {
match self {
MaterialParameter::Float(val) => {
sk.material_set_float(material, parameter_name, *val);
}
MaterialParameter::Vector2(val) => {
sk.material_set_vector2(material, parameter_name, *val);
}
MaterialParameter::Vector3(val) => {
sk.material_set_vector3(material, parameter_name, *val);
}
MaterialParameter::Vector4(val) => {
sk.material_set_vector4(material, parameter_name, *val);
}
MaterialParameter::Color(val) => {
sk.material_set_color(material, parameter_name, Color128::from(val.clone()));
}
MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *val);
}
MaterialParameter::Int2(val) => {
sk.material_set_int2(material, parameter_name, val.x, val.y);
}
MaterialParameter::Int3(val) => {
sk.material_set_int3(material, parameter_name, val.x, val.y, val.z);
}
MaterialParameter::Int4(val) => {
sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z);
}
MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val);
}
MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val);
}
MaterialParameter::UInt2(val) => {
sk.material_set_uint2(material, parameter_name, val.x, val.y);
}
MaterialParameter::UInt3(val) => {
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z);
}
MaterialParameter::UInt4(val) => {
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z);
}
MaterialParameter::Matrix(val) => {
sk.material_set_matrix(material, parameter_name, Mat4::from(*val));
}
MaterialParameter::Texture(resource) => {
let Some(texture_path) = resource.get_file(
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("png"), OsStr::new("jpg")],
) else {return};
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
sk.material_set_texture(material, parameter_name, &tex);
}
}
}
}
}
pub struct ModelPart {
id: i32,
path: PathBuf,
space: Arc<Spatial>,
model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<SendWrapper<Material>>>>,
}
impl ModelPart {
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
let first_root_part = sk.model_node_get_root(sk_model);
let mut current_option_part = Some(first_root_part);
while let Some(current_part) = &mut current_option_part {
ModelPart::create(sk, model, sk_model, *current_part);
if let Some(child) = sk.model_node_child(sk_model, *current_part) {
*current_part = child;
} else if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
*current_part = sibling;
} else {
while let Some(current_part) = &mut current_option_part {
if let Some(sibling) = sk.model_node_sibling(sk_model, *current_part) {
*current_part = sibling;
break;
}
current_option_part = sk.model_node_parent(sk_model, *current_part);
}
}
}
}
fn create(
sk: &impl StereoKitMultiThread,
model: &Arc<Model>,
sk_model: &SKModel,
id: i32,
) -> Option<Arc<Self>> {
let parent_node = sk
.model_node_parent(sk_model, id)
.and_then(|id| model.parts.get(&id));
let parent_part = parent_node
.as_ref()
.and_then(|node| match node.drawable.get() {
Some(Drawable::ModelPart(model_part)) => Some(model_part),
_ => None,
});
let stardust_model_part = model.space.node()?;
let client = stardust_model_part.get_client()?;
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default();
part_path.push(sk.model_node_get_name(sk_model, id)?);
let node = client.scenegraph.add_node(Node::create(
&client,
stardust_model_part.get_path(),
part_path.to_str()?,
false,
));
let spatial_parent = parent_node
.and_then(|n| n.spatial.get().cloned())
.unwrap_or_else(|| model.space.clone());
let space = Spatial::add_to(
&node,
Some(spatial_parent),
sk.model_node_get_transform_local(sk_model, id),
false,
)
.ok()?;
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {return Bounds::default()};
let Some(sk) = SK_MULTITHREAD.get() else {return Bounds::default()};
let Some(model) = model_part.model.upgrade() else {return Bounds::default()};
let Some(sk_model) = model.sk_model.get() else {return Bounds::default()};
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {return Bounds::default()};
sk.mesh_get_bounds(sk_mesh)
});
let model_part = Arc::new(ModelPart {
id,
path: part_path,
space,
model: Arc::downgrade(model),
pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None),
});
node.add_local_signal(
"set_material_parameter",
ModelPart::set_material_parameter_flex,
);
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
model.parts.add(id, &node);
Some(model_part)
}
fn set_material_parameter_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
let (name, value): (String, MaterialParameter) = deserialize(message.as_ref())?;
model_part
.pending_material_parameters
.lock()
.insert(name, value);
Ok(())
}
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
self.pending_material_replacement
.lock()
.replace(replacement);
}
fn update(&self, sk: &impl StereoKitDraw) {
let Some(model) = self.model.upgrade() else {return};
let Some(sk_model) = model.sk_model.get() else {return};
let Some(node) = model.space.node() else {return};
let Some(client) = node.get_client() else {return};
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref());
}
let mut material_parameters = self.pending_material_parameters.lock();
for (parameter_name, parameter_value) in material_parameters.drain() {
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {continue};
let new_material = sk.material_copy(material);
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
sk.model_node_set_material(sk_model, self.id, &new_material);
}
sk.model_node_set_transform_model(
sk_model,
self.id,
Spatial::space_to_space_matrix(Some(&self.space), Some(&model.space)),
);
}
}
pub struct Model {
self_ref: Weak<Model>,
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
resource_id: ResourceID,
pending_model_path: OnceCell<PathBuf>,
pending_material_parameters: Mutex<FxHashMap<(u32, String), MaterialParameter>>,
pub pending_material_replacements: Mutex<FxHashMap<u32, Arc<SendWrapper<Material>>>>,
sk_model: OnceCell<SendWrapper<SKModel>>,
_resource_id: ResourceID,
sk_model: OnceCell<SKModel>,
parts: LifeLinkedNodeMap<i32>,
}
unsafe impl Send for Model {}
unsafe impl Sync for Model {}
impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
@@ -47,125 +277,73 @@ impl Model {
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.model.get().is_none(),
"Internal: Node already has a model attached!"
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let model = Model {
let pending_model_path = resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("glb"), OsStr::new("gltf")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model {
self_ref: self_ref.clone(),
enabled: node.enabled.clone(),
space: node.spatial.get().unwrap().clone(),
resource_id,
pending_model_path: OnceCell::new(),
pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacements: Mutex::new(FxHashMap::default()),
_resource_id: resource_id,
sk_model: OnceCell::new(),
};
node.add_local_signal("setMaterialParameter", Model::set_material_parameter);
let model_arc = MODEL_REGISTRY.add(model);
let _ = model_arc.pending_model_path.set(
model_arc
.resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| anyhow!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
)
.ok_or_else(|| anyhow!("Resource not found"))?,
parts: LifeLinkedNodeMap::default(),
});
MODEL_REGISTRY.add_raw(&model);
let sk = SK_MULTITHREAD.get().unwrap();
let sk_model = sk.model_copy(
sk.model_create_file(pending_model_path.to_str().unwrap(), None::<Shader>)?,
);
let _ = node.model.set(model_arc.clone());
Ok(model_arc)
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
let _ = model.sk_model.set(sk_model);
let _ = node.drawable.set(Drawable::Model(model.clone()));
Ok(model)
}
fn set_material_parameter(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct MaterialParameterInfo {
idx: u32,
name: String,
value: MaterialParameter,
fn draw(&self, sk: &impl StereoKitDraw) {
let Some(sk_model) = self.sk_model.get() else {return};
for model_node_node in self.parts.nodes() {
let Some(Drawable::ModelPart(model_node)) = model_node_node.drawable.get() else {continue};
model_node.update(sk);
}
let info: MaterialParameterInfo = deserialize(data)?;
node.model
.get()
.unwrap()
.pending_material_parameters
.lock()
.insert((info.idx, info.name), info.value);
Ok(())
}
fn draw(&self, sk: &StereoKit, draw_ctx: &DrawContext) {
let sk_model = self
.sk_model
.get_or_try_init(|| {
self.pending_model_path
.get()
.and_then(|path| SKModel::from_file(sk, path.as_path(), None))
.as_ref()
.cloned()
.map(SendWrapper::new)
.ok_or(Error)
})
.ok();
if let Some(sk_model) = sk_model {
{
let mut material_replacements = self.pending_material_replacements.lock();
for (material_idx, replacement_material) in material_replacements.iter() {
sk_model.set_material(*material_idx as i32, replacement_material);
}
material_replacements.clear();
}
{
let mut material_parameters = self.pending_material_parameters.lock();
for ((material_idx, parameter_name), parameter_value) in material_parameters.iter()
{
if let Some(material) = sk_model.get_material(sk, *material_idx as i32) {
match parameter_value {
MaterialParameter::Texture(path) => {
if let Some(tex) = Texture::from_file(sk, path.as_path(), true, 0) {
material.set_parameter(parameter_name.as_str(), &tex);
}
}
}
}
}
material_parameters.clear();
}
let global_transform = self.space.global_transform().into();
sk_model.draw(
draw_ctx,
global_transform,
Rgba::new(Rgb::new(1_f32, 1_f32, 1_f32), 1_f32),
RenderLayer::Layer0,
);
}
sk.model_draw(
sk_model,
self.space.global_transform(),
WHITE,
RenderLayer::LAYER0,
);
}
}
impl Drop for Model {
fn drop(&mut self) {
if let Some(model) = self.sk_model.take() {
destroy_queue::add(model);
}
MODEL_REGISTRY.remove(self);
}
}
pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) {
pub fn draw_all(sk: &impl StereoKitDraw) {
for model in MODEL_REGISTRY.get_valid_contents() {
model.draw(sk, draw_ctx);
if model.enabled.load(Ordering::Relaxed) {
model.draw(sk);
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateModelInfo<'a> {
name: &'a str,
@@ -173,12 +351,12 @@ pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Re
transform: Transform,
resource: ResourceID,
}
let info: CreateModelInfo = deserialize(data)?;
let info: CreateModelInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/model", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
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(())
}

View File

@@ -0,0 +1,139 @@
#![allow(dead_code)]
use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError,
};
use std::mem::transmute;
use stereokit::Shader;
use tracing::error;
struct FfiAssetHeader {
asset_type: i32,
asset_state: i32,
id: u64,
index: u64,
refs: i32,
debug: *mut u8,
}
struct FfiSkgShader {
meta: *mut u8,
vertex: u32,
pixel: u32,
program: u32,
compute: u32,
}
struct FfiShader {
header: FfiAssetHeader,
shader: FfiSkgShader,
}
unsafe fn compile_shader(
gl: &ffi::Gles2,
variant: ffi::types::GLuint,
src: &str,
) -> Result<ffi::types::GLuint, GlesError> {
let shader = gl.CreateShader(variant);
if shader == 0 {
return Err(GlesError::CreateShaderObject);
}
gl.ShaderSource(
shader,
1,
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
&(src.len() as i32) as *const _,
);
gl.CompileShader(shader);
let mut status = ffi::FALSE as i32;
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
if status == ffi::FALSE as i32 {
let mut max_len = 0;
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
let mut error = Vec::with_capacity(max_len as usize);
let mut len = 0;
gl.GetShaderInfoLog(
shader,
max_len as _,
&mut len as *mut _,
error.as_mut_ptr() as *mut _,
);
error.set_len(len as usize);
error!(
"[GL] {}",
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
);
gl.DeleteShader(shader);
return Err(GlesError::ShaderCompileError);
}
Ok(shader)
}
unsafe fn link_program(
gl: &ffi::Gles2,
vert: ffi::types::GLuint,
frag: ffi::types::GLuint,
) -> Result<ffi::types::GLuint, GlesError> {
let program = gl.CreateProgram();
gl.AttachShader(program, vert);
gl.AttachShader(program, frag);
gl.LinkProgram(program);
// gl.DetachShader(program, vert);
// gl.DetachShader(program, frag);
// gl.DeleteShader(vert);
// gl.DeleteShader(frag);
let mut status = ffi::FALSE as i32;
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
if status == ffi::FALSE as i32 {
let mut max_len = 0;
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
let mut error = Vec::with_capacity(max_len as usize);
let mut len = 0;
gl.GetProgramInfoLog(
program,
max_len as _,
&mut len as *mut _,
error.as_mut_ptr() as *mut _,
);
error.set_len(len as usize);
error!(
"[GL] {}",
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
);
gl.DeleteProgram(program);
return Err(GlesError::ProgramLinkError);
}
Ok(program)
}
pub unsafe fn shader_inject(
c: &Gles2,
sk_shader: &mut Shader,
vert_str: &str,
frag_str: &str,
) -> Result<(), GlesError> {
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
let gl_prog = link_program(c, gl_vert, gl_frag)?;
let shader: *mut FfiShader = transmute(sk_shader.0.as_mut());
if let Some(shader) = shader.as_mut() {
shader.shader.vertex = gl_vert;
shader.shader.pixel = gl_frag;
shader.shader.program = gl_prog;
}
Ok(())
}

View File

@@ -1,26 +1,23 @@
use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
nodes::{
drawable::Drawable,
spatial::{find_spatial_parent, parse_transform, Spatial},
Node,
Message, Node,
},
};
use anyhow::{anyhow, ensure, Result};
use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::{vec3, Mat4, Vec2};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use prisma::{Flatten, Rgb, Rgba};
use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Rgba};
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{path::PathBuf, sync::Arc};
use stereokit::{
font::Font,
lifecycle::DrawContext,
text::{self, TextAlign, TextFit, TextStyle},
StereoKit,
};
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle};
static TEXT_REGISTRY: Registry<Text> = Registry::new();
@@ -35,6 +32,7 @@ struct TextData {
}
pub struct Text {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceCell<SendWrapper<TextStyle>>,
@@ -59,17 +57,20 @@ impl Text {
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.model.get().is_none(),
"Internal: Node already has text attached!"
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let client = node
.get_client()
.ok_or_else(|| anyhow!("Client not found"))?;
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(),
space: node.spatial.get().unwrap().clone(),
font_path: font_resource_id
.and_then(|res| res.get_file(&client.base_resource_prefixes.lock().clone())),
font_path: font_resource_id.and_then(|res| {
res.get_file(
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("ttf"), OsStr::new("otf")],
)
}),
style: OnceCell::new(),
data: Mutex::new(TextData {
@@ -82,29 +83,26 @@ impl Text {
color,
}),
});
node.add_local_signal("setCharacterHeight", Text::set_character_height_flex);
node.add_local_signal("setText", Text::set_text_flex);
let _ = node.text.set(text.clone());
node.add_local_signal("set_character_height", Text::set_character_height_flex);
node.add_local_signal("set_text", Text::set_text_flex);
let _ = node.drawable.set(Drawable::Text(text.clone()));
Ok(text)
}
fn draw(&self, sk: &StereoKit, draw_ctx: &DrawContext) {
let style =
self.style
.get_or_try_init(|| -> Result<SendWrapper<TextStyle>, anyhow::Error> {
let font = if let Some(path) = self.font_path.as_deref() {
Font::from_file(sk, path)
} else {
Some(Font::default(sk))
};
Ok(SendWrapper::new(TextStyle::new(
sk,
font.ok_or(std::fmt::Error)?,
1.0,
Rgba::new(Rgb::new(1.0, 1.0, 1.0), 1.0),
)))
});
fn draw(&self, sk: &impl StereoKitDraw) {
let style = self.style.get_or_try_init(
|| -> Result<SendWrapper<TextStyle>, color_eyre::eyre::Error> {
let font = self
.font_path
.as_deref()
.and_then(|path| sk.font_create(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
Ok(SendWrapper::new(unsafe {
sk.text_make_style(font, 1.0, WHITE)
}))
},
);
if let Ok(style) = style {
let data = self.data.lock();
@@ -115,28 +113,36 @@ impl Text {
data.character_height,
));
if let Some(bounds) = data.bounds {
text::draw_in(
draw_ctx,
sk.text_add_in(
&data.text,
transform,
bounds / data.character_height,
data.fit,
style,
**style,
data.bounds_align,
data.text_align,
vec3(0.0, 0.0, 0.0),
data.color,
Color128::from([
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
);
} else {
text::draw_at(
draw_ctx,
sk.text_add_at(
&data.text,
transform,
style,
**style,
data.bounds_align,
data.text_align,
vec3(0.0, 0.0, 0.0),
data.color,
Color128::from([
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
);
}
}
@@ -145,16 +151,22 @@ impl Text {
pub fn set_character_height_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
let height = flexbuffers::Reader::get_root(data)?.get_f64()? as f32;
node.text.get().unwrap().data.lock().character_height = height;
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
text.data.lock().character_height = deserialize(message.as_ref())?;
Ok(())
}
pub fn set_text_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let text = flexbuffers::Reader::get_root(data)?.get_str()?.to_string();
node.text.get().unwrap().data.lock().text = text;
pub fn set_text_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
text.data.lock().text = deserialize(message.as_ref())?;
Ok(())
}
}
@@ -167,13 +179,15 @@ impl Drop for Text {
}
}
pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) {
pub fn draw_all(sk: &impl StereoKitDraw) {
for text in TEXT_REGISTRY.get_valid_contents() {
text.draw(sk, draw_ctx);
if text.enabled.load(Ordering::Relaxed) {
text.draw(sk);
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
@@ -188,14 +202,14 @@ pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Re
bounds_align: TextAlign,
color: [f32; 4],
}
let info: CreateTextInfo = deserialize(data)?;
let info: CreateTextInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true)?;
let transform = parse_transform(info.transform, true, true, true);
let color = Rgba::from_slice(&info.color);
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Text::add_to(
&node,
info.font_resource,

View File

@@ -1,7 +1,8 @@
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use anyhow::{ensure, Result};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{vec3, vec3a, Vec3, Vec3A};
use mint::Vector3;
use parking_lot::Mutex;
@@ -16,7 +17,7 @@ pub struct BoxField {
}
impl BoxField {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<()> {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<Arc<Field>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
@@ -30,19 +31,24 @@ impl BoxField {
size: Mutex::new(size.into()),
};
box_field.add_field_methods(node);
node.add_local_signal("setSize", BoxField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Box(box_field)));
Ok(())
node.add_local_signal("set_size", BoxField::set_size_flex);
let field = Arc::new(Field::Box(box_field));
let _ = node.field.set(field.clone());
Ok(field)
}
pub fn set_size(&self, size: Vector3<f32>) {
*self.size.lock() = size.into();
}
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let Field::Box(box_field) = node.field.get().unwrap().as_ref() {
box_field.set_size(deserialize(data)?);
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
box_field.set_size(deserialize(message.as_ref())?);
Ok(())
}
}
@@ -63,7 +69,11 @@ impl FieldTrait for BoxField {
}
}
pub fn create_box_field_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
pub fn create_box_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
@@ -71,12 +81,12 @@ pub fn create_box_field_flex(_node: &Node, calling_client: Arc<Client>, data: &[
transform: Transform,
size: Vector3<f32>,
}
let info: CreateFieldInfo = deserialize(data)?;
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
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(())
}

View File

@@ -1,7 +1,8 @@
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use anyhow::{ensure, Result};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use serde::Deserialize;
@@ -33,7 +34,7 @@ impl CylinderField {
radius: AtomicF32::new(radius.abs()),
};
cylinder_field.add_field_methods(node);
node.add_local_signal("setSize", CylinderField::set_size_flex);
node.add_local_signal("set_size", CylinderField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field)));
Ok(())
}
@@ -43,11 +44,14 @@ impl CylinderField {
self.radius.store(radius.abs(), Ordering::Relaxed);
}
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() {
let (length, radius) = deserialize(data)?;
cylinder_field.set_size(length, radius);
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (length, radius) = deserialize(message.as_ref())?;
cylinder_field.set_size(length, radius);
Ok(())
}
}
@@ -68,7 +72,7 @@ impl FieldTrait for CylinderField {
pub fn create_cylinder_field_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
@@ -78,12 +82,12 @@ pub fn create_cylinder_field_flex(
length: f32,
radius: f32,
}
let info: CreateFieldInfo = deserialize(data)?;
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
CylinderField::add_to(&node, dbg!(info.length), dbg!(info.radius))?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
CylinderField::add_to(&node, info.length, info.radius)?;
Ok(())
}

View File

@@ -1,25 +1,42 @@
mod r#box;
pub mod r#box;
mod cylinder;
mod sphere;
mod torus;
use self::cylinder::{create_cylinder_field_flex, CylinderField};
use self::r#box::{create_box_field_flex, BoxField};
use self::sphere::{create_sphere_field_flex, SphereField};
use self::torus::{create_torus_field_flex, TorusField};
use super::alias::AliasInfo;
use super::spatial::Spatial;
use super::Node;
use super::{Message, Node};
use crate::core::client::Client;
use crate::nodes::spatial::find_reference_space;
use anyhow::Result;
use color_eyre::eyre::Result;
use glam::{vec2, vec3a, Vec3, Vec3A};
use mint::Vector3;
use serde::Deserialize;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::ops::Deref;
use std::sync::Arc;
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
server_methods: vec!["distance", "normal", "closest_point", "ray_march"],
..Default::default()
});
pub trait FieldTrait {
fn add_field_methods(&self, node: &Arc<Node>) {
node.add_local_method("distance", field_distance_flex);
node.add_local_method("normal", field_normal_flex);
node.add_local_method("closest_point", field_closest_point_flex);
node.add_local_method("ray_march", field_ray_march_flex);
}
fn spatial_ref(&self) -> &Spatial;
fn local_distance(&self, p: Vec3A) -> f32;
fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
let d = self.local_distance(p);
@@ -61,93 +78,36 @@ pub trait FieldTrait {
.transform_point3a(self.local_closest_point(local_p, r))
}
fn add_field_methods(&self, node: &Arc<Node>) {
node.add_local_method("distance", field_distance_flex);
node.add_local_method("normal", field_normal_flex);
node.add_local_method("closest_point", field_closest_point_flex);
}
fn ray_march(&self, ray: Ray) -> RayMarchResult {
let mut result = RayMarchResult {
min_distance: f32::MAX,
deepest_point_distance: 0_f32,
ray_length: 0_f32,
ray_steps: 0,
};
fn spatial_ref(&self) -> &Spatial;
}
let ray_to_field_matrix =
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
let ray_direction = ray_to_field_matrix.transform_vector3a(ray.direction.into());
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)?;
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);
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)?;
result.ray_length += march_distance;
ray_point += ray_direction * march_distance;
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)?;
if result.min_distance > distance {
result.deepest_point_distance = result.ray_length;
result.min_distance = distance;
}
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))?)
}
pub enum Field {
Box(BoxField),
Cylinder(CylinderField),
Sphere(SphereField),
}
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,
result.ray_steps += 1;
}
}
}
pub fn create_interface(client: &Arc<Client>) {
let node = Node::create(client, "", "field", false);
node.add_local_signal("createBoxField", create_box_field_flex);
node.add_local_signal("createCylinderField", create_cylinder_field_flex);
node.add_local_signal("createSphereField", create_sphere_field_flex);
node.add_to_scenegraph();
result
}
}
pub struct Ray {
@@ -156,9 +116,9 @@ pub struct Ray {
pub space: Arc<Spatial>,
}
#[derive(Debug, Serialize)]
pub struct RayMarchResult {
pub ray: Ray,
pub distance: f32,
pub min_distance: f32,
pub deepest_point_distance: f32,
pub ray_length: f32,
pub ray_steps: u32,
@@ -173,40 +133,121 @@ const MAX_RAY_MARCH: f32 = f32::MAX;
// const MIN_RAY_LENGTH: f32 = 0_f32;
const MAX_RAY_LENGTH: f32 = 1000_f32;
pub fn ray_march(ray: Ray, field: &Field) -> RayMarchResult {
let mut result = RayMarchResult {
ray,
distance: f32::MAX,
deepest_point_distance: 0_f32,
ray_length: 0_f32,
ray_steps: 0,
};
let ray_to_field_matrix =
Spatial::space_to_space_matrix(Some(&result.ray.space), Some(field.spatial_ref()));
let mut ray_point = ray_to_field_matrix.transform_point3a(result.ray.origin.into());
let ray_direction = ray_to_field_matrix.transform_vector3a(result.ray.direction.into());
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
let distance = field.local_distance(ray_point);
let march_distance = distance.clamp(MIN_RAY_MARCH, MAX_RAY_MARCH);
result.ray_length += march_distance;
ray_point += ray_direction * march_distance;
if result.distance > distance {
result.deepest_point_distance = result.ray_length;
}
result.distance = distance.min(result.distance);
result.ray_steps += 1;
fn field_distance_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
result
let distance = node
.field
.get()
.unwrap()
.distance(reference_space.as_ref(), args.point.into());
Ok(serialize(distance)?.into())
}
fn field_normal_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let normal = node.field.get().as_ref().unwrap().normal(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(normal))?.into())
}
fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let closest_point = node.field.get().as_ref().unwrap().closest_point(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(closest_point))?.into())
}
fn field_ray_march_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
ray_origin: Vector3<f32>,
ray_direction: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let ray_march_result = node.field.get().unwrap().ray_march(Ray {
origin: args.ray_origin.into(),
direction: args.ray_direction.into(),
space: reference_space,
});
Ok(serialize(ray_march_result)?.into())
}
pub enum Field {
Box(BoxField),
Cylinder(CylinderField),
Sphere(SphereField),
Torus(TorusField),
}
impl Deref for Field {
type Target = dyn FieldTrait;
fn deref(&self) -> &Self::Target {
match self {
Field::Box(field) => field,
Field::Cylinder(field) => field,
Field::Sphere(field) => field,
Field::Torus(field) => field,
}
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "field", false);
node.add_local_signal("create_box_field", create_box_field_flex);
node.add_local_signal("create_cylinder_field", create_cylinder_field_flex);
node.add_local_signal("create_sphere_field", create_sphere_field_flex);
node.add_local_signal("create_torus_field", create_torus_field_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn find_field(client: &Client, path: &str) -> Result<Arc<Field>> {
Ok(client
client
.get_node("Field", path)?
.get_aspect("Field", "info", |n| &n.field)?)
.get_aspect("Field", "info", |n| &n.field)
.cloned()
}

View File

@@ -1,7 +1,8 @@
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, Spatial};
use anyhow::{ensure, Result};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{Mat4, Vec3A};
use mint::Vector3;
use portable_atomic::AtomicF32;
@@ -30,7 +31,7 @@ impl SphereField {
radius: AtomicF32::new(radius),
};
sphere_field.add_field_methods(node);
node.add_local_signal("setRadius", SphereField::set_radius_flex);
node.add_local_signal("set_radius", SphereField::set_radius_flex);
let _ = node.field.set(Arc::new(Field::Sphere(sphere_field)));
Ok(())
}
@@ -39,11 +40,13 @@ impl SphereField {
self.radius.store(radius, Ordering::Relaxed);
}
pub fn set_radius_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let radius = flexbuffers::Reader::get_root(data)?.get_f64()? as f32;
if let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() {
sphere_field.set_radius(radius);
}
pub fn set_radius_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
sphere_field.set_radius(deserialize(message.as_ref())?);
Ok(())
}
}
@@ -66,7 +69,7 @@ impl FieldTrait for SphereField {
pub fn create_sphere_field_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
@@ -75,7 +78,7 @@ pub fn create_sphere_field_flex(
origin: Option<Vector3<f32>>,
radius: f32,
}
let info: CreateFieldInfo = deserialize(data)?;
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = Mat4::from_translation(
@@ -83,8 +86,8 @@ pub fn create_sphere_field_flex(
.unwrap_or_else(|| Vector3::from([0.0; 3]))
.into(),
);
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
SphereField::add_to(&node, info.radius)?;
Ok(())
}

93
src/nodes/fields/torus.rs Normal file
View File

@@ -0,0 +1,93 @@
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct TorusField {
space: Arc<Spatial>,
radius_a: AtomicF32,
radius_b: AtomicF32,
}
impl TorusField {
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let torus_field = TorusField {
space: node.spatial.get().unwrap().clone(),
radius_a: AtomicF32::new(radius_a.abs()),
radius_b: AtomicF32::new(radius_b.abs()),
};
torus_field.add_field_methods(node);
node.add_local_signal("set_size", TorusField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Torus(torus_field)));
Ok(())
}
pub fn set_size(&self, radius_a: f32, radius_b: f32) {
self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (radius_a, radius_b) = deserialize(message.as_ref())?;
torus_field.set_size(radius_a, radius_b);
Ok(())
}
}
impl FieldTrait for TorusField {
fn local_distance(&self, p: Vec3A) -> f32 {
let radius_a = self.radius_a.load(Ordering::Relaxed);
let radius_b = self.radius_b.load(Ordering::Relaxed);
let q = vec2(p.xz().length() - radius_a, p.y);
q.length() - radius_b
}
fn spatial_ref(&self) -> &Spatial {
self.space.as_ref()
}
}
pub fn create_torus_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
radius_a: f32,
radius_b: f32,
}
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
TorusField::add_to(&node, info.radius_a, info.radius_b)?;
Ok(())
}

View File

@@ -3,9 +3,11 @@ use crate::{
core::client::{Client, INTERNAL_CLIENT},
nodes::alias::AliasInfo,
};
use color_eyre::eyre::Result;
use glam::{vec3, Mat4};
use std::sync::Arc;
use stereokit::StereoKit;
use stereokit::StereoKitMultiThread;
use tracing::instrument;
lazy_static::lazy_static! {
static ref HMD: Arc<Node> = create();
@@ -13,13 +15,17 @@ lazy_static::lazy_static! {
fn create() -> Arc<Node> {
let node = Arc::new(Node::create(&INTERNAL_CLIENT, "", "hmd", false));
Spatial::add_to(&node, None, Mat4::IDENTITY).unwrap();
Spatial::add_to(&node, None, Mat4::IDENTITY, false).expect("Unable to make spatial for HMD");
node
}
pub fn frame(sk: &StereoKit) {
let spatial = HMD.spatial.get().unwrap();
#[instrument(level = "debug", name = "Update HMD Pose", skip(sk))]
pub fn frame(sk: &impl StereoKitMultiThread) {
let spatial = HMD
.spatial
.get()
.expect("Unable to get spatial to update HMD");
let hmd_pose = sk.input_head();
*spatial.transform.lock() = Mat4::from_scale_rotation_translation(
vec3(1.0, 1.0, 1.0),
@@ -28,14 +34,14 @@ pub fn frame(sk: &StereoKit) {
);
}
pub fn make_alias(client: &Arc<Client>) -> Arc<Node> {
Alias::new(
pub fn make_alias(client: &Arc<Client>) -> Result<Arc<Node>> {
Alias::create(
client,
"",
"hmd",
&HMD,
AliasInfo {
local_signals: vec!["getTransform"],
server_signals: vec!["get_bounds", "get_transform"],
..Default::default()
},
)

View File

@@ -11,7 +11,10 @@ pub struct Hand {
pub base: FlatHand,
}
impl InputSpecialization for Hand {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
self.true_distance(space, field).abs()
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let mut min_distance = f32::MAX;
for tip in [
@@ -60,9 +63,8 @@ impl InputSpecialization for Hand {
]);
for joint in joints {
let joint_matrix =
Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into())
* local_to_handler_matrix;
let joint_matrix = local_to_handler_matrix
* Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into());
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into();
joint.rotation = rotation.into();

View File

@@ -6,30 +6,37 @@ use self::hand::Hand;
use self::pointer::Pointer;
use self::tip::Tip;
use super::fields::Field;
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
use super::Node;
use crate::core::client::Client;
use crate::core::eventloop::FRAME;
use crate::core::registry::Registry;
use crate::nodes::fields::find_field;
use anyhow::{ensure, Result};
use super::{
alias::{Alias, AliasInfo},
fields::{find_field, Field, FIELD_ALIAS_INFO},
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
};
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry};
use color_eyre::eyre::{ensure, Result};
use glam::Mat4;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::AtomicBool;
use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType};
use stardust_xr::schemas::{flat::InputData, flex::deserialize};
use stardust_xr::schemas::{
flat::{Datamap, InputDataType},
flex::serialize,
};
use stardust_xr::values::Transform;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak};
use tracing::{debug_span, instrument};
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
pub trait InputSpecialization: Send + Sync {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32;
fn serialize(
&self,
distance_link: &DistanceLink,
@@ -53,63 +60,133 @@ impl Deref for InputType {
}
pub struct InputMethod {
pub uid: String,
node: Weak<Node>,
uid: String,
pub enabled: Mutex<bool>,
pub spatial: Arc<Spatial>,
pub specialization: Mutex<InputType>,
pub captures: Registry<InputHandler>,
captures: Registry<InputHandler>,
pub datamap: Mutex<Option<Datamap>>,
handler_aliases: LifeLinkedNodeMap<String>,
handler_order: OnceCell<Mutex<Vec<Weak<InputHandler>>>>,
}
impl InputMethod {
pub fn new(spatial: Arc<Spatial>, specialization: InputType) -> Arc<InputMethod> {
let method = InputMethod {
uid: nanoid!(),
enabled: Mutex::new(true),
spatial,
specialization: Mutex::new(specialization),
captures: Registry::new(),
datamap: Mutex::new(None),
};
INPUT_METHOD_REGISTRY.add(method)
}
#[allow(dead_code)]
pub fn add_to(
node: &Arc<Node>,
specialization: InputType,
datamap: Option<Datamap>,
) -> Result<()> {
) -> Result<Arc<InputMethod>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
node.add_local_signal("setDatamap", InputMethod::set_datamap);
node.add_local_signal("capture", InputMethod::capture_flex);
node.add_local_signal("set_datamap", InputMethod::set_datamap_flex);
node.add_local_signal("set_handlers", InputMethod::set_handlers_flex);
let method = InputMethod {
node: Arc::downgrade(node),
uid: node.uid.clone(),
enabled: Mutex::new(true),
spatial: node.spatial.get().unwrap().clone(),
specialization: Mutex::new(specialization),
captures: Registry::new(),
datamap: Mutex::new(datamap),
handler_aliases: LifeLinkedNodeMap::default(),
handler_order: OnceCell::new(),
};
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
}
let method = INPUT_METHOD_REGISTRY.add(method);
let _ = node.input_method.set(method);
let _ = node.input_method.set(method.clone());
Ok(method)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Method", "input method", |n| &n.input_method)
.cloned()
}
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
let handler = InputHandler::find(&calling_client, deserialize(message.as_ref())?)?;
method.captures.add_raw(&handler);
node.send_remote_signal("capture", message)
}
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
method.datamap.lock().replace(Datamap::new(message.data)?);
Ok(())
}
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
let handler_paths: Vec<&str> = deserialize(message.as_ref())?;
let handlers: Vec<Weak<InputHandler>> = handler_paths
.into_iter()
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
.map(|h| Arc::downgrade(&h))
.collect();
*method
.handler_order
.get_or_init(|| Mutex::new(Vec::new()))
.lock() = handlers;
Ok(())
}
fn set_datamap(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
node.input_method
.get()
.unwrap()
.datamap
fn compare_distance(&self, to: &Field) -> f32 {
self.specialization
.lock()
.replace(Datamap::new(data.to_vec())?);
Ok(())
.compare_distance(&self.spatial, to)
}
fn true_distance(&self, to: &Field) -> f32 {
self.specialization.lock().true_distance(&self.spatial, to)
}
fn distance(&self, to: &Field) -> f32 {
self.specialization.lock().distance(&self.spatial, to)
fn handle_new_handler(&self, handler: &InputHandler) {
let Some(method_node) = self.node.upgrade() else {return};
let Some(method_client) = method_node.get_client() else {return};
let Some(handler_node) = handler.node.upgrade() else {return};
// Receiver itself
let Ok(handler_alias) = Alias::create(
&method_client,
method_node.get_path(),
handler.uid.as_str(),
&handler_node,
AliasInfo {
server_methods: vec!["getTransform"],
..Default::default()
},
) else {return};
self.handler_aliases
.add(handler.uid.clone(), &handler_alias);
if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() {
// Handler's field
let Ok(rx_field_alias) = Alias::create(
&method_client,
handler_alias.get_path(),
"field",
&handler_field_node,
FIELD_ALIAS_INFO.clone(),
) else {return};
self.handler_aliases
.add(handler.uid.clone() + "-field", &rx_field_alias);
}
let Ok(data) = serialize(&handler.uid) else {return};
let _ = method_node.send_remote_signal("handler_created", data);
}
fn handle_drop_handler(&self, handler: &InputHandler) {
let uid = handler.uid.as_str();
self.handler_aliases.remove(uid);
self.handler_aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {return};
let Ok(data) = serialize(&uid) else {return};
let _ = tx_node.send_remote_signal("handler_destroyed", data);
}
}
impl Drop for InputMethod {
@@ -119,26 +196,37 @@ impl Drop for InputMethod {
}
pub struct DistanceLink {
pub distance: f32,
pub method: Arc<InputMethod>,
pub handler: Arc<InputHandler>,
pub handler_field: Arc<Field>,
distance: f32,
method: Arc<InputMethod>,
handler: Arc<InputHandler>,
}
impl DistanceLink {
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> {
let handler_field = handler.field.upgrade()?;
let handler_node = handler.node.upgrade()?;
let method_alias = Alias::create(
&handler_node.get_client()?,
handler_node.get_path(),
&method.uid,
&method.node.upgrade()?,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
)
.ok()?;
handler.method_aliases.add(Arc::downgrade(&method_alias));
Some(DistanceLink {
distance: method.distance(&handler_field),
distance: method.compare_distance(&handler.field),
method,
handler,
handler_field,
})
}
fn send_input(&self, frame: u64, datamap: Datamap) {
self.handler.send_input(frame, self, datamap);
fn send_input(&self, order: u32, datamap: Datamap) {
self.handler.send_input(order, self, datamap);
}
fn serialize(&self, datamap: Datamap) -> Vec<u8> {
#[instrument(level = "debug", skip(self))]
fn serialize(&self, order: u32, datamap: Datamap) -> Vec<u8> {
let input = self.method.specialization.lock().serialize(
self,
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
@@ -147,17 +235,21 @@ impl DistanceLink {
let root = InputData {
uid: self.method.uid.clone(),
input,
distance: self.distance,
distance: self.method.true_distance(&self.handler.field),
datamap,
order,
};
root.serialize()
}
}
pub struct InputHandler {
enabled: Arc<AtomicBool>,
uid: String,
node: Weak<Node>,
spatial: Arc<Spatial>,
pub field: Weak<Field>,
field: Arc<Field>,
method_aliases: LifeLinkedNodeList,
}
impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
@@ -167,58 +259,60 @@ impl InputHandler {
);
let handler = InputHandler {
enabled: node.enabled.clone(),
uid: node.uid.clone(),
node: Arc::downgrade(node),
spatial: node.spatial.get().unwrap().clone(),
field: Arc::downgrade(field),
field: field.clone(),
method_aliases: LifeLinkedNodeList::default(),
};
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
}
let handler = INPUT_HANDLER_REGISTRY.add(handler);
let _ = node.input_handler.set(handler);
Ok(())
}
fn find(client: &Client, path: &str) -> Result<Arc<Self>> {
InputHandler::get(&*client.get_node("Input Handler", path)?)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Handler", "input handler", |n| &n.input_handler)
.cloned()
}
fn send_input(&self, frame: u64, distance_link: &DistanceLink, datamap: Datamap) {
let data = distance_link.serialize(datamap);
let node = self.node.upgrade().unwrap();
let method = Arc::downgrade(&distance_link.method);
let handler = Arc::downgrade(&distance_link.handler);
tokio::spawn(async move {
let data = node.execute_remote_method("input", data).await;
if frame == FRAME.load(Ordering::Relaxed) {
if let Ok(data) = data {
let capture = flexbuffers::Reader::get_root(data.as_slice())
.and_then(|data| data.get_bool())
.unwrap_or(false);
if let Some(method) = method.upgrade() {
if let Some(handler) = handler.upgrade() {
if capture {
method.captures.add_raw(&handler);
}
}
}
}
}
});
#[instrument(level = "debug", skip(self, distance_link))]
fn send_input(&self, order: u32, distance_link: &DistanceLink, datamap: Datamap) {
let Some(node) = self.node.upgrade() else {return};
let _ = node.send_remote_signal("input", distance_link.serialize(order, datamap));
}
}
impl PartialEq for InputHandler {
fn eq(&self, other: &Self) -> bool {
self.spatial == other.spatial
}
}
impl Drop for InputHandler {
fn drop(&mut self) {
INPUT_HANDLER_REGISTRY.remove(self);
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.handle_drop_handler(self);
}
}
}
pub fn create_interface(client: &Arc<Client>) {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "input", false);
node.add_local_signal("createInputHandler", create_input_handler_flex);
node.add_local_signal("createInputMethodTip", tip::create_tip_flex);
node.add_to_scenegraph();
node.add_local_signal("create_input_handler", create_input_handler_flex);
node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_input_handler_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateInputHandlerInfo<'a> {
@@ -227,52 +321,75 @@ pub fn create_input_handler_flex(
transform: Transform,
field_path: &'a str,
}
let info: CreateInputHandlerInfo = deserialize(data)?;
let info: CreateInputHandlerInfo = deserialize(message.as_ref())?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let transform = parse_transform(info.transform, true, true, true);
let field = find_field(&calling_client, info.field_path)?;
let node = Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
let node =
Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
InputHandler::add_to(&node, &field)?;
Ok(())
}
#[tracing::instrument(level = "debug")]
pub fn process_input() {
for method in INPUT_METHOD_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|method| *method.enabled.lock())
.filter(|method| method.datamap.lock().is_some())
{
let mut distance_links: Vec<DistanceLink> = INPUT_HANDLER_REGISTRY
// Iterate over all valid input methods
let methods = debug_span!("Get valid methods").in_scope(|| {
INPUT_METHOD_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|handler| handler.field.upgrade().is_some())
.filter_map(|handler| DistanceLink::from(method.clone(), handler))
.collect();
distance_links.sort_unstable_by(|a, b| {
a.distance
.abs()
.partial_cmp(&b.distance.abs())
.unwrap()
.reverse()
});
.filter(|method| *method.enabled.lock())
.filter(|method| method.datamap.lock().is_some())
});
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
for handler in &handlers {
handler.method_aliases.clear();
}
for method in methods {
debug_span!("Process input method").in_scope(|| {
// Get all valid input handlers and convert them to DistanceLink objects
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links")
.in_scope(|| {
if let Some(handler_order) = method.handler_order.get() {
let handler_order = handler_order.lock();
handler_order
.iter()
.filter_map(|h| h.upgrade())
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.filter_map(|handler| DistanceLink::from(method.clone(), handler))
.collect()
} else {
let mut distance_links: Vec<_> = handlers
.iter()
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.filter_map(|handler| {
DistanceLink::from(method.clone(), handler.clone())
})
.collect();
let mut last_distance = 0.0;
let frame = FRAME.load(Ordering::Relaxed);
let captures = method.captures.get_valid_contents();
for distance_link in distance_links {
distance_link.send_input(frame, method.datamap.lock().clone().unwrap());
if last_distance != distance_link.distance
&& captures
.iter()
.any(|c| Arc::ptr_eq(c, &distance_link.handler))
{
break;
// Sort the distance links by their distance in ascending order
debug_span!("Sort distance links").in_scope(|| {
distance_links.sort_unstable_by(|a, b| {
a.distance.abs().partial_cmp(&b.distance.abs()).unwrap()
});
});
distance_links
}
});
let captures = method.captures.take_valid_contents();
// Iterate over the distance links and send input to them
for (i, distance_link) in distance_links.into_iter().enumerate() {
distance_link.send_input(i as u32, method.datamap.lock().clone().unwrap());
// If the current distance link is in the list of captured input handlers,
// break out of the loop to avoid sending input to the remaining distance links
if captures.contains(&distance_link.handler) {
break;
}
}
last_distance = distance_link.distance;
}
method.captures.clear();
});
}
}

View File

@@ -1,12 +1,18 @@
use super::{DistanceLink, InputSpecialization};
use crate::nodes::fields::{ray_march, Field, Ray, RayMarchResult};
use crate::nodes::spatial::Spatial;
use crate::core::client::Client;
use crate::nodes::fields::{Field, Ray, RayMarchResult};
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::{Message, Node};
use glam::{vec3, Mat4};
use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::Arc;
#[derive(Default)]
pub struct Pointer {}
pub struct Pointer;
// impl Default for Pointer {
// fn default() -> Self {
// Pointer {
@@ -17,20 +23,24 @@ pub struct Pointer {}
// }
impl Pointer {
fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
ray_march(
Ray {
origin: vec3(0_f32, 0_f32, 0_f32),
direction: vec3(0_f32, 0_f32, 1_f32),
space: space.clone(),
},
field,
)
field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: space.clone(),
})
}
}
impl InputSpecialization for Pointer {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
self.ray_march(space, field).distance
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
ray_info
.deepest_point_distance
.hypot(ray_info.min_distance.recip())
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
ray_info.min_distance
}
fn serialize(
&self,
@@ -38,11 +48,8 @@ impl InputSpecialization for Pointer {
local_to_handler_matrix: Mat4,
) -> InputDataType {
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
let direction = local_to_handler_matrix.transform_vector3(vec3(0_f32, 0_f32, 1_f32));
let ray_march = self.ray_march(
&distance_link.method.spatial,
&distance_link.handler.field.upgrade().unwrap(),
);
let direction = local_to_handler_matrix.transform_vector3(vec3(0.0, 0.0, -1.0));
let ray_march = self.ray_march(&distance_link.method.spatial, &distance_link.handler.field);
let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
InputDataType::Pointer(FlatPointer {
@@ -52,3 +59,30 @@ impl InputSpecialization for Pointer {
})
}
}
pub fn create_pointer_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> color_eyre::eyre::Result<()> {
#[derive(Deserialize)]
struct CreatePointerInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
datamap: Option<Vec<u8>>,
}
let info: CreatePointerInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to(
&node,
InputType::Pointer(Pointer),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
)?;
Ok(())
}

View File

@@ -3,8 +3,8 @@ use crate::core::client::Client;
use crate::nodes::fields::Field;
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Node;
use anyhow::Result;
use crate::nodes::{Message, Node};
use color_eyre::eyre::Result;
use glam::{vec3a, Mat4};
use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip};
@@ -17,15 +17,18 @@ pub struct Tip {
pub radius: f32,
}
impl Tip {
fn set_radius(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
fn set_radius(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
if let InputType::Tip(tip) = &mut *node.input_method.get().unwrap().specialization.lock() {
tip.radius = deserialize(data)?;
tip.radius = deserialize(message.as_ref())?;
}
Ok(())
}
}
impl InputSpecialization for Tip {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0))
}
fn serialize(
@@ -42,7 +45,7 @@ impl InputSpecialization for Tip {
}
}
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTipInfo<'a> {
name: &'a str,
@@ -51,13 +54,13 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
radius: f32,
datamap: Option<Vec<u8>>,
}
let info: CreateTipInfo = deserialize(data)?;
let info: CreateTipInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/input/method/tip", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to(
&node,
InputType::Tip(Tip {
@@ -65,6 +68,6 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
}),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
)?;
node.add_local_signal("setRadius", Tip::set_radius);
node.add_local_signal("set_radius", Tip::set_radius);
Ok(())
}

View File

@@ -1,12 +1,18 @@
use super::{Item, ItemSpecialization, ItemType, ITEM_TYPE_INFO_ENVIRONMENT};
use super::{Item, ItemType};
use crate::{
core::client::{Client, INTERNAL_CLIENT},
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
items::TypeInfo,
spatial::{find_spatial_parent, parse_transform, Spatial},
Node,
Message, Node,
},
};
use anyhow::{anyhow, Result};
use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use serde::Deserialize;
use stardust_xr::{
schemas::flex::{deserialize, serialize},
@@ -14,6 +20,18 @@ use stardust_xr::{
};
use std::sync::Arc;
lazy_static! {
pub(super) static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
type_name: "environment",
aliased_local_signals: vec!["apply_sky_tex", "apply_sky_light"],
aliased_local_methods: vec![],
aliased_remote_signals: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
pub struct EnvironmentItem {
path: String,
}
@@ -21,30 +39,33 @@ impl EnvironmentItem {
pub fn add_to(node: &Arc<Node>, path: String) {
Item::add_to(
node,
nanoid!(),
&ITEM_TYPE_INFO_ENVIRONMENT,
ItemType::Environment(EnvironmentItem { path }),
);
node.add_local_method("getPath", EnvironmentItem::get_path_flex);
node.add_local_method("get_path", EnvironmentItem::get_path_flex);
}
fn get_path_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<Vec<u8>> {
let path: Result<String> = match &node.item.get().unwrap().specialization {
ItemType::Environment(env) => Ok(env.path.clone()),
_ => Err(anyhow!("")),
fn get_path_flex(
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<Message> {
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
return Err(eyre!("Wrong item type?"))
};
Ok(flexbuffers::singleton(path?.as_str()))
Ok(serialize(environment_item.path.as_str())?.into())
}
}
impl ItemSpecialization for EnvironmentItem {
fn serialize_start_data(&self, id: &str) -> Vec<u8> {
serialize((id, self.path.as_str())).unwrap()
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize((id, self.path.as_str()))?.into())
}
}
pub(super) fn create_environment_item_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateEnvironmentItemInfo<'a> {
@@ -53,33 +74,18 @@ pub(super) fn create_environment_item_flex(
transform: Transform,
item_data: String,
}
let info: CreateEnvironmentItemInfo = deserialize(data)?;
let parent_name = format!("/item/{}/item/", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let node = Node::create(&INTERNAL_CLIENT, &parent_name, info.name, true);
let info: CreateEnvironmentItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let space = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, None, transform * space.global_transform())?;
let transform = parse_transform(info.transform, true, true, false);
let node =
Node::create(&INTERNAL_CLIENT, &parent_name, info.name, false).add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false)?;
EnvironmentItem::add_to(&node, info.item_data);
node.item
.get()
.unwrap()
.make_alias(&calling_client, &parent_name);
.make_alias_named(&calling_client, &parent_name, info.name)?;
Ok(())
}
pub(super) fn create_environment_item_acceptor_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
super::create_item_acceptor_flex(calling_client, data, &ITEM_TYPE_INFO_ENVIRONMENT)
}
pub(super) fn register_environment_item_ui_flex(
_node: &Node,
calling_client: Arc<Client>,
_data: &[u8],
) -> Result<()> {
super::register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_ENVIRONMENT)
}

View File

@@ -1,66 +1,58 @@
mod environment;
pub mod panel;
use self::environment::EnvironmentItem;
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL};
use super::fields::Field;
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
use super::{Alias, Node};
use crate::core::client::{Client, INTERNAL_CLIENT};
use crate::core::nodelist::LifeLinkedNodeList;
use super::{Alias, Message, Node};
use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field;
use crate::wayland::panel_item::{register_panel_item_ui_flex, PanelItem};
use anyhow::{anyhow, ensure, Result};
use color_eyre::eyre::{ensure, eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use parking_lot::Mutex;
use portable_atomic::Ordering;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::ops::Deref;
use std::hash::Hash;
use std::sync::{Arc, Weak};
lazy_static! {
static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![
"getTransform",
"setTransform",
"setSpatialParent",
"setSpatialParentInPlace",
"setZoneable",
"get_bounds",
"get_transform",
"set_transform",
"set_spatial_parent",
"set_spatial_parent_in_place",
"set_zoneable",
"release",
];
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec!["captureInto"];
static ref ITEM_ALIAS_LOCAL_METHODS: Vec<&'static str> = vec![];
static ref ITEM_ALIAS_REMOTE_SIGNALS: Vec<&'static str> = vec![];
static ref ITEM_ALIAS_REMOTE_METHODS: Vec<&'static str> = vec![];
static ref ITEM_TYPE_INFO_ENVIRONMENT: TypeInfo = TypeInfo {
type_name: "environment",
aliased_local_signals: vec!["applySkyTex", "applySkyLight"],
aliased_local_methods: vec![],
aliased_remote_signals: vec![],
aliased_remote_methods: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if item.captured_acceptor.lock().upgrade().is_some() {
release(item);
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
release(item, Some(&acceptor));
}
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
acceptor.handle_capture(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() {
ui.handle_capture(item);
ui.handle_capture_item(item, acceptor);
}
}
fn release(item: &Arc<Item>) {
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
*item.captured_acceptor.lock() = Weak::default();
fn release(item: &Item, acceptor: Option<&ItemAcceptor>) {
let mut captured_acceptor = item.captured_acceptor.lock();
if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) {
*captured_acceptor = Weak::default();
acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() {
ui.handle_release(item);
ui.handle_release_item(item, &acceptor);
}
}
}
@@ -70,27 +62,21 @@ pub struct TypeInfo {
pub aliased_local_signals: Vec<&'static str>,
pub aliased_local_methods: Vec<&'static str>,
pub aliased_remote_signals: Vec<&'static str>,
pub aliased_remote_methods: Vec<&'static str>,
pub ui: Mutex<Weak<ItemUI>>,
pub items: Registry<Item>,
pub acceptors: Registry<ItemAcceptor>,
}
fn capture_into_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let acceptor_path = flexbuffers::Reader::get_root(data)?
.get_str()
.map_err(|_| anyhow!("Acceptor path is not a string"))?;
let acceptor = calling_client
.scenegraph
.get_node(acceptor_path)
.ok_or_else(|| anyhow!("Acceptor node not found"))?;
let acceptor = acceptor
.item_acceptor
.get()
.ok_or_else(|| anyhow!("Acceptor node is not an acceptor!"))?;
capture(node.item.get().unwrap(), acceptor);
Ok(())
impl Hash for TypeInfo {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.type_name.hash(state);
}
}
impl PartialEq for TypeInfo {
fn eq(&self, other: &Self) -> bool {
self.type_name == other.type_name
}
}
impl Eq for TypeInfo {}
pub struct Item {
node: Weak<Node>,
@@ -102,84 +88,117 @@ pub struct Item {
impl Item {
pub fn add_to(
node: &Arc<Node>,
uid: String,
type_info: &'static TypeInfo,
specialization: ItemType,
) -> Arc<Self> {
node.add_local_signal("captureInto", capture_into_flex);
let item = Item {
node: Arc::downgrade(node),
uid: nanoid!(),
uid,
type_info,
captured_acceptor: Default::default(),
specialization,
};
let item = type_info.items.add(item);
node.add_local_signal("release", Item::release_flex);
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item);
}
let _ = node.item.set(item.clone());
if let Some(auto_acceptor) = node.get_client().and_then(|client| {
client
.startup_settings
.as_ref()
.and_then(|settings| settings.acceptors.get(type_info))
.and_then(|acceptor| acceptor.upgrade())
}) {
capture(&item, &auto_acceptor);
}
item
}
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> (Arc<Node>, Arc<Alias>) {
let node = Alias::new(
fn make_alias_named(
&self,
client: &Arc<Client>,
parent: &str,
name: &str,
) -> Result<Arc<Node>> {
Alias::create(
client,
parent,
&self.uid,
name,
&self.node.upgrade().unwrap(),
AliasInfo {
local_signals: [
server_signals: [
&self.type_info.aliased_local_signals,
ITEM_ALIAS_LOCAL_SIGNALS.as_slice(),
]
.concat(),
local_methods: [
server_methods: [
&self.type_info.aliased_local_methods,
ITEM_ALIAS_LOCAL_METHODS.as_slice(),
]
.concat(),
remote_signals: [
client_signals: [
&self.type_info.aliased_remote_signals,
ITEM_ALIAS_REMOTE_SIGNALS.as_slice(),
]
.concat(),
},
);
let alias = node.alias.get().unwrap().clone();
(node, alias)
)
}
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> Result<Arc<Node>> {
self.make_alias_named(client, parent, &self.uid)
}
fn release_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let item = node.get_aspect("Item", "item", |n| &n.item)?;
release(item, None);
Ok(())
}
}
impl Drop for Item {
fn drop(&mut self) {
self.type_info.items.remove(self);
release(self, None);
if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_item(self);
}
}
}
pub trait ItemSpecialization {
fn serialize_start_data(&self, id: &str) -> Vec<u8>;
}
pub enum ItemType {
Environment(EnvironmentItem),
Panel(PanelItem),
Panel(Arc<dyn PanelItemTrait>),
}
impl Deref for ItemType {
type Target = dyn ItemSpecialization;
fn deref(&self) -> &Self::Target {
impl ItemType {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
match self {
ItemType::Environment(item) => item,
ItemType::Panel(item) => item,
ItemType::Environment(e) => e.serialize_start_data(id),
ItemType::Panel(p) => p.serialize_start_data(id),
}
}
}
// 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 {
node: Weak<Node>,
type_info: &'static TypeInfo,
aliases: LifeLinkedNodeList,
item_aliases: LifeLinkedNodeMap<String>,
acceptor_aliases: LifeLinkedNodeMap<String>,
acceptor_field_aliases: LifeLinkedNodeMap<String>,
}
impl ItemUI {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
@@ -191,7 +210,9 @@ impl ItemUI {
let ui = Arc::new(ItemUI {
node: Arc::downgrade(node),
type_info,
aliases: Default::default(),
item_aliases: Default::default(),
acceptor_aliases: Default::default(),
acceptor_field_aliases: Default::default(),
});
*type_info.ui.lock() = Arc::downgrade(&ui);
let _ = node.item_ui.set(ui.clone());
@@ -199,51 +220,66 @@ impl ItemUI {
for item in type_info.items.get_valid_contents() {
ui.handle_create_item(&item);
}
for acceptor in type_info.acceptors.get_valid_contents() {
ui.handle_create_acceptor(&acceptor);
}
Ok(())
}
fn send_state(&self, state: &str, name: &str) {
let Ok(serialized_data) = serialize(name) else {return};
let _ = self
.node
.upgrade()
.unwrap()
.send_remote_signal(state, flexbuffers::singleton(name).as_slice());
.send_remote_signal(state, serialized_data);
}
fn handle_create_item(&self, item: &Item) {
let node = self.node.upgrade().unwrap();
if let Some(client) = node.get_client() {
let (alias_node, _) =
item.make_alias(&client, &(node.get_path().to_string() + "/item"));
self.aliases.add(Arc::downgrade(&alias_node));
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
let _ = node.send_remote_signal(
"create",
&item.specialization.serialize_start_data(&item.uid),
);
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
self.item_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
let _ = node.send_remote_signal("create_item", serialized_data);
}
fn handle_destroy_item(&self, item: &Item) {
self.send_state("destroy", item.uid.as_str());
self.item_aliases.remove(&item.uid);
self.send_state("destroy_item", item.uid.as_str());
}
fn handle_capture(&self, item: &Item) {
self.send_state("capture", item.uid.as_str());
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
let _ = node.send_remote_signal("capture_item", message);
}
fn handle_release(&self, item: &Item) {
self.send_state("release", item.uid.as_str());
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
let _ = node.send_remote_signal("release_item", message);
}
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let node = self.node.upgrade().unwrap();
if let Some(client) = node.get_client() {
let aliases = acceptor.make_aliases(
&client,
&format!("/item/{}/acceptor", self.type_info.type_name),
);
aliases
.iter()
.for_each(|alias| self.aliases.add(Arc::downgrade(alias)));
}
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
let Ok((alias, field_alias)) = acceptor.make_aliases(
&client,
&format!("/item/{}/acceptor", self.type_info.type_name),
) else {return};
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
self.acceptor_field_aliases
.add(acceptor.uid.clone(), &field_alias);
let Ok(message) = serialize(&acceptor.uid) else {return};
let _ = node.send_remote_signal("create_acceptor", message);
}
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
self.send_state("destroy_acceptor", acceptor.uid.as_str());
self.acceptor_aliases.remove(&acceptor.uid);
self.acceptor_field_aliases.remove(&acceptor.uid);
}
fn handle_destroy_acceptor(&self, _acceptor: &ItemAcceptor) {}
}
impl Drop for ItemUI {
fn drop(&mut self) {
@@ -252,98 +288,141 @@ impl Drop for ItemUI {
}
pub struct ItemAcceptor {
uid: String,
node: Weak<Node>,
type_info: &'static TypeInfo,
field: Mutex<Weak<Field>>,
accepted: Registry<Item>,
pub type_info: &'static TypeInfo,
field: Arc<Field>,
accepted_aliases: LifeLinkedNodeMap<String>,
accepted_registry: Registry<Item>,
}
impl ItemAcceptor {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Weak<Field>) {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo, field: Arc<Field>) {
let acceptor = type_info.acceptors.add(ItemAcceptor {
uid: nanoid!(),
node: Arc::downgrade(node),
type_info,
field: Mutex::new(field),
accepted: Registry::new(),
field,
accepted_aliases: Default::default(),
accepted_registry: Registry::new(),
});
node.add_local_signal("capture", ItemAcceptor::capture_flex);
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor);
}
let _ = node.item_acceptor.set(acceptor);
}
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Vec<Arc<Node>> {
let mut aliases = Vec::new();
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
if !node.enabled.load(Ordering::Relaxed) {
return Ok(());
}
let acceptor = node.item_acceptor.get().unwrap();
let item_path: &str = deserialize(message.as_ref())?;
let item_node = calling_client.get_node("Item", item_path)?;
let item = item_node.get_aspect("Item", "item", |n| &n.item)?;
capture(item, acceptor);
Ok(())
}
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Result<(Arc<Node>, Arc<Node>)> {
let acceptor_node = &self.node.upgrade().unwrap();
let acceptor_alias = Alias::new(
let acceptor_alias = Alias::create(
client,
parent,
acceptor_node.uid.as_str(),
&self.uid,
acceptor_node,
AliasInfo {
local_signals: vec!["release"],
server_signals: vec!["capture"],
..Default::default()
},
);
if let Some(field) = self.field.lock().upgrade() {
let acceptor_field_alias = Alias::new(
client,
acceptor_alias.get_path(),
"field",
&field.spatial_ref().node.upgrade().unwrap(),
AliasInfo::default(),
);
)?;
aliases.push(acceptor_field_alias);
}
aliases.push(acceptor_alias);
aliases
}
fn send_event(&self, state: &str, name: &str) {
let _ = self
.node
.upgrade()
.unwrap()
.send_remote_signal(state, flexbuffers::singleton(name).as_slice());
let acceptor_field_alias = Alias::create(
client,
acceptor_alias.get_path(),
"field",
&self.field.spatial_ref().node.upgrade().unwrap(),
AliasInfo::default(),
)?;
Ok((acceptor_alias, acceptor_field_alias))
}
fn handle_capture(&self, item: &Arc<Item>) {
self.accepted.add_raw(item);
self.send_event("capture", item.uid.as_str());
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
self.accepted_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
let _ = node.send_remote_signal("capture", serialized_data);
}
fn handle_release(&self, item: &Item) {
self.accepted.remove(item);
self.send_event("release", item.uid.as_str());
let Some(node) = self.node.upgrade() else {return};
self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid);
let Ok(message) = serialize(&item.uid) else {return};
let _ = node.send_remote_signal("release", message);
}
}
impl Drop for ItemAcceptor {
fn drop(&mut self) {
self.type_info.acceptors.remove(self);
for item in self.accepted_registry.get_valid_contents() {
release(&item, Some(self));
}
if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_acceptor(self);
}
}
}
pub fn create_interface(client: &Arc<Client>) {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "item", false);
node.add_local_signal(
"createEnvironmentItem",
"create_environment_item",
environment::create_environment_item_flex,
);
node.add_local_signal(
"registerEnvironmentItemUI",
environment::register_environment_item_ui_flex,
);
node.add_local_signal("registerPanelItemUI", register_panel_item_ui_flex);
node.add_local_signal(
"createEnvironmentItemAcceptor",
environment::create_environment_item_acceptor_flex,
);
node.add_to_scenegraph();
node.add_local_signal("register_item_ui", register_item_ui_flex);
node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
node.add_to_scenegraph().map(|_| ())
}
pub(self) fn create_item_acceptor_flex(
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],
type_info: &'static TypeInfo,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct RegisterItemUIInfo<'a> {
item_type: &'a str,
}
let info: RegisterItemUIInfo = deserialize(message.as_ref())?;
let type_info = type_info(info.item_type)?;
let ui =
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?;
Ok(())
}
fn create_item_acceptor_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateItemAcceptorInfo<'a> {
@@ -351,28 +430,22 @@ pub(self) fn create_item_acceptor_flex(
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
item_type: &'a str,
}
let info: CreateItemAcceptorInfo = deserialize(data)?;
let parent_name = format!("/item/{}/acceptor/", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let info: CreateItemAcceptorInfo = deserialize(message.as_ref())?;
let space = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let type_info = type_info(info.item_type)?;
let node = Node::create(&INTERNAL_CLIENT, &parent_name, info.name, true).add_to_scenegraph();
Spatial::add_to(&node, Some(space), transform)?;
ItemAcceptor::add_to(&node, type_info, Arc::downgrade(&field));
node.item
.get()
.unwrap()
.make_alias(&calling_client, &parent_name);
Ok(())
}
pub fn register_item_ui_flex(
calling_client: Arc<Client>,
type_info: &'static TypeInfo,
) -> Result<()> {
let ui = Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph();
ItemUI::add_to(&ui, type_info)?;
let node = Node::create(
&calling_client,
&format!("/item/{}/acceptor", type_info.type_name),
info.name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(space), transform, false)?;
ItemAcceptor::add_to(&node, type_info, field);
Ok(())
}

507
src/nodes/items/panel.rs Normal file
View File

@@ -0,0 +1,507 @@
use crate::{
core::{
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
drawable::{model::ModelPart, Drawable},
items::{self, Item, ItemType, TypeInfo},
spatial::Spatial,
Message, Node,
},
};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use nanoid::nanoid;
use serde::{
de::{Deserializer, Error, SeqAccess, Visitor},
ser::Serializer,
Deserialize, Serialize,
};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak};
use tracing::debug;
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 trait Backend: Send + Sync + 'static {
fn serialize_start_data(&self, id: &str) -> Result<Message>;
fn serialize_toplevel(&self) -> Result<Message>;
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>);
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
);
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>);
fn 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_set_keymap(&self, keymap: &str) -> Result<()>;
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool);
}
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
Some(panel_item.clone())
}
pub trait PanelItemTrait: Backend + Send + Sync + 'static {
fn uid(&self) -> &str;
// fn node(&self) -> Option<Arc<Node>>;
}
pub struct PanelItem<B: Backend + ?Sized> {
pub uid: String,
node: Weak<Node>,
pub backend: Box<B>,
}
impl<B: Backend + ?Sized> PanelItem<B> {
pub fn create(backend: Box<B>, pid: Option<i32>) -> (Arc<Node>, Arc<PanelItem<B>>) {
debug!(?pid, "Create panel item");
let startup_settings = pid
.and_then(|pid| get_env(pid).ok())
.and_then(|env| startup_settings(&env));
let uid = nanoid!();
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
.add_to_scenegraph()
.unwrap();
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(
spatial.global_transform().inverse() * startup_settings.transform,
);
}
let panel_item = Arc::new(PanelItem {
uid: uid.clone(),
node: Arc::downgrade(&node),
backend,
});
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
let item = Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item),
);
// panel_item
// .seat_data
// .new_surface(&wl_surface, Arc::downgrade(&panel_item));
if let Some(startup_settings) = &startup_settings {
if let Some(acceptor) = startup_settings
.acceptors
.get(&*ITEM_TYPE_INFO_PANEL)
.and_then(|acc| acc.upgrade())
{
items::capture(&item, &acceptor);
}
}
node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex);
node.add_local_signal("configure_toplevel", Self::configure_toplevel_flex);
node.add_local_signal(
"set_toplevel_capabilities",
Self::set_toplevel_capabilities_flex,
);
node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex);
node.add_local_signal("pointer_button", Self::pointer_button_flex);
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
node.add_local_signal(
"keyboard_set_keymap_string",
Self::keyboard_set_keymap_string_flex,
);
// node.add_local_signal(
// "keyboard_set_keymap_names",
// Self::keyboard_set_keymap_names_flex,
// );
node.add_local_signal("keyboard_key", Self::keyboard_key_flex);
(node, panel_item)
}
pub fn node(&self) -> Option<Arc<Node>> {
self.node.upgrade()
}
fn apply_surface_material_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct SurfaceMaterialInfo<'a> {
surface: SurfaceID,
model_node_path: &'a str,
}
let info: SurfaceMaterialInfo = deserialize(message.as_ref())?;
let model_node = calling_client
.scenegraph
.get_node(info.model_node_path)
.ok_or_else(|| eyre!("Model node not found"))?;
let Some(Drawable::ModelPart(model_part)) = model_node.drawable.get() else {bail!("Node is not a model")};
debug!(?info, "Apply surface material");
panel_item.apply_surface_material(info.surface, model_part);
Ok(())
}
fn pointer_motion_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, position): (SurfaceID, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?surface_id, ?position, "Pointer deactivate");
panel_item.pointer_motion(&surface_id, position);
Ok(())
}
fn pointer_button_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(?surface_id, button, state, "Pointer button");
panel_item.pointer_button(&surface_id, button, state == 0);
Ok(())
}
fn pointer_scroll_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct PointerScrollInfo {
surface_id: SurfaceID,
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
}
let info: PointerScrollInfo = deserialize(message.as_ref())?;
debug!(?info, "Pointer scroll");
panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete);
Ok(())
}
fn keyboard_set_keymap_string_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let keymap_string: &str = deserialize(message.as_ref())?;
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
debug!("Keyboard set keymap");
panel_item.keyboard_set_keymap(keymap_string)
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
}
// fn keyboard_set_keymap_names_flex(
// node: &Node,
// _calling_client: Arc<Client>,
// message: Message,
// ) -> Result<()> {
// #[derive(Debug, Deserialize)]
// struct Names<'a> {
// rules: &'a str,
// model: &'a str,
// layout: &'a str,
// variant: &'a str,
// options: Option<String>,
// }
// let names: Names = deserialize(message.as_ref())?;
// let context = xkb::Context::new(0);
// let keymap = Keymap::new_from_names(
// &context,
// names.rules,
// names.model,
// names.layout,
// names.variant,
// names.options,
// XKB_KEYMAP_FORMAT_TEXT_V1,
// )
// .ok_or_else(|| eyre!("Keymap is not valid"))?;
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
// }
// fn keyboard_set_keymap_flex(node: &Node, keymap: &str) -> Result<()> {
// let Some(panel_item): Option<Arc<PanelItem<dyn WaylandBackend>>> = panel_item_from_node(node) else { return Ok(()) };
// debug!("Keyboard set keymap");
// panel_item.seat_data.set_keymap(
// keymap,
// match &panel_item {
// Backend::Wayland(w) => w.input_surfaces(),
// #[cfg(feature = "xwayland")]
// Backend::X11(_) => panel_item
// .toplevel_wl_surface()
// .map(|s| vec![s])
// .unwrap_or_default(),
// },
// );
// Ok(())
// }
fn keyboard_key_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(key, state, "Set keyboard key state");
panel_item.keyboard_key(&surface_id, key, state == 0);
Ok(())
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize(sid) else {return};
let _ = node.send_remote_signal("grab_keyboard", message);
}
fn configure_toplevel_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct ConfigureToplevelInfo {
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
}
let info: ConfigureToplevelInfo = deserialize(message.as_ref())?;
panel_item.configure_toplevel(info.size, info.states, info.bounds);
Ok(())
}
fn set_toplevel_capabilities_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let capabilities: Vec<u8> = deserialize(message.as_ref())?;
debug!("Set toplevel capabilities");
panel_item.set_toplevel_capabilities(capabilities);
Ok(())
}
pub fn commit_toplevel(&self) {
debug!("Commit toplevel");
let Some(node) = self.node.upgrade() else {return};
let Ok(data) = self.backend.serialize_toplevel() else {return};
let _ = node.send_remote_signal("commit_toplevel", data);
}
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
let Some(node) = self.node.upgrade() else {return};
let data = serialize(state).unwrap();
debug!(?state, "Recommend toplevel state");
let _ = node.send_remote_signal("recommend_toplevel_state", data);
}
}
impl<B: Backend + ?Sized> PanelItemTrait for PanelItem<B> {
fn uid(&self) -> &str {
&self.uid
}
}
impl<B: Backend + ?Sized> Backend for PanelItem<B> {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
self.backend.serialize_start_data(id)
}
fn serialize_toplevel(&self) -> Result<Message> {
self.backend.serialize_toplevel()
}
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>) {
self.backend.set_toplevel_capabilities(capabilities)
}
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
) {
self.backend.configure_toplevel(size, states, bounds)
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
self.backend.apply_surface_material(surface, model_part)
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
self.backend.pointer_motion(surface, position)
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
self.backend.pointer_button(surface, button, pressed)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
self.backend
.pointer_scroll(surface, scroll_distance, scroll_steps)
}
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
self.backend.keyboard_set_keymap(keymap)
}
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
self.backend.keyboard_key(surface, key, state)
}
}
impl<B: Backend + ?Sized> Drop for PanelItem<B> {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
}
}

View File

@@ -1,4 +1,5 @@
pub mod alias;
pub mod audio;
pub mod data;
pub mod drawable;
pub mod fields;
@@ -9,57 +10,84 @@ pub mod root;
pub mod spatial;
pub mod startup;
use anyhow::{anyhow, Result};
use color_eyre::eyre::{eyre, Result};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHasher;
use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError;
use std::sync::atomic::{AtomicBool, Ordering};
use stardust_xr::schemas::flex::deserialize;
use std::fmt::Debug;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use std::vec::Vec;
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use rustc_hash::FxHasher;
use tracing::{debug_span, instrument};
use crate::core::client::Client;
use crate::core::registry::Registry;
#[cfg(feature = "openxr_runtime")]
use crate::openxr;
use self::alias::Alias;
use self::audio::Sound;
use self::data::{PulseReceiver, PulseSender};
use self::drawable::model::Model;
use self::drawable::text::Text;
use self::drawable::Drawable;
use self::fields::Field;
use self::input::{InputHandler, InputMethod};
use self::items::{Item, ItemAcceptor, ItemUI};
use self::spatial::zone::Zone;
use self::spatial::Spatial;
use self::startup::StartupSettings;
pub type Signal = fn(&Node, Arc<Client>, &[u8]) -> Result<()>;
pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>;
pub struct Message {
pub data: Vec<u8>,
pub fds: Vec<OwnedFd>,
}
impl From<Vec<u8>> for Message {
fn from(data: Vec<u8>) -> Self {
Message {
data,
fds: Vec::new(),
}
}
}
impl AsRef<[u8]> for Message {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
pub type Signal = fn(&Node, Arc<Client>, Message) -> Result<()>;
pub type Method = fn(&Node, Arc<Client>, Message) -> Result<Message>;
pub struct Node {
pub enabled: Arc<AtomicBool>,
pub(super) uid: String,
path: String,
client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize,
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>,
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>,
destroyable: AtomicBool,
destroyable: bool,
pub alias: OnceCell<Arc<Alias>>,
aliases: Registry<Alias>,
pub spatial: OnceCell<Arc<Spatial>>,
pub field: OnceCell<Arc<Field>>,
pub zone: OnceCell<Arc<Zone>>,
// Data
pub pulse_sender: OnceCell<Arc<PulseSender>>,
pub pulse_receiver: OnceCell<Arc<PulseReceiver>>,
// Drawable
pub model: OnceCell<Arc<Model>>,
pub text: OnceCell<Arc<Text>>,
pub drawable: OnceCell<Drawable>,
// Input
pub input_method: OnceCell<Arc<InputMethod>>,
@@ -70,10 +98,15 @@ pub struct Node {
pub item_acceptor: OnceCell<Arc<ItemAcceptor>>,
pub item_ui: OnceCell<Arc<ItemUI>>,
// Sound
pub sound: OnceCell<Arc<Sound>>,
// Startup
pub startup_settings: OnceCell<Mutex<StartupSettings>>,
pub(crate) client: Weak<Client>,
// OpenXR
#[cfg(feature = "openxr_runtime")]
pub openxr_object: OnceCell<openxr::Object>,
}
impl Node {
@@ -86,53 +119,73 @@ impl Node {
pub fn get_path(&self) -> &str {
self.path.as_str()
}
pub fn is_destroyable(&self) -> bool {
self.destroyable.load(Ordering::Relaxed)
}
pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self {
let mut path = parent.to_string();
path.push('/');
path.push_str(name);
let node = Node {
enabled: Arc::new(AtomicBool::new(true)),
uid: nanoid!(),
client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(),
path,
// trailing_slash_pos: parent.len(),
local_signals: Default::default(),
local_methods: Default::default(),
destroyable: AtomicBool::from(destroyable),
destroyable,
alias: OnceCell::new(),
aliases: Registry::new(),
spatial: OnceCell::new(),
field: OnceCell::new(),
zone: OnceCell::new(),
pulse_sender: OnceCell::new(),
pulse_receiver: OnceCell::new(),
model: OnceCell::new(),
text: OnceCell::new(),
drawable: OnceCell::new(),
input_method: OnceCell::new(),
input_handler: OnceCell::new(),
item: OnceCell::new(),
item_acceptor: OnceCell::new(),
item_ui: OnceCell::new(),
sound: OnceCell::new(),
#[cfg(feature = "openxr_runtime")]
openxr_object: OnceCell::new(),
startup_settings: OnceCell::new(),
};
node.add_local_signal("set_enabled", Node::set_enabled_flex);
node.add_local_signal("destroy", Node::destroy_flex);
node
}
pub fn add_to_scenegraph(self) -> Arc<Node> {
self.get_client().unwrap().scenegraph.add_node(self)
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
Ok(self
.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
.scenegraph
.add_node(self))
}
pub fn destroy(&self) {
let _ = self
.get_client()
.map(|c| c.scenegraph.remove_node(self.get_path()));
if let Some(client) = self.get_client() {
client.scenegraph.remove_node(self.get_path());
}
}
pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
if node.is_destroyable() {
pub fn set_enabled_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
node.enabled
.store(deserialize(message.as_ref())?, Ordering::Relaxed);
Ok(())
}
pub fn destroy_flex(
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
@@ -150,98 +203,128 @@ impl Node {
node_name: &'static str,
aspect_type: &'static str,
aspect_fn: F,
) -> Result<Arc<T>>
) -> Result<&T>
where
F: FnOnce(&Node) -> &OnceCell<Arc<T>>,
F: FnOnce(&Node) -> &OnceCell<T>,
{
aspect_fn(self)
.get()
.ok_or_else(|| anyhow!("{} is not a {} node", node_name, aspect_type))
.cloned()
.ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
}
pub fn send_local_signal(
&self,
calling_client: Arc<Client>,
method: &str,
data: &[u8],
message: Message,
) -> Result<(), ScenegraphError> {
if let Some(alias) = self.alias.get() {
if !alias.info.local_signals.iter().any(|e| e == &method) {
if !alias.info.server_signals.iter().any(|e| e == &method) {
return Err(ScenegraphError::SignalNotFound);
}
alias
.original
.upgrade()
.ok_or(ScenegraphError::BrokenAlias)?
.send_local_signal(calling_client, method, data)
.send_local_signal(calling_client, method, message)
} else {
let signal = self
.local_signals
.get(method)
.ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, data)
.map_err(|error| ScenegraphError::SignalError { error })
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
error: error.to_string(),
})
}
}
pub fn execute_local_method(
&self,
calling_client: Arc<Client>,
method: &str,
data: &[u8],
) -> Result<Vec<u8>, ScenegraphError> {
message: Message,
) -> Result<Message, ScenegraphError> {
if let Some(alias) = self.alias.get() {
if !alias.info.local_methods.iter().any(|e| e == &method) {
if !alias.info.server_methods.iter().any(|e| e == &method) {
return Err(ScenegraphError::MethodNotFound);
}
alias
.original
.upgrade()
.ok_or(ScenegraphError::BrokenAlias)?
.execute_local_method(calling_client, method, data)
.execute_local_method(
calling_client,
method,
Message {
data: message.data.clone(),
fds: Vec::new(),
},
)
} else {
let method = self
.local_methods
.get(method)
.ok_or(ScenegraphError::MethodNotFound)?;
method(self, calling_client, data)
.map_err(|error| ScenegraphError::MethodError { error })
debug_span!("Handle method").in_scope(|| {
method(self, calling_client, message).map_err(|error| {
ScenegraphError::MethodError {
error: error.to_string(),
}
})
})
}
}
pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> {
#[instrument(level = "debug", skip_all)]
pub fn send_remote_signal(&self, method: &str, message: impl Into<Message>) -> Result<()> {
let message = message.into();
self.aliases
.get_valid_contents()
.iter()
.filter(|alias| alias.info.remote_signals.iter().any(|e| e == &method))
.for_each(|alias| {
let _ = alias
.node
.upgrade()
.unwrap()
.send_remote_signal(method, data);
.filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
.filter_map(|alias| alias.node.upgrade())
.for_each(|node| {
// 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(client) = self.get_client() {
if let Some(messenger) = client.messenger.as_ref() {
messenger.send_remote_signal(path.as_str(), method.as_str(), data.as_slice());
}
if let Some(handle) = self.message_sender_handle.as_ref() {
handle.signal(path.as_str(), method.as_str(), &message.data, message.fds)?;
}
Ok(())
}
pub async fn execute_remote_method(&self, method: &str, data: Vec<u8>) -> Result<Vec<u8>> {
if let Some(client) = self.get_client() {
match client.messenger.as_ref() {
None => Err(anyhow!("Messenger does not exist for this node's client")),
Some(messenger) => {
messenger
.execute_remote_method(self.path.as_str(), method, &data)
.await
}
}
} else {
Err(anyhow!("Client does not exist somehow?"))
}
// #[instrument(level = "debug", skip_all)]
// pub fn execute_remote_method(
// &self,
// method: &str,
// data: Vec<u8>,
// ) -> Result<impl Future<Output = Result<Message>>> {
// let message_sender_handle = self
// .message_sender_handle
// .as_ref()
// .ok_or(eyre!("Messenger does not exist for this node"))?;
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?;
// Ok(async { future.await.map_err(|e| eyre!(e)) })
// }
}
impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node")
.field("uid", &self.uid)
.field("path", &self.path)
.finish()
}
}
impl Drop for Node {
fn drop(&mut self) {
// Debug breakpoint
}
}

View File

@@ -1,11 +1,11 @@
use super::spatial::Spatial;
use super::startup::DESKTOP_STARTUP_IDS;
use super::Node;
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::registry::Registry;
use anyhow::{anyhow, Result};
use color_eyre::eyre::Result;
use glam::Mat4;
use stardust_xr::schemas::flex::{deserialize, serialize};
use tracing::instrument;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -14,62 +14,62 @@ static ROOT_REGISTRY: Registry<Root> = Registry::new();
pub struct Root {
node: Arc<Node>,
logic_step: AtomicBool,
send_frame_event: AtomicBool,
}
impl Root {
pub fn create(client: &Arc<Client>) -> Arc<Self> {
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
let node = Node::create(client, "", "", false);
node.add_local_signal("applyDesktopStartupID", Root::apply_desktop_startup_id);
node.add_local_signal("subscribeLogicStep", Root::subscribe_logic_step);
node.add_local_signal("setBasePrefixes", Root::set_base_prefixes);
let node = node.add_to_scenegraph();
let _ = Spatial::add_to(&node, None, Mat4::IDENTITY);
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(
&node,
None,
client
.startup_settings
.as_ref()
.map(|settings| settings.transform)
.unwrap_or(Mat4::IDENTITY),
false,
);
ROOT_REGISTRY.add(Root {
Ok(ROOT_REGISTRY.add(Root {
node,
logic_step: AtomicBool::from(false),
})
send_frame_event: AtomicBool::from(false),
}))
}
fn apply_desktop_startup_id(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
fn subscribe_frame_flex(
_node: &Node,
calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let startup_settings = DESKTOP_STARTUP_IDS
.lock()
.remove(flexbuffers::Reader::get_root(data)?.get_str()?)
.ok_or_else(|| anyhow!("Desktop startup ID not found in the list!"))?;
node.spatial
.get()
.unwrap()
.set_local_transform(startup_settings.transform);
Ok(())
}
fn subscribe_logic_step(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
calling_client
.root
.get()
.unwrap()
.logic_step
.send_frame_event
.store(true, Ordering::Relaxed);
Ok(())
}
pub fn logic_step(delta: f64) {
#[instrument(level = "debug")]
pub fn send_frame_events(delta: f64) {
if let Ok(data) = serialize((delta, 0.0)) {
for root in ROOT_REGISTRY.get_valid_contents() {
if root.logic_step.load(Ordering::Relaxed) {
let _ = root.node.send_remote_signal("logicStep", &data);
if root.send_frame_event.load(Ordering::Relaxed) {
let _ = root.node.send_remote_signal("frame", data.clone());
}
}
}
}
fn set_base_prefixes(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(data)?;
fn set_base_prefixes_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
Ok(())
}
}

View File

@@ -1,291 +0,0 @@
use super::Node;
use crate::core::client::Client;
use anyhow::{anyhow, ensure, Result};
use glam::{Mat4, Quat};
use mint::Vector3;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::ptr;
use std::sync::{Arc, Weak};
pub struct Spatial {
pub(super) node: Weak<Node>,
parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>,
}
impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new(Spatial {
node,
parent: Mutex::new(parent),
transform: Mutex::new(transform),
})
}
pub fn add_to(
node: &Arc<Node>,
parent: Option<Arc<Spatial>>,
transform: Mat4,
) -> Result<Arc<Spatial>> {
ensure!(
node.spatial.get().is_none(),
"Internal: Node already has a Spatial aspect!"
);
let spatial = Spatial {
node: Arc::downgrade(node),
parent: Mutex::new(parent),
transform: Mutex::new(transform),
};
node.add_local_method("getTransform", Spatial::get_transform_flex);
node.add_local_signal("setTransform", Spatial::set_transform_flex);
node.add_local_signal("setSpatialParent", Spatial::set_spatial_parent_flex);
node.add_local_signal(
"setSpatialParentInPlace",
Spatial::set_spatial_parent_in_place_flex,
);
let spatial_arc = Arc::new(spatial);
let _ = node.spatial.set(spatial_arc.clone());
Ok(spatial_arc)
}
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
world_to_space_matrix * space_to_world_matrix
}
pub fn local_transform(&self) -> Mat4 {
*self.transform.lock()
}
pub fn global_transform(&self) -> Mat4 {
match self.parent.lock().clone() {
Some(value) => value.global_transform() * *self.transform.lock(),
None => *self.transform.lock(),
}
}
pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.lock() = transform;
}
pub fn set_local_transform_components(
&self,
reference_space: Option<&Spatial>,
transform: Transform,
) {
let reference_to_parent_transform = reference_space
.map(|reference_space| {
Spatial::space_to_space_matrix(Some(reference_space), self.parent.lock().as_deref())
})
.unwrap_or(Mat4::IDENTITY);
let mut local_transform_in_reference_space =
reference_to_parent_transform.inverse() * self.local_transform();
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
local_transform_in_reference_space.to_scale_rotation_translation();
if let Some(pos) = transform.position {
reference_space_pos = pos.into()
}
if let Some(rot) = transform.rotation {
reference_space_rot = rot.into()
} else if reference_space_rot.is_nan() {
reference_space_rot = Quat::IDENTITY;
}
if let Some(scl) = transform.scale {
reference_space_scl = scl.into()
}
local_transform_in_reference_space = Mat4::from_scale_rotation_translation(
reference_space_scl,
reference_space_rot,
reference_space_pos,
);
self.set_local_transform(
reference_to_parent_transform * local_transform_in_reference_space,
);
}
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
let mut current_ancestor = spatial;
loop {
if Arc::as_ptr(&current_ancestor) == ptr::addr_of!(*self) {
return true;
}
let current_ancestor_parent = current_ancestor.parent.lock().clone();
if let Some(parent) = current_ancestor_parent {
current_ancestor = parent;
} else {
return false;
}
}
}
pub fn set_spatial_parent(&self, parent: Option<&Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(anyhow!("Setting spatial parent would cause a loop"));
}
*self.parent.lock() = parent.cloned();
Ok(())
}
pub fn set_spatial_parent_in_place(&self, parent: Option<&Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(anyhow!("Setting spatial parent would cause a loop"));
}
self.set_local_transform(Spatial::space_to_space_matrix(
Some(self),
parent.cloned().as_deref(),
));
*self.parent.lock() = parent.cloned();
Ok(())
}
pub fn get_transform_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| anyhow!("Node doesn't have a spatial?"))?;
let relative_spatial = find_reference_space(&calling_client, deserialize(data)?)?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
serialize((
mint::Vector3::from(position),
mint::Quaternion::from(rotation),
mint::Vector3::from(scale),
))
.map_err(|e| e.into())
}
pub fn set_transform_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct TransformArgs<'a> {
reference_space_path: Option<&'a str>,
transform: Transform,
}
let transform_args: TransformArgs = deserialize(data)?;
let reference_space_transform = transform_args
.reference_space_path
.map(|path| find_reference_space(&calling_client, path))
.transpose()?;
node.spatial.get().unwrap().set_local_transform_components(
reference_space_transform.as_deref(),
transform_args.transform,
);
Ok(())
}
pub fn set_spatial_parent_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
node.spatial
.get()
.unwrap()
.set_spatial_parent(Some(&parent))
}
pub fn set_spatial_parent_in_place_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(data)?)?;
node.spatial
.get()
.unwrap()
.set_spatial_parent_in_place(Some(&parent))?;
Ok(())
}
}
pub fn parse_transform(
transform: Transform,
translation: bool,
rotation: bool,
scale: bool,
) -> Result<Mat4> {
let translation = translation
.then_some(transform.position)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(transform.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(transform.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Ok(Mat4::from_scale_rotation_translation(
scale.into(),
rotation.into(),
translation.into(),
))
}
pub fn find_spatial(
calling_client: &Arc<Client>,
node_name: &'static str,
node_path: &str,
) -> anyhow::Result<Arc<Spatial>> {
Ok(calling_client
.get_node(node_name, node_path)?
.get_aspect(node_name, "spatial", |n| &n.spatial)?
.clone())
}
pub fn find_spatial_parent(
calling_client: &Arc<Client>,
node_path: &str,
) -> anyhow::Result<Arc<Spatial>> {
find_spatial(calling_client, "Spatial parent", node_path)
}
pub fn find_reference_space(
calling_client: &Arc<Client>,
node_path: &str,
) -> anyhow::Result<Arc<Spatial>> {
find_spatial(calling_client, "Reference space", node_path)
}
pub fn create_interface(client: &Arc<Client>) {
let node = Node::create(client, "", "spatial", false);
node.add_local_signal("createSpatial", create_spatial_flex);
node.add_to_scenegraph();
}
pub fn create_spatial_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct CreateSpatialInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
}
let info: CreateSpatialInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true)?;
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform)?;
Ok(())
}

521
src/nodes/spatial/mod.rs Normal file
View File

@@ -0,0 +1,521 @@
pub mod zone;
use self::zone::{create_zone_flex, Zone};
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::registry::Registry;
use color_eyre::eyre::{ensure, eyre, Result};
use glam::{vec3a, Mat4, Quat};
use mint::Vector3;
use nanoid::nanoid;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::fmt::Debug;
use std::ptr;
use std::sync::{Arc, OnceLock, Weak};
use stereokit::{bounds_grow_to_fit_box, Bounds};
use tracing::instrument;
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial {
uid: String,
pub(super) node: Weak<Node>,
self_ref: Weak<Spatial>,
parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
}
impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new_cyclic(|self_ref| Spatial {
uid: nanoid!(),
node,
self_ref: self_ref.clone(),
parent: Mutex::new(parent),
old_parent: Mutex::new(None),
transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()),
children: Registry::new(),
bounding_box_calc: OnceLock::default(),
})
}
pub fn add_to(
node: &Arc<Node>,
parent: Option<Arc<Spatial>>,
transform: Mat4,
zoneable: bool,
) -> Result<Arc<Spatial>> {
ensure!(
node.spatial.get().is_none(),
"Internal: Node already has a Spatial aspect!"
);
let spatial = Spatial::new(Arc::downgrade(node), parent, transform);
node.add_local_method("get_bounding_box", Spatial::get_bounding_box_flex);
node.add_local_method("get_transform", Spatial::get_transform_flex);
node.add_local_signal("set_transform", Spatial::set_transform_flex);
node.add_local_signal("set_spatial_parent", Spatial::set_spatial_parent_flex);
node.add_local_signal(
"set_spatial_parent_in_place",
Spatial::set_spatial_parent_in_place_flex,
);
node.add_local_signal("set_zoneable", Spatial::set_zoneable_flex);
node.add_local_method("field_distance", Spatial::field_distance_flex);
node.add_local_method("field_normal", Spatial::field_normal_flex);
node.add_local_method("field_closest_point", Spatial::field_closest_point_flex);
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
}
let _ = node.spatial.set(spatial.clone());
Ok(spatial)
}
pub fn node(&self) -> Option<Arc<Node>> {
self.node.upgrade()
}
#[instrument(level = "debug", skip_all)]
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
world_to_space_matrix * space_to_world_matrix
}
// the output bounds are probably way bigger than they need to be
#[instrument(level = "debug")]
pub fn get_bounding_box(&self) -> Bounds {
let Some(node) = self.node() else {return Bounds::default()};
let mut bounds = self
.bounding_box_calc
.get()
.map(|b| (b)(&node))
.unwrap_or_default();
for child in self.children.get_valid_contents() {
bounds = bounds_grow_to_fit_box(
bounds,
child.get_bounding_box(),
Some(child.local_transform()),
);
}
bounds
}
#[instrument(level = "debug", skip_all)]
pub fn local_transform(&self) -> Mat4 {
*self.transform.lock()
}
pub fn global_transform(&self) -> Mat4 {
match self.get_parent() {
Some(value) => value.global_transform() * *self.transform.lock(),
None => *self.transform.lock(),
}
}
#[instrument]
pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.lock() = transform;
}
#[instrument(level = "debug", skip(self, reference_space))]
pub fn set_local_transform_components(
&self,
reference_space: Option<&Spatial>,
transform: Transform,
) {
if reference_space == Some(self) {
self.set_local_transform(
parse_transform(transform, true, true, true) * self.local_transform(),
);
return;
}
let reference_to_parent_transform = reference_space
.map(|reference_space| {
Spatial::space_to_space_matrix(Some(reference_space), self.get_parent().as_deref())
})
.unwrap_or(Mat4::IDENTITY);
let mut local_transform_in_reference_space =
reference_to_parent_transform.inverse() * self.local_transform();
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
local_transform_in_reference_space.to_scale_rotation_translation();
if let Some(pos) = transform.position {
reference_space_pos = pos.into()
}
if let Some(rot) = transform.rotation {
reference_space_rot = rot.into()
} else if reference_space_rot.is_nan() {
reference_space_rot = Quat::IDENTITY;
}
if let Some(scl) = transform.scale {
reference_space_scl = scl.into()
}
local_transform_in_reference_space = Mat4::from_scale_rotation_translation(
reference_space_scl,
reference_space_rot,
reference_space_pos,
);
self.set_local_transform(
reference_to_parent_transform * local_transform_in_reference_space,
);
}
#[instrument(level = "debug", skip_all)]
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
let mut current_ancestor = spatial;
loop {
if Arc::as_ptr(&current_ancestor) == ptr::addr_of!(*self) {
return true;
}
if let Some(parent) = current_ancestor.get_parent() {
current_ancestor = parent;
} else {
return false;
}
}
}
fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.lock().clone()
}
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) {
if let Some(parent) = self.get_parent() {
parent.children.remove(self);
}
if let Some(new_parent) = &new_parent {
new_parent
.children
.add_raw(&self.self_ref.upgrade().unwrap());
}
*self.parent.lock() = new_parent;
}
#[instrument(level = "debug", skip_all)]
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.as_ref()
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
}
self.set_parent(parent);
Ok(())
}
#[instrument(level = "debug", skip_all)]
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.as_ref()
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
}
self.set_local_transform(Spatial::space_to_space_matrix(
Some(self),
parent.as_deref(),
));
self.set_parent(parent);
Ok(())
}
pub fn get_bounding_box_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial_path: Option<&str> = deserialize(message.as_ref())?;
let bounds = if let Some(relative_spatial_path) = relative_spatial_path {
let relative_spatial = find_reference_space(&calling_client, relative_spatial_path)?;
let center =
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let bounds: Bounds = Bounds {
center,
dimensions: [0.0; 3].into(),
};
bounds_grow_to_fit_box(
bounds,
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
)
} else {
this_spatial.get_bounding_box()
};
Ok(serialize((
mint::Vector3::from(bounds.center),
mint::Vector3::from(bounds.dimensions),
))?
.into())
}
pub fn get_transform_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial =
find_reference_space(&calling_client, deserialize(message.as_ref())?)?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
Some(relative_spatial.as_ref()),
)
.to_scale_rotation_translation();
Ok(serialize((
mint::Vector3::from(position),
mint::Quaternion::from(rotation),
mint::Vector3::from(scale),
))?
.into())
}
pub fn set_transform_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct TransformArgs<'a> {
reference_space_path: Option<&'a str>,
transform: Transform,
}
let transform_args: TransformArgs = deserialize(message.as_ref())?;
let reference_space_transform = transform_args
.reference_space_path
.map(|path| find_reference_space(&calling_client, path))
.transpose()?;
node.spatial.get().unwrap().set_local_transform_components(
reference_space_transform.as_deref(),
transform_args.transform,
);
Ok(())
}
pub fn set_spatial_parent_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
}
pub fn set_spatial_parent_in_place_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
node.spatial
.get()
.unwrap()
.set_spatial_parent_in_place(Some(parent))?;
Ok(())
}
pub fn set_zoneable_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let zoneable: bool = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
if zoneable {
ZONEABLE_REGISTRY.add_raw(spatial);
} else {
ZONEABLE_REGISTRY.remove(spatial);
zone::release(spatial);
}
Ok(())
}
pub fn field_distance_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| f.distance(spatial, point.into())))
.collect::<Vec<Option<f32>>>();
Ok(serialize(output)?.into())
}
pub fn field_normal_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.normal(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?.into())
}
pub fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.closest_point(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?.into())
}
#[instrument]
pub(self) fn zone_distance(&self) -> f32 {
self.zone
.lock()
.upgrade()
.and_then(|zone| zone.field.upgrade())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX)
}
}
impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
}
impl Debug for Spatial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Spatial")
.field("uid", &self.uid)
.field("parent", &self.parent)
.field("old_parent", &self.old_parent)
.field("transform", &self.transform)
.finish()
}
}
impl Drop for Spatial {
fn drop(&mut self) {
ZONEABLE_REGISTRY.remove(self);
zone::release(self);
}
}
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(transform.position)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(transform.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(transform.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
pub fn find_spatial(
calling_client: &Arc<Client>,
node_name: &'static str,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
calling_client
.get_node(node_name, node_path)?
.get_aspect(node_name, "spatial", |n| &n.spatial)
.cloned()
}
pub fn find_spatial_parent(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Spatial parent", node_path)
}
pub fn find_reference_space(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Reference space", node_path)
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "spatial", false);
node.add_local_signal("create_spatial", create_spatial_flex);
node.add_local_signal("create_zone", create_zone_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_spatial_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateSpatialInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
zoneable: bool,
}
let info: CreateSpatialInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, info.zoneable)?;
Ok(())
}

171
src/nodes/spatial/zone.rs Normal file
View File

@@ -0,0 +1,171 @@
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY};
use crate::{
core::{client::Client, registry::Registry},
nodes::{
alias::{Alias, AliasInfo},
fields::{find_field, Field},
spatial::{find_spatial_parent, parse_transform},
Message, Node,
},
};
use color_eyre::eyre::Result;
use glam::vec3a;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::{
schemas::flex::{deserialize, serialize},
values::Transform,
};
use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
let old_distance = spatial.zone_distance();
let new_distance = zone
.field
.upgrade()
.map(|field| field.distance(spatial, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX);
if new_distance.abs() < old_distance.abs() {
release(spatial);
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
zone.captured.add_raw(spatial);
let Some(node) = zone.spatial.node.upgrade() else {return};
let Ok(message) = serialize(&spatial.uid) else {return};
let _ = node.send_remote_signal("capture", message);
}
}
pub fn release(spatial: &Spatial) {
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take());
let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
let Some(node) = spatial_zone.spatial.node.upgrade() else {return};
spatial_zone.captured.remove(spatial);
let Ok(message) = serialize(&spatial.uid) else {return};
let _ = node.send_remote_signal("release", message);
}
*spatial_zone = Weak::new();
}
pub struct Zone {
spatial: Arc<Spatial>,
pub field: Weak<Field>,
zoneables: Mutex<FxHashMap<String, Arc<Node>>>,
captured: Registry<Spatial>,
}
impl Zone {
pub fn add_to(node: &Arc<Node>, spatial: Arc<Spatial>, field: &Arc<Field>) -> Arc<Zone> {
let zone = Arc::new(Zone {
spatial,
field: Arc::downgrade(field),
zoneables: Mutex::new(FxHashMap::default()),
captured: Registry::new(),
});
node.add_local_signal("capture", Zone::capture_flex);
node.add_local_signal("release", Zone::release_flex);
node.add_local_signal("update", Zone::update);
let _ = node.zone.set(zone.clone());
zone
}
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let zone = node.zone.get().unwrap();
let capture_path: &str = deserialize(message.as_ref())?;
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
capture(&spatial, zone);
Ok(())
}
fn release_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let capture_path: &str = deserialize(message.as_ref())?;
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
release(&spatial);
Ok(())
}
fn update(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let zone = node.zone.get().unwrap();
let Some(field) = zone.field.upgrade() else { return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed")) };
let Some((zone_client, zone_node)) = zone
.spatial
.node
.upgrade()
.and_then(|n| n.get_client().zip(Some(n))) else { return Err(color_eyre::eyre::eyre!("No client on node?")) };
let mut old_zoneables = zone.zoneables.lock();
for (_uid, zoneable) in old_zoneables.iter() {
zoneable.destroy();
}
let captured = zone.captured.get_valid_contents();
let zoneables = ZONEABLE_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|zoneable| zoneable.node.upgrade().is_some())
.filter(|zoneable| {
if captured
.iter()
.any(|captured| Arc::ptr_eq(captured, zoneable))
{
return true;
}
let spatial_zone_distance = zoneable.zone_distance();
let self_zone_distance = field.distance(zoneable, vec3a(0.0, 0.0, 0.0));
self_zone_distance < 0.0 && spatial_zone_distance > self_zone_distance
})
.filter_map(|zoneable| {
let alias = Alias::create(
&zone_client,
zone_node.get_path(),
&zoneable.uid,
&zoneable.node.upgrade().unwrap(),
AliasInfo {
server_signals: vec![
"set_transform",
"set_spatial_parent",
"set_spatial_parent_in_place",
],
server_methods: vec!["get_bounds", "get_transform"],
..Default::default()
},
)
.ok()?;
Some((zoneable.uid.clone(), alias))
})
.collect::<FxHashMap<String, Arc<Node>>>();
for entered_uid in zoneables.keys().filter(|k| !old_zoneables.contains_key(*k)) {
node.send_remote_signal("enter", serialize(entered_uid)?)?;
}
for left_uid in old_zoneables.keys().filter(|k| !zoneables.contains_key(*k)) {
node.send_remote_signal("leave", serialize(left_uid)?)?;
}
*old_zoneables = zoneables;
Ok(())
}
}
impl Drop for Zone {
fn drop(&mut self) {
for captured in self.captured.get_valid_contents() {
release(&captured);
}
}
}
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateZoneInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
}
let info: CreateZoneInfo = deserialize(message.as_ref())?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let node =
Node::create(&calling_client, "/spatial/zone", info.name, true).add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent), transform, false)?;
Zone::add_to(&node, space, &field);
Ok(())
}

View File

@@ -1,76 +1,155 @@
use crate::core::client::Client;
#[cfg(feature = "xwayland")]
use crate::wayland::xwayland::DISPLAY;
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
use super::Node;
use anyhow::{anyhow, Result};
use super::{
items::{ItemAcceptor, TypeInfo},
spatial::find_spatial,
Message, Node,
};
use color_eyre::eyre::Result;
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::sync::Arc;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
lazy_static::lazy_static! {
pub static ref DESKTOP_STARTUP_IDS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
pub static ref STARTUP_SETTINGS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
}
#[derive(Debug, Default, Clone)]
#[derive(Default, Clone)]
pub struct StartupSettings {
pub transform: Mat4,
pub acceptors: FxHashMap<&'static TypeInfo, Weak<ItemAcceptor>>,
}
impl StartupSettings {
pub fn add_to(node: &Arc<Node>) {
node.startup_settings
.set(Mutex::new(StartupSettings::default()))
.unwrap();
let _ = node
.startup_settings
.set(Mutex::new(StartupSettings::default()));
}
fn set_root_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let startup_id = flexbuffers::Reader::get_root(data)?.get_str()?;
let spatial_node = calling_client
.scenegraph
.get_node(startup_id)
.ok_or_else(|| anyhow!("Root spatial node does not exist"))?;
let spatial = spatial_node
.spatial
.get()
.ok_or_else(|| anyhow!("Root spatial node is not a spatial"))?;
fn set_root_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let spatial = find_spatial(
&calling_client,
"Root spatial",
deserialize(message.as_ref())?,
)?;
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
Ok(())
}
fn generate_desktop_startup_id_flex(
fn add_automatic_acceptor_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let acceptor_node =
calling_client.get_node("Item acceptor", deserialize(message.as_ref())?)?;
let acceptor =
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
let mut startup_settings = node.startup_settings.get().unwrap().lock();
startup_settings
.acceptors
.insert(acceptor.type_info, Arc::downgrade(acceptor));
Ok(())
}
fn generate_startup_token_flex(
node: &Node,
_calling_client: Arc<Client>,
_data: &[u8],
) -> Result<Vec<u8>> {
_message: Message,
) -> Result<Message> {
let id = nanoid::nanoid!();
let data = flexbuffers::singleton(id.as_str());
DESKTOP_STARTUP_IDS
let data = serialize(&id)?;
STARTUP_SETTINGS
.lock()
.insert(id, node.startup_settings.get().unwrap().lock().clone());
Ok(data)
Ok(data.into())
}
}
impl Debug for StartupSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StartupSettings")
.field("transform", &self.transform)
.field(
"acceptors",
&self
.acceptors
.iter()
.map(|(k, _)| k.type_name)
.collect::<Vec<_>>(),
)
.finish()
}
}
pub fn create_interface(client: &Arc<Client>) {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "startup", false);
node.add_local_signal("createStartupSettings", create_startup_settings_flex);
node.add_to_scenegraph();
node.add_local_signal("create_startup_settings", create_startup_settings_flex);
node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_startup_settings_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
message: Message,
) -> Result<()> {
let name = flexbuffers::Reader::get_root(data)?.get_str()?;
let node = Node::create(&calling_client, "/startup/settings", name, true).add_to_scenegraph();
let node = Node::create(
&calling_client,
"/startup/settings",
deserialize(message.as_ref())?,
true,
)
.add_to_scenegraph()?;
StartupSettings::add_to(&node);
node.add_local_signal("setRoot", StartupSettings::set_root_flex);
node.add_local_signal("set_root", StartupSettings::set_root_flex);
node.add_local_signal(
"add_automatic_acceptor",
StartupSettings::add_automatic_acceptor_flex,
);
node.add_local_method(
"generateDesktopStartupID",
StartupSettings::generate_desktop_startup_id_flex,
"generate_startup_token",
StartupSettings::generate_startup_token_flex,
);
Ok(())
}
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<Message> {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
var_env_insert!(env, DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,56 @@
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
input::{pointer::Pointer, InputMethod, InputType},
spatial::Spatial,
Node,
},
};
use color_eyre::eyre::Result;
use glam::Mat4;
use nanoid::nanoid;
use serde::Serialize;
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
use std::sync::Arc;
use stereokit::StereoKitMultiThread;
use tracing::instrument;
#[derive(Debug, Clone, Serialize)]
pub struct KeyboardEvent {
pub keyboard: String,
pub keymap: Option<String>,
pub keys_up: Option<Vec<u32>>,
pub keys_down: Option<Vec<u32>>,
}
pub struct EyePointer {
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
}
impl EyePointer {
pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
Ok(EyePointer { spatial, pointer })
}
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let ray = sk.input_eyes();
self.spatial
.set_local_transform(Mat4::from_rotation_translation(
ray.orientation,
ray.position,
));
{
// Set pointer input datamap
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("eye", 2);
map.end_map();
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
}
}
}

View File

@@ -1,3 +1,4 @@
pub mod eye_pointer;
pub mod mouse_pointer;
pub mod sk_controller;
pub mod sk_hand;

View File

@@ -1,59 +1,150 @@
use crate::nodes::{
input::{pointer::Pointer, InputMethod, InputType},
spatial::Spatial,
};
use glam::{vec3, Mat4};
use stardust_xr::{schemas::flat::Datamap, values::Transform};
use std::sync::{Arc, Weak};
use stereokit::{
input::{ButtonState, Key, Ray},
StereoKit,
use crate::{
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY},
fields::Ray,
input::{pointer::Pointer, InputMethod, InputType},
spatial::Spatial,
Node,
},
};
use color_eyre::eyre::Result;
use glam::{vec2, vec3, Mat4, Vec2, Vec3};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::flexbuffers;
use std::{convert::TryFrom, sync::Arc};
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
use tracing::instrument;
const SK_KEYMAP: &str = include_str!("sk.kmp");
#[derive(Default, Deserialize, Serialize)]
struct MouseDatamap {
select: f32,
grab: f32,
scroll: Vec2,
}
#[derive(Debug, Clone, Serialize)]
struct KeyboardEvent {
pub keyboard: String,
pub keymap: Option<String>,
pub keys_up: Option<Vec<u32>>,
pub keys_down: Option<Vec<u32>>,
}
pub struct MousePointer {
node: Arc<Node>,
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
datamap: TypedDatamap<MouseDatamap>,
keyboard_sender: Arc<PulseSender>,
}
impl MousePointer {
pub fn new() -> Self {
MousePointer {
pointer: InputMethod::new(
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
InputType::Pointer(Pointer::default()),
),
}
pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
let keyboard_mask = {
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("keyboard", "xkbv1");
map.end_map();
Mask(fbb.take_buffer())
};
let keyboard_sender = PulseSender::add_to(&node, keyboard_mask).unwrap();
Ok(MousePointer {
node,
spatial,
pointer,
datamap: Default::default(),
keyboard_sender,
})
}
pub fn update(&self, sk: &StereoKit) {
if let Some(ray) = Ray::from_mouse(sk.input_mouse()) {
self.pointer.spatial.set_local_transform_components(
None,
Transform {
position: Some(ray.pos),
rotation: Some(
glam::Quat::from_rotation_arc(vec3(0.0, 0.0, 1.0), ray.dir.into()).into(),
),
scale: None,
},
);
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let mouse = sk.input_mouse();
let ray = ray_from_mouse(mouse.pos).unwrap();
self.spatial.set_local_transform(
Mat4::look_to_rh(
Vec3::from(ray.pos),
Vec3::from(ray.dir),
vec3(0.0, 1.0, 0.0),
)
.inverse(),
);
{
// Set pointer input datamap
self.datamap.select = if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.datamap.grab = if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.datamap.scroll = vec2(0.0, mouse.scroll_change / 120.0);
*self.pointer.datamap.lock() = self.datamap.to_datamap().ok();
}
self.send_keyboard_input(sk);
}
fn send_keyboard_input(&self, sk: &impl StereoKitMultiThread) {
let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| {
let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(),
});
(rx, result)
})
.filter(|(_rx, result)| {
result.deepest_point_distance > 0.0 && result.min_distance < 0.05
})
.reduce(|(rx_a, result_a), (rx_b, result_b)| {
if result_a.deepest_point_distance < result_b.deepest_point_distance {
(rx_a, result_a)
} else {
(rx_b, result_b)
}
})
.map(|(rx, _)| rx);
if let Some(rx) = rx {
let mut keys_up = vec![];
let mut keys_down = vec![];
let keys = (8_u32..254)
.filter_map(|i| Some((i, Key::try_from(i).ok()?)))
.map(|(i, k)| (i - 8, sk.input_key(k)));
for (key, state) in keys {
if state.contains(ButtonState::JUST_ACTIVE) {
keys_down.push(key);
} else if state.contains(ButtonState::JUST_INACTIVE) {
keys_up.push(key);
}
}
let key_event = KeyboardEvent {
keyboard: "xkbv1".to_string(),
keymap: Some(SK_KEYMAP.to_string()),
keys_up: Some(keys_up),
keys_down: Some(keys_down),
};
let mut serializer = flexbuffers::FlexbufferSerializer::new();
let _ = key_event.serialize(&mut serializer);
rx.send_data(&self.node.uid, serializer.take_buffer())
.unwrap();
}
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push(
"select",
if sk.input_key(Key::MouseLeft).contains(ButtonState::Active) {
1.0f32
} else {
0.0f32
},
);
map.push(
"grab",
if sk.input_key(Key::MouseRight).contains(ButtonState::Active) {
1.0f32
} else {
0.0f32
},
);
map.end_map();
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
}
}

275
src/objects/input/sk.kmp Normal file
View File

@@ -0,0 +1,275 @@
xkb_keymap {
default xkb_keycodes "basic" {
minimum = 8;
maximum = 255;
<backspace> = 8;
<tab> = 9;
<return> = 13;
<shift> = 16;
<ctrl> = 17;
<alt> = 18;
<caps_lock> = 20;
<esc> = 27;
<space> = 32;
<end> = 35;
<home> = 36;
<left> = 37;
<right> = 39;
<up> = 38;
<down> = 40;
<page_up> = 33;
<page_down> = 34;
<printscreen> = 42;
<key_insert> = 45;
<del> = 46;
<0> = 48;
<1> = 49;
<2> = 50;
<3> = 51;
<4> = 52;
<5> = 53;
<6> = 54;
<7> = 55;
<8> = 56;
<9> = 57;
<a> = 65;
<b> = 66;
<c> = 67;
<d> = 68;
<e> = 69;
<f> = 70;
<g> = 71;
<h> = 72;
<i> = 73;
<j> = 74;
<k> = 75;
<l> = 76;
<m> = 77;
<n> = 78;
<o> = 79;
<p> = 80;
<q> = 81;
<r> = 82;
<s> = 83;
<t> = 84;
<u> = 85;
<v> = 86;
<w> = 87;
<x> = 88;
<y> = 89;
<z> = 90;
<num0> = 96;
<num1> = 97;
<num2> = 98;
<num3> = 99;
<num4> = 100;
<num5> = 101;
<num6> = 102;
<num7> = 103;
<num8> = 104;
<num9> = 105;
<f1> = 112;
<f2> = 113;
<f3> = 114;
<f4> = 115;
<f5> = 116;
<f6> = 117;
<f7> = 118;
<f8> = 119;
<f9> = 120;
<f10> = 121;
<f11> = 122;
<f12> = 123;
<comma> = 188;
<period> = 190;
<slash_fwd> = 191;
<slash_back> = 220;
<semicolon> = 186;
<apostrophe> = 222;
<bracket_open> = 219;
<bracket_close> = 221;
<minus> = 189;
<equals> = 187;
<backtick> = 192;
<lcmd> = 91;
<rcmd> = 92;
<multiply> = 106;
<add> = 107;
<subtract> = 109;
<decimal> = 110;
<divide> = 111;
};
partial default xkb_types "basic" {
virtual_modifiers Alt;
type "ONE_LEVEL" {
modifiers= none;
level_name[1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= 2;
level_name[1]= "Base";
level_name[2]= "Shift";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= 2;
map[Lock]= 2;
level_name[1]= "Base";
level_name[2]= "Caps";
};
type "SHIFT+ALT" {
modifiers= Shift+Alt;
map[Shift+Alt]= 2;
level_name[1]= "Base";
level_name[2]= "Shift+Alt";
};
type "PC_CONTROL_LEVEL2" {
modifiers= Control;
map[Control]= 2;
level_name[1]= "Base";
level_name[2]= "Control";
};
};
partial default xkb_compatibility "basic" {
interpret.useModMapMods= AnyLevel;
interpret.repeat= False;
interpret ISO_Level2_Latch+Exactly(Shift) {
useModMapMods=level1;
action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
};
interpret Caps_Lock+AnyOfOrNone(all) {
action= LockMods(modifiers=Lock);
};
indicator "Caps Lock" {
whichModState= locked;
modifiers= Lock;
};
};
default xkb_symbols "basic" {
name[Group1]="English (US)";
key <backspace> { [ BackSpace, BackSpace ] };
key <tab> { [ Tab, ISO_Left_Tab ] };
key <return> { [ Return ] };
key <shift> { [ Shift_L ] };
key <shift> { [ Shift_R ] };
key <ctrl> { [ Control_L ] };
key <ctrl> { [ Control_R ] };
key <alt> { [ Alt_L ] };
key <alt> { [ Alt_R ] };
key <caps_lock> { [ Caps_Lock ] };
key <esc> { [ Escape ] };
key <space> { [ space ] };
key <end> { [ End ] };
key <home> { [ Home ] };
key <left> { [ Left ] };
key <right> { [ Right ] };
key <up> { [ Up ] };
key <down> { [ Down ] };
key <page_up> { [ Page_Up ] };
key <page_down> { [ Page_Down ] };
key <printscreen> { [ Print ] };
key <key_insert> { [ Insert ] };
key <del> { [ Delete ] };
key <1> { [ 1, exclam ] };
key <2> { [ 2, at ] };
key <3> { [ 3, numbersign ] };
key <4> { [ 4, dollar ] };
key <5> { [ 5, percent ] };
key <6> { [ 6, asciicircum ] };
key <7> { [ 7, ampersand ] };
key <8> { [ 8, asterisk ] };
key <9> { [ 9, parenleft ] };
key <0> { [ 0, parenright ] };
key <a> { [ a, A ] };
key <b> { [ b, B ] };
key <c> { [ c, C ] };
key <d> { [ d, D ] };
key <e> { [ e, E ] };
key <f> { [ f, F ] };
key <g> { [ g, G ] };
key <h> { [ h, H ] };
key <i> { [ i, I ] };
key <j> { [ j, J ] };
key <k> { [ k, K ] };
key <l> { [ l, L ] };
key <m> { [ m, M ] };
key <n> { [ n, N ] };
key <o> { [ o, O ] };
key <p> { [ p, P ] };
key <q> { [ q, Q ] };
key <r> { [ r, R ] };
key <s> { [ s, S ] };
key <t> { [ t, T ] };
key <u> { [ u, U ] };
key <v> { [ v, V ] };
key <w> { [ w, W ] };
key <x> { [ x, X ] };
key <y> { [ y, Y ] };
key <z> { [ z, Z ] };
key <num0> { [ KP_0 ] };
key <num1> { [ KP_1 ] };
key <num2> { [ KP_2 ] };
key <num3> { [ KP_3 ] };
key <num4> { [ KP_4 ] };
key <num5> { [ KP_5 ] };
key <num6> { [ KP_6 ] };
key <num7> { [ KP_7 ] };
key <num8> { [ KP_8 ] };
key <num9> { [ KP_9 ] };
key <f1> { [ F1 ] };
key <f2> { [ F2 ] };
key <f3> { [ F3 ] };
key <f4> { [ F4 ] };
key <f5> { [ F5 ] };
key <f6> { [ F6 ] };
key <f7> { [ F7 ] };
key <f8> { [ F8 ] };
key <f9> { [ F9 ] };
key <f10> { [ F10 ] };
key <f11> { [ F11 ] };
key <f12> { [ F12 ] };
key <comma> { [ comma, less ] };
key <period> { [ period, greater ] };
key <slash_fwd> { [ slash, question ] };
key <slash_back> { [ backslash, bar ] };
key <semicolon> { [ semicolon, colon ] };
key <apostrophe> { [ apostrophe ] };
key <bracket_open> { [ bracketleft, braceleft ] };
key <bracket_close> { [ bracketright, braceright ] };
key <minus> { [ minus, underscore ] };
key <equals> { [ equal, plus ] };
key <backtick> { [ grave, asciitilde ] };
key <lcmd> { [ Super_L ] };
key <rcmd> { [ Super_R ] };
key <multiply> { [ KP_Multiply ] };
key <add> { [ KP_Add ] };
key <subtract> { [ KP_Subtract ] };
key <decimal> { [ KP_Decimal ] };
key <divide> { [ KP_Divide ] };
modifier_map Shift { <shift> };
modifier_map Lock { <caps_lock> };
modifier_map Control { <caps_lock> };
modifier_map Mod1 { <alt> };
modifier_map Mod4 { <lcmd>, <rcmd> };
};
};

View File

@@ -1,47 +1,76 @@
use crate::nodes::{
input::{tip::Tip, InputMethod, InputType},
spatial::Spatial,
use crate::{
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
input::{tip::Tip, InputMethod, InputType},
spatial::Spatial,
Node,
},
};
use glam::Mat4;
use stardust_xr::{schemas::flat::Datamap, values::Transform};
use std::sync::{Arc, Weak};
use color_eyre::eyre::Result;
use glam::{Mat4, Vec2};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::values::Transform;
use std::sync::Arc;
use stereokit::{
input::{ButtonState, Handed},
StereoKit,
ButtonState, Color128, Handed, Model, RenderLayer, StereoKitDraw, StereoKitMultiThread,
};
use tracing::instrument;
#[derive(Default, Deserialize, Serialize)]
struct ControllerDatamap {
select: f32,
grab: f32,
scroll: Vec2,
}
pub struct SkController {
tip: Arc<InputMethod>,
_node: Arc<Node>,
input: Arc<InputMethod>,
model: Model,
handed: Handed,
datamap: TypedDatamap<ControllerDatamap>,
}
impl SkController {
pub fn new(handed: Handed) -> Self {
SkController {
tip: InputMethod::new(
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
InputType::Tip(Tip::default()),
),
pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> {
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
let model = sk.model_create_mem("cursor", include_bytes!("cursor.glb"), None)?;
let tip = InputType::Tip(Tip::default());
let input = InputMethod::add_to(&_node, tip, None)?;
Ok(SkController {
_node,
input,
handed,
}
model,
datamap: Default::default(),
})
}
pub fn update(&mut self, sk: &StereoKit) {
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitDraw) {
let controller = sk.input_controller(self.handed);
*self.tip.enabled.lock() = controller.tracked.contains(ButtonState::Active);
if *self.tip.enabled.lock() {
self.tip.spatial.set_local_transform_components(
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
if *self.input.enabled.lock() {
sk.model_draw(
&self.model,
Mat4::from_rotation_translation(
controller.aim.orientation,
controller.aim.position,
),
Color128::default(),
RenderLayer::all(),
);
self.input.spatial.set_local_transform_components(
None,
Transform {
position: Some(controller.pose.position),
rotation: Some(controller.pose.orientation),
scale: None,
},
Transform::from_position_rotation(
controller.aim.position,
controller.aim.orientation,
),
);
}
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("select", controller.trigger);
map.push("grab", controller.grip);
map.end_map();
*self.tip.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
self.datamap.select = controller.trigger;
self.datamap.grab = controller.grip;
self.datamap.scroll = controller.stick;
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
}
}

View File

@@ -1,49 +1,67 @@
use crate::nodes::{
input::{hand::Hand, InputMethod, InputType},
spatial::Spatial,
use crate::{
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
input::{hand::Hand, InputMethod, InputType},
spatial::Spatial,
Node,
},
};
use color_eyre::eyre::Result;
use glam::Mat4;
use stardust_xr::schemas::flat::{Datamap, Hand as FlatHand, Joint};
use std::sync::{Arc, Weak};
use stereokit::{
input::{ButtonState, Handed, Joint as SkJoint},
StereoKit,
};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flat::{Hand as FlatHand, Joint};
use std::sync::Arc;
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
use tracing::instrument;
fn convert_joint(joint: SkJoint) -> Joint {
fn convert_joint(joint: HandJoint) -> Joint {
Joint {
position: joint.position,
rotation: joint.orientation,
position: joint.position.into(),
rotation: joint.orientation.into(),
radius: joint.radius,
}
}
#[derive(Default, Deserialize, Serialize)]
struct HandDatamap {
pinch_strength: f32,
grab_strength: f32,
}
pub struct SkHand {
hand: Arc<InputMethod>,
_node: Arc<Node>,
input: Arc<InputMethod>,
handed: Handed,
datamap: TypedDatamap<HandDatamap>,
}
impl SkHand {
pub fn new(handed: Handed) -> Self {
SkHand {
hand: InputMethod::new(
Spatial::new(Weak::new(), None, Mat4::IDENTITY),
InputType::Hand(Box::new(Hand {
base: FlatHand {
right: handed == Handed::Right,
..Default::default()
},
})),
),
pub fn new(handed: Handed) -> Result<Self> {
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
let hand = InputType::Hand(Box::new(Hand {
base: FlatHand {
right: handed == Handed::Right,
..Default::default()
},
}));
let input = InputMethod::add_to(&_node, hand, None)?;
Ok(SkHand {
_node,
input,
handed,
}
datamap: Default::default(),
})
}
pub fn update(&mut self, sk: &StereoKit) {
#[instrument(level = "debug", name = "Update Hand Input Method", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let sk_hand = sk.input_hand(self.handed);
if let InputType::Hand(hand) = &mut *self.hand.specialization.lock() {
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
let controller = sk.input_controller(self.handed);
*self.hand.enabled.lock() = controller.tracked.contains(ButtonState::Inactive)
&& sk_hand.tracked_state.contains(ButtonState::Active);
if *self.hand.enabled.lock() {
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE)
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE);
sk.input_hand_visible(self.handed, *self.input.enabled.lock());
if *self.input.enabled.lock() {
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
hand.base.thumb.distal = convert_joint(sk_hand.fingers[0][3]);
hand.base.thumb.proximal = convert_joint(sk_hand.fingers[0][2]);
@@ -62,24 +80,21 @@ impl SkHand {
finger.metacarpal = convert_joint(sk_finger[0]);
}
hand.base.palm.position = sk_hand.palm.position;
hand.base.palm.rotation = sk_hand.palm.orientation;
hand.base.palm.position = sk_hand.palm.position.into();
hand.base.palm.rotation = sk_hand.palm.orientation.into();
hand.base.palm.radius =
(sk_hand.fingers[2][0].radius + sk_hand.fingers[2][1].radius) * 0.5;
hand.base.wrist.position = sk_hand.wrist.position;
hand.base.wrist.rotation = sk_hand.wrist.orientation;
hand.base.wrist.position = sk_hand.wrist.position.into();
hand.base.wrist.rotation = sk_hand.wrist.orientation.into();
hand.base.wrist.radius =
(sk_hand.fingers[0][0].radius + sk_hand.fingers[4][0].radius) * 0.5;
hand.base.elbow = None;
}
}
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("grabStrength", sk_hand.grip_activation);
map.push("pinchStrength", sk_hand.pinch_activation);
map.end_map();
*self.hand.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
self.datamap.pinch_strength = sk_hand.pinch_activation;
self.datamap.grab_strength = sk_hand.grip_activation;
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
}
}

View File

@@ -1 +1,2 @@
pub mod input;
pub mod play_space;

73
src/objects/play_space.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::sync::Arc;
use color_eyre::eyre::Result;
use glam::Mat4;
use mint::Vector2;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stereokit::StereoKitMultiThread;
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
data::{Mask, PulseReceiver},
fields::{r#box::BoxField, Field},
spatial::Spatial,
Node,
},
};
#[derive(Debug, Deserialize, Serialize)]
struct PlaySpaceMap {
play_space: (),
size: Vector2<f32>,
}
impl Default for PlaySpaceMap {
fn default() -> Self {
Self {
play_space: (),
size: [0.0; 2].into(),
}
}
}
pub struct PlaySpace {
_node: Arc<Node>,
spatial: Arc<Spatial>,
field: Arc<Field>,
_pulse_rx: Arc<PulseReceiver>,
}
impl PlaySpace {
pub fn new() -> Result<Self> {
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false)?;
let field = BoxField::add_to(&node, [0.0; 3].into())?;
let pulse_rx =
PulseReceiver::add_to(&node, field.clone(), Mask::from_struct::<PlaySpaceMap>())?;
Ok(PlaySpace {
_node: node,
spatial,
field,
_pulse_rx: pulse_rx,
})
}
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let pose = sk.world_get_bounds_pose();
self.spatial
.set_local_transform(Mat4::from_rotation_translation(
pose.orientation,
pose.position,
));
let Field::Box(box_field) = self.field.as_ref() else {return};
box_field.set_size(
[
sk.world_get_bounds_size().x,
0.0,
sk.world_get_bounds_size().y,
]
.into(),
);
}
}

76
src/openxr/action.rs Normal file
View File

@@ -0,0 +1,76 @@
use super::Object;
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::Arc;
#[derive(Debug)]
pub struct Action {
// _info: InstanceInfo,
_localized_name: String,
suggested_bindings: Mutex<FxHashMap<String, String>>,
}
impl Action {
pub fn create_action_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::ActionSet(action_set) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
#[derive(Debug, Deserialize)]
struct CreateActionInfo {
name: String,
localized_name: String,
}
let info: CreateActionInfo = dbg!(deserialize(data)?);
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
&info.name,
true,
)
.add_to_scenegraph();
node.add_local_signal("suggest_binding", Self::suggest_binding_flex);
let action = Arc::new(Action {
_localized_name: info.localized_name,
suggested_bindings: Mutex::new(FxHashMap::default()),
});
action_set
.actions
.lock()
.insert(info.name, Arc::downgrade(&action));
node.openxr_object.set(Object::Action(action)).unwrap();
Ok(())
}
pub fn suggest_binding_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Action(action) = node.get_aspect("OpenXR interface", "Action", |n| &n.openxr_object)? else {
bail!("Object not an action")
};
#[derive(Debug, Deserialize)]
struct SuggestBindingArgs {
interaction_profile: String,
binding: String,
}
let args: SuggestBindingArgs = dbg!(deserialize(data)?);
action
.suggested_bindings
.lock()
.insert(args.interaction_profile, args.binding);
Ok(())
}
}

60
src/openxr/action_set.rs Normal file
View File

@@ -0,0 +1,60 @@
use super::{action::Action, Object};
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::{Arc, Weak};
#[derive(Debug)]
pub struct ActionSet {
// _info: InstanceInfo,
_localized_name: String,
_priority: u32,
pub actions: Mutex<FxHashMap<String, Weak<Action>>>,
}
impl ActionSet {
pub fn create_action_set_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
let Some(instance) = instance.get() else { bail!("Instance not initialized") };
#[derive(Deserialize)]
struct CreateActionSetInfo {
name: String,
localized_name: String,
priority: u32,
}
let info: CreateActionSetInfo = deserialize(data)?;
let node = Node::create(
&node.get_client().unwrap(),
"/openxr/action_set",
&info.name,
true,
)
.add_to_scenegraph();
node.add_local_signal("create_action", Action::create_action_flex);
let action_set = Arc::new(ActionSet {
_localized_name: info.localized_name,
_priority: info.priority,
actions: Mutex::new(FxHashMap::default()),
});
instance
.action_sets
.lock()
.insert(info.name, Arc::downgrade(&action_set));
node.openxr_object
.set(Object::ActionSet(action_set))
.unwrap();
Ok(())
}
}

47
src/openxr/instance.rs Normal file
View File

@@ -0,0 +1,47 @@
use super::{action_set::ActionSet, Object};
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, eyre, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::{Arc, Weak};
#[derive(Debug, Deserialize)]
struct InstanceInfo {
_app_info: ApplicationInfo,
_extension_names: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct ApplicationInfo {
_app_name: String,
_app_version: u32,
_engine_name: String,
_engine_version: u32,
_api_version: u64,
}
#[derive(Debug)]
pub struct Instance {
_info: InstanceInfo,
pub action_sets: Mutex<FxHashMap<String, Weak<ActionSet>>>,
}
impl Instance {
pub fn setup_instance_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
let instance_info = Instance {
_info: deserialize(data)?,
action_sets: Mutex::new(FxHashMap::default()),
};
dbg!(&instance_info);
instance
.set(Arc::new(instance_info))
.map_err(|_| eyre!("Instance already set up"))
}
}

34
src/openxr/mod.rs Normal file
View File

@@ -0,0 +1,34 @@
mod action;
mod action_set;
mod instance;
mod session;
mod system;
use self::{
action::Action, action_set::ActionSet, instance::Instance, session::Session, system::System,
};
use crate::{core::client::Client, nodes::Node};
use once_cell::sync::OnceCell;
use std::sync::Arc;
#[derive(Debug)]
pub enum Object {
Instance(OnceCell<Arc<Instance>>),
System(System),
Session(Session),
ActionSet(Arc<ActionSet>),
Action(Arc<Action>),
}
pub fn create_interface(client: &Arc<Client>) {
let node = Node::create(client, "", "openxr", false);
node.add_local_signal("setup_instance", Instance::setup_instance_flex);
node.add_local_method("get_system", System::get_system_flex);
node.add_local_signal("create_action_set", ActionSet::create_action_set_flex);
node.openxr_object
.set(Object::Instance(OnceCell::new()))
.unwrap();
node.add_to_scenegraph();
}

34
src/openxr/session.rs Normal file
View File

@@ -0,0 +1,34 @@
use std::sync::Arc;
use color_eyre::eyre::{bail, Result};
use stardust_xr::schemas::flex::deserialize;
use super::Object;
use crate::{core::client::Client, nodes::Node};
#[derive(Debug)]
pub struct Session {
// _info: InstanceInfo,
}
impl Session {
pub fn create_session_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::System(_system) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not a system")
};
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
deserialize(data)?,
true,
)
.add_to_scenegraph();
let session = Session {};
node.openxr_object.set(Object::Session(session)).unwrap();
Ok(())
}
}

76
src/openxr/system.rs Normal file
View File

@@ -0,0 +1,76 @@
use super::{session::Session, Object};
use crate::{core::client::Client, nodes::Node, SK_INFO};
use color_eyre::eyre::{bail, eyre, Result};
use serde::Serialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
#[derive(Debug)]
pub enum System {
Handheld,
HeadMounted,
}
impl System {
pub fn from_raw(raw: u32) -> Option<Self> {
match raw {
1 => Some(System::Handheld),
2 => Some(System::HeadMounted),
_ => None,
}
}
pub fn get_system_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
// let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
// bail!("Object not an instance")
// };
let system_type: u32 = deserialize(data)?;
let system = System::from_raw(system_type).ok_or_else(|| eyre!("No system exists!"))?;
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
&format!("system{}", system_type),
true,
)
.add_to_scenegraph();
node.add_local_method("views", System::views_flex);
node.add_local_signal("create_session", Session::create_session_flex);
node.openxr_object.set(Object::System(system)).unwrap();
Ok(serialize(system_type)?)
}
fn views_flex(_node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
let view_configuration_type: u64 = deserialize(data)?;
let view_count: u32 = match view_configuration_type {
1 => 1,
2 => 2,
1000037000 => 4,
1000054000 => 1,
_ => bail!("Invalid view config type"),
};
#[derive(Debug, Serialize)]
struct View {
recommended_image_rect_width: u32,
max_image_rect_width: u32,
recommended_image_rect_height: u32,
max_image_rect_height: u32,
}
let sk_info = SK_INFO.get().unwrap();
Ok(serialize(
(0..view_count)
.map(|_| View {
recommended_image_rect_width: sk_info.display_width,
max_image_rect_width: sk_info.display_width,
recommended_image_rect_height: sk_info.display_height,
max_image_rect_height: sk_info.display_height,
})
.collect::<Vec<_>>(),
)?)
}
}

View File

@@ -1,9 +1,16 @@
use super::{state::WaylandState, surface::CoreSurface};
use crate::wayland::surface::CoreSurface;
use super::state::{ClientState, WaylandState};
use portable_atomic::{AtomicU32, Ordering};
#[cfg(feature = "xwayland")]
use smithay::xwayland::XWaylandClientData;
use smithay::{
delegate_compositor,
reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::compositor::{self, CompositorHandler, CompositorState},
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
wayland::compositor::{self, CompositorClientState, CompositorHandler, CompositorState},
};
use std::sync::Arc;
use tracing::debug;
impl CompositorHandler for WaylandState {
fn compositor_state(&mut self) -> &mut CompositorState {
@@ -11,16 +18,37 @@ impl CompositorHandler for WaylandState {
}
fn commit(&mut self, surface: &WlSurface) {
compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| {
CoreSurface::new(
&self.weak_ref.upgrade().unwrap(),
&self.display,
self.display_handle.clone(),
surface,
)
})
debug!(?surface, "Surface commit");
let mut count = 0;
let core_surface = compositor::with_states(surface, |data| {
let count_new = data
.data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
if !count_new {
count = data
.data_map
.get::<AtomicU32>()
.unwrap()
.fetch_add(1, Ordering::Relaxed);
}
data.data_map.get::<Arc<CoreSurface>>().cloned()
});
if let Some(core_surface) = core_surface {
core_surface.commit(count);
}
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
if let Some(client_state) = client.get_data::<ClientState>() {
&client_state.compositor_state
} else {
#[cfg(feature = "xwayland")]
if let Some(xwayland_client_data) = client.get_data::<XWaylandClientData>() {
return &xwayland_client_data.compositor_state;
}
unimplemented!()
}
}
}

View File

@@ -1,34 +1,96 @@
use super::state::WaylandState;
use smithay::{
delegate_kde_decoration, delegate_xdg_decoration,
reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
wayland::shell::{
self, kde::decoration::KdeDecorationHandler, xdg::decoration::XdgDecorationHandler,
delegate_kde_decoration,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::{
zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1},
zxdg_toplevel_decoration_v1::{self, Mode, ZxdgToplevelDecorationV1},
},
shell::server::xdg_toplevel::XdgToplevel,
},
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{
Mode as KdeMode, OrgKdeKwinServerDecoration,
},
wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle,
GlobalDispatch, New, Resource, WEnum, Weak,
},
},
wayland::shell::{self, kde::decoration::KdeDecorationHandler},
};
impl XdgDecorationHandler for WaylandState {
fn new_decoration(&mut self, toplevel: smithay::wayland::shell::xdg::ToplevelSurface) {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(Mode::ServerSide);
});
toplevel.send_configure();
}
fn request_mode(
&mut self,
_toplevel: smithay::wayland::shell::xdg::ToplevelSurface,
_mode: smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
impl GlobalDispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ZxdgDecorationManagerV1>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &ZxdgDecorationManagerV1,
request: zxdg_decoration_manager_v1::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
zxdg_decoration_manager_v1::Request::Destroy => (),
zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => {
data_init.init(id, toplevel.downgrade());
}
_ => unreachable!(),
}
}
}
impl Dispatch<ZxdgToplevelDecorationV1, Weak<XdgToplevel>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
resource: &ZxdgToplevelDecorationV1,
request: zxdg_toplevel_decoration_v1::Request,
_data: &Weak<XdgToplevel>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
zxdg_toplevel_decoration_v1::Request::SetMode { mode: _ } => {
resource.configure(Mode::ServerSide);
}
zxdg_toplevel_decoration_v1::Request::UnsetMode => {
resource.configure(Mode::ServerSide);
}
zxdg_toplevel_decoration_v1::Request::Destroy => (),
_ => unreachable!(),
}
}
fn unset_mode(&mut self, _toplevel: smithay::wayland::shell::xdg::ToplevelSurface) {}
}
delegate_xdg_decoration!(WaylandState);
impl KdeDecorationHandler for WaylandState {
fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState {
&self.kde_decoration_state
}
fn new_decoration(&mut self, _surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) {
decoration.mode(KdeMode::Server);
}
fn request_mode(
&mut self,
_surface: &WlSurface,
decoration: &OrgKdeKwinServerDecoration,
mode: WEnum<KdeMode>,
) {
decoration.mode(mode.into_result().unwrap());
}
}
delegate_kde_decoration!(WaylandState);

View File

@@ -1,38 +1,43 @@
pub mod compositor;
mod compositor;
mod data_device;
pub mod decoration;
pub mod panel_item;
pub mod seat;
pub mod shaders;
pub mod state;
pub mod surface;
pub mod xdg_activation;
pub mod xdg_shell;
mod decoration;
mod seat;
mod shaders;
mod state;
mod surface;
// mod xdg_activation;
mod xdg_shell;
#[cfg(feature = "xwayland")]
pub mod xwayland;
use self::{panel_item::PanelItem, state::WaylandState, surface::CORE_SURFACES};
use crate::wayland::state::ClientState;
use anyhow::{ensure, Result};
use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::wayland::seat::SeatData;
use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{ensure, Result};
use global_counter::primitive::exact::CounterU32;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use slog::Logger;
use smithay::{
backend::{egl::EGLContext, renderer::gles2::Gles2Renderer},
reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket, Resource},
};
use sk::StereoKitDraw;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::egl::EGLContext;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportDma;
use smithay::reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket};
use std::os::unix::prelude::AsRawFd;
use std::{
ffi::c_void,
os::unix::{
net::UnixListener,
prelude::{FromRawFd, RawFd},
},
os::unix::{net::UnixListener, prelude::FromRawFd},
sync::Arc,
};
use stereokit as sk;
use stereokit::StereoKit;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
};
use tracing::{debug, debug_span, info, instrument};
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
struct EGLRawHandles {
display: *const c_void,
@@ -58,116 +63,133 @@ fn get_sk_egl() -> Result<EGLRawHandles> {
static GLOBAL_DESTROY_QUEUE: OnceCell<mpsc::Sender<GlobalId>> = OnceCell::new();
pub struct Wayland {
log: slog::Logger,
display: Arc<Mutex<Display<WaylandState>>>,
pub socket_name: String,
join_handle: JoinHandle<Result<()>>,
renderer: Gles2Renderer,
state: Arc<Mutex<WaylandState>>,
renderer: GlesRenderer,
dmabuf_rx: UnboundedReceiver<Dmabuf>,
wayland_state: Arc<Mutex<WaylandState>>,
#[cfg(feature = "xwayland")]
pub xwayland_state: xwayland::XWaylandState,
}
impl Wayland {
pub fn new(log: Logger) -> Result<Self> {
pub fn new() -> Result<Self> {
let egl_raw_handles = get_sk_egl()?;
let renderer = unsafe {
Gles2Renderer::new(
EGLContext::from_raw(
egl_raw_handles.display,
egl_raw_handles.config,
egl_raw_handles.context,
log.clone(),
)?,
log.clone(),
)?
GlesRenderer::new(EGLContext::from_raw(
egl_raw_handles.display,
egl_raw_handles.config,
egl_raw_handles.context,
)?)?
};
let display: Display<WaylandState> = Display::new()?;
let display_handle = display.handle();
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let display = Arc::new(Mutex::new(display));
let state = WaylandState::new(log.clone(), display.clone(), display_handle);
#[cfg(feature = "xwayland")]
let xwayland_state = xwayland::XWaylandState::create(&display_handle).unwrap();
let wayland_state =
WaylandState::new(display.clone(), display_handle, &renderer, dmabuf_tx);
let (global_destroy_queue_in, global_destroy_queue) = mpsc::channel(8);
GLOBAL_DESTROY_QUEUE.set(global_destroy_queue_in).unwrap();
let join_handle =
Wayland::start_loop(display.clone(), state.clone(), global_destroy_queue)?;
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
let socket_name = socket.socket_name().unwrap().to_str().unwrap().to_string();
WAYLAND_DISPLAY
.set(socket_name.clone())
.expect("seriously message nova this time they screwed up big time");
info!(socket_name, "Wayland active");
let join_handle = Wayland::start_loop(
display.clone(),
socket,
wayland_state.clone(),
global_destroy_queue,
)?;
Ok(Wayland {
log,
display,
socket_name,
join_handle,
renderer,
state,
dmabuf_rx,
wayland_state,
#[cfg(feature = "xwayland")]
xwayland_state,
})
}
fn start_loop(
display: Arc<Mutex<Display<WaylandState>>>,
socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>,
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
) -> Result<JoinHandle<Result<()>>> {
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
if let Some(socket_name) = socket.socket_name() {
println!("Wayland compositor {:?} active", socket_name);
}
let listen_async =
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
let dispatch_poll_fd: RawFd = display.lock().backend().poll_fd();
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?;
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
let dh1 = display.lock().handle();
let mut dh2 = dh1.clone();
Ok(tokio::task::spawn(async move {
Ok(task::new(|| "wayland loop", async move {
let _socket = socket; // Keep the socket alive
loop {
tokio::select! {
e = global_destroy_queue.recv() => { // New global to destroy
debug!(?e, "destroy global");
dh1.remove_global::<WaylandState>(e.unwrap());
}
acc = listen_async.accept() => { // New client connected
let (stream, _) = acc?;
let client = dh2.insert_client(stream.into_std()?, Arc::new(ClientState))?;
state.lock().new_client(client.id(), &dh2);
let client_state = Arc::new(ClientState {
compositor_state: Default::default(),
display: Arc::downgrade(&display),
seat: SeatData::new(&dh1)
});
let client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
client_state.seat.client.set(client.id()).unwrap();
}
e = dispatch_poll_listener.readable() => { // Dispatch
let mut guard = e?;
let mut display = display.lock();
display.dispatch_clients(&mut *state.lock())?;
display.flush_clients()?;
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
let mut display = display.lock();
display.dispatch_clients(&mut *state.lock())?;
display.flush_clients()?;
Ok(())
})?;
guard.clear_ready();
}
}
}
}))
})?)
}
pub fn frame(&mut self, sk: &StereoKit) {
let time_ms = (sk.time_getf() * 1000.) as u32;
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
pub fn update(&mut self, sk: &impl StereoKitDraw) {
while let Ok(dmabuf) = self.dmabuf_rx.try_recv() {
let _ = self.renderer.import_dmabuf(&dmabuf, None);
}
for core_surface in CORE_SURFACES.get_valid_contents() {
let client_id = core_surface.wl_surface().client_id().unwrap();
let seat_data = self.state.lock().seats.get(&client_id).unwrap().clone();
core_surface.process(
sk,
&mut self.renderer,
time_ms,
&self.log,
|data| {
PanelItem::on_mapped(&core_surface, data, seat_data);
},
|data| {
PanelItem::if_mapped(&core_surface, data);
},
);
core_surface.process(sk, &mut self.renderer);
}
self.display.lock().flush_clients().unwrap();
}
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
let state = self.wayland_state.lock();
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.frame(sk, state.output.clone());
}
}
pub fn make_context_current(&self) {
unsafe {
self.renderer.egl_context().make_current().unwrap();

View File

@@ -1,506 +0,0 @@
use super::{
seat::{KeyboardInfo, SeatData},
surface::CoreSurface,
};
use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
items::{register_item_ui_flex, Item, ItemSpecialization, ItemType, TypeInfo},
spatial::Spatial,
Node,
},
};
use anyhow::{anyhow, bail, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use nanoid::nanoid;
use serde::Deserialize;
use smithay::{
reexports::wayland_server::protocol::wl_pointer::{Axis, ButtonState},
utils::Size,
wayland::{compositor::SurfaceData, shell::xdg::XdgToplevelSurfaceData},
};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak};
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
lazy_static! {
static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel",
aliased_local_signals: vec![
"applySurfaceMaterial",
"applyCursorMaterial",
"pointerDeactivate",
"pointerScroll",
"pointerButton",
"pointerMotion",
"keyboardSetActive",
"keyboardSetKeyState",
"keyboardSetModifiers",
"resize",
"close",
],
aliased_local_methods: vec![],
aliased_remote_signals: vec!["resize", "setCursor",],
aliased_remote_methods: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
pub struct PanelItem {
node: Weak<Node>,
core_surface: Weak<CoreSurface>,
seat_data: SeatData,
}
impl PanelItem {
pub fn create(core_surface: &Arc<CoreSurface>, seat_data: SeatData) -> Arc<Node> {
let node = Arc::new(Node::create(
&INTERNAL_CLIENT,
"/item/panel/item",
&nanoid!(),
true,
));
Spatial::add_to(&node, None, Mat4::IDENTITY).unwrap();
let specialization = ItemType::Panel(PanelItem {
node: Arc::downgrade(&node),
core_surface: Arc::downgrade(core_surface),
seat_data,
});
let item = Item::add_to(&node, &ITEM_TYPE_INFO_PANEL, specialization);
if let ItemType::Panel(panel) = &item.specialization {
let _ = panel.seat_data.panel_item.set(Arc::downgrade(&item));
}
node.add_local_signal(
"applySurfaceMaterial",
PanelItem::apply_surface_material_flex,
);
node.add_local_signal("applyCursorMaterial", PanelItem::apply_cursor_material_flex);
node.add_local_signal("pointerDeactivate", PanelItem::pointer_deactivate_flex);
node.add_local_signal("pointerScroll", PanelItem::pointer_scroll_flex);
node.add_local_signal("pointerButton", PanelItem::pointer_button_flex);
node.add_local_signal("pointerMotion", PanelItem::pointer_motion_flex);
node.add_local_signal(
"keyboardActivateString",
PanelItem::keyboard_activate_string_flex,
);
node.add_local_signal(
"keyboardActivateNames",
PanelItem::keyboard_activate_names_flex,
);
node.add_local_signal("keyboardDeactivate", PanelItem::keyboard_deactivate_flex);
node.add_local_signal("keyboardKeyState", PanelItem::keyboard_key_state_flex);
node.add_local_signal("resize", PanelItem::resize_flex);
node
}
pub fn from_node(node: &Node) -> &PanelItem {
match &node.item.get().unwrap().specialization {
ItemType::Panel(panel_item) => panel_item,
_ => unreachable!(),
}
}
fn apply_surface_material_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct SurfaceMaterialInfo<'a> {
model_path: &'a str,
idx: u32,
}
let info: SurfaceMaterialInfo = deserialize(data)?;
let model_node = calling_client
.scenegraph
.get_node(info.model_path)
.ok_or_else(|| anyhow!("Model node not found"))?;
let model = model_node
.model
.get()
.ok_or_else(|| anyhow!("Node is not a model"))?;
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
core_surface.apply_material(model.clone(), info.idx);
}
}
Ok(())
}
fn apply_cursor_material_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(Deserialize)]
struct SurfaceMaterialInfo<'a> {
model_path: &'a str,
idx: u32,
}
let info: SurfaceMaterialInfo = deserialize(data)?;
let model_node = calling_client
.scenegraph
.get_node(info.model_path)
.ok_or_else(|| anyhow!("Model node not found"))?;
let model = model_node
.model
.get()
.ok_or_else(|| anyhow!("Node is not a model"))?;
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(cursor) = &*panel_item.seat_data.cursor.lock() {
if let Some(core_surface) = cursor.lock().core_surface.upgrade() {
core_surface.apply_material(model.clone(), info.idx);
}
}
}
Ok(())
}
pub fn on_mapped(
core_surface: &Arc<CoreSurface>,
surface_data: &SurfaceData,
seat_data: SeatData,
) {
if surface_data
.data_map
.get::<XdgToplevelSurfaceData>()
.is_some()
{
surface_data
.data_map
.insert_if_missing_threadsafe(|| PanelItem::create(core_surface, seat_data));
}
}
pub fn if_mapped(_core_surface: &Arc<CoreSurface>, surface_data: &SurfaceData) {
if let Some(panel_node) = surface_data.data_map.get::<Arc<Node>>() {
let panel_item = PanelItem::from_node(panel_node);
// core_surface.with_data(|core_surface_data| {
// panel_item.resize();
// });
panel_item.set_cursor();
}
}
pub fn resize(&self) {
self.core_surface.upgrade().unwrap().with_data(|data| {
let _ = self
.node
.upgrade()
.unwrap()
.send_remote_signal("resize", &serialize(data.size).unwrap());
});
}
pub fn set_cursor(&self) {
let mut cursor_changed = self.seat_data.cursor_changed.lock();
if !*cursor_changed {
return;
}
let mut data = serialize(()).unwrap();
if let Some(cursor) = &*self.seat_data.cursor.lock() {
let cursor = cursor.lock();
if let Some(core_surface) = cursor.core_surface.upgrade() {
if let Some(mapped_data) = &*core_surface.mapped_data.lock() {
data = serialize((mapped_data.size, cursor.hotspot)).unwrap();
} else {
return;
};
} else {
return;
}
}
let _ = self
.node
.upgrade()
.unwrap()
.send_remote_signal("setCursor", &data);
*cursor_changed = false;
}
fn pointer_deactivate_flex(
node: &Node,
_calling_client: Arc<Client>,
_data: &[u8],
) -> Result<()> {
let panel_item = PanelItem::from_node(node);
if *panel_item.seat_data.pointer_active.lock() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
if let Some(pointer) = panel_item.seat_data.pointer() {
pointer.leave(0, &core_surface.wl_surface());
*panel_item.seat_data.pointer_active.lock() = false;
pointer.frame();
core_surface.flush_clients();
}
}
}
Ok(())
}
fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
if let Some(size) = core_surface.with_data(|data| data.size) {
let mut position: Vector2<f64> = deserialize(data)?;
position.x = position.x.clamp(0.0, size.x as f64);
position.y = position.y.clamp(0.0, size.y as f64);
let mut pointer_active = panel_item.seat_data.pointer_active.lock();
if *pointer_active {
pointer.motion(0, position.x, position.y);
} else {
pointer.enter(0, &core_surface.wl_surface(), position.x, position.y);
*pointer_active = true;
}
pointer.frame();
core_surface.flush_clients();
}
}
}
}
Ok(())
}
fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let (button, state): (u32, u32) = deserialize(data)?;
pointer.button(
0,
0,
button,
match state {
0 => ButtonState::Released,
1 => ButtonState::Pressed,
_ => {
bail!("Button state is out of bounds")
}
},
);
pointer.frame();
core_surface.flush_clients();
}
}
}
}
Ok(())
}
fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct PointerScrollArgs {
axis_continuous: Vector2<f32>,
axis_discrete: Option<Vector2<f32>>,
}
let args: PointerScrollArgs = deserialize(data)?;
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let flex = flexbuffers::Reader::get_root(data)?;
if flex.flexbuffer_type().is_null() {
pointer.axis_stop(0, Axis::HorizontalScroll);
pointer.axis_stop(0, Axis::VerticalScroll);
} else {
pointer.axis(0, Axis::HorizontalScroll, args.axis_continuous.x as f64);
pointer.axis(0, Axis::VerticalScroll, args.axis_continuous.y as f64);
if let Some(axis_discrete_vec) = args.axis_discrete {
pointer.axis_discrete(
Axis::HorizontalScroll,
axis_discrete_vec.x as i32,
);
pointer.axis_discrete(
Axis::VerticalScroll,
axis_discrete_vec.y as i32,
);
}
}
pointer.frame();
core_surface.flush_clients();
}
}
}
}
Ok(())
}
fn keyboard_activate_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(|| anyhow!("Keymap is not valid"))?;
PanelItem::keyboard_activate_flex(node, &keymap)
}
fn keyboard_activate_names_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
#[derive(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(|| anyhow!("Keymap is not valid"))?;
PanelItem::keyboard_activate_flex(node, &keymap)
}
fn keyboard_activate_flex(node: &Node, keymap: &Keymap) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock();
if keyboard_info.is_none() {
keyboard.enter(0, &core_surface.wl_surface(), vec![]);
keyboard.repeat_info(0, 0);
}
keyboard_info.replace(KeyboardInfo::new(keymap));
keyboard_info.as_ref().unwrap().keymap.send(keyboard)?;
}
}
}
Ok(())
}
fn keyboard_deactivate_flex(
node: &Node,
_calling_client: Arc<Client>,
_data: &[u8],
) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock();
if keyboard_info.is_some() {
keyboard.leave(0, &core_surface.wl_surface());
*keyboard_info = None;
}
}
}
}
Ok(())
}
fn keyboard_key_state_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock();
if let Some(keyboard_info) = &mut *keyboard_info {
let (key, state): (u32, u32) = deserialize(data)?;
keyboard_info.process(key, state, keyboard)?;
}
}
}
Ok(())
}
fn resize_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let size: Vector2<u32> = deserialize(data)?;
let toplevel_surface = core_surface
.wayland_state()
.lock()
.xdg_shell_state
.toplevel_surfaces(|surfaces| {
surfaces
.iter()
.find(|surf| surf.wl_surface().clone() == core_surface.wl_surface())
.cloned()
});
if let Some(toplevel_surface) = toplevel_surface {
let mut size_set = false;
toplevel_surface.with_pending_state(|state| {
state.size = Some(Size::default());
state.size.as_mut().unwrap().w = size.x as i32;
state.size.as_mut().unwrap().h = size.y as i32;
size_set = true;
});
if size_set {
toplevel_surface.send_configure();
}
}
}
}
Ok(())
}
}
impl ItemSpecialization for PanelItem {
fn serialize_start_data(&self, id: &str) -> Vec<u8> {
// Panel size
let panel_size = self
.core_surface
.upgrade()
.unwrap()
.with_data(|data| data.size);
let cursor_lock = (*self.seat_data.cursor.lock()).clone();
let cursor_size = cursor_lock
.clone()
.and_then(|cursor| cursor.lock().core_surface.upgrade())
.and_then(|surf| surf.with_data(|data| data.size));
let cursor_hotspot = cursor_lock.map(|cursor| cursor.lock().hotspot);
serialize((id, (panel_size, cursor_size.zip(cursor_hotspot)))).unwrap()
}
}
pub fn register_panel_item_ui_flex(
_node: &Node,
calling_client: Arc<Client>,
_data: &[u8],
) -> Result<()> {
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
}

View File

@@ -1,57 +1,65 @@
use super::{state::WaylandState, surface::CoreSurface, GLOBAL_DESTROY_QUEUE};
use crate::nodes::items::Item;
use anyhow::Result;
use super::{
state::{ClientState, WaylandState},
surface::CoreSurface,
GLOBAL_DESTROY_QUEUE, SERIAL_COUNTER,
};
use crate::core::task;
use color_eyre::eyre::{eyre, Result};
use mint::Vector2;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rand::{seq::IteratorRandom, thread_rng};
use rustc_hash::{FxHashMap, FxHashSet};
use smithay::{
input::keyboard::{KeymapFile, ModifiersState},
reexports::wayland_server::{
backend::{ClientId, GlobalId},
delegate_dispatch, delegate_global_dispatch,
backend::{ClientId, GlobalId, ObjectId},
protocol::{
wl_keyboard::{self, KeyState, WlKeyboard},
wl_pointer::{self, WlPointer},
wl_pointer::{self, Axis, ButtonState, WlPointer},
wl_seat::{self, Capability, WlSeat, EVT_NAME_SINCE},
wl_surface::WlSurface,
wl_touch::{self, WlTouch},
},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, Weak as WlWeak,
},
wayland::compositor,
};
use std::sync::Arc;
use std::{ops::Deref, sync::Weak};
use xkbcommon::xkb::{self, Keymap};
pub struct Cursor {
pub core_surface: Weak<CoreSurface>,
pub hotspot: Vector2<i32>,
}
use std::{
collections::VecDeque,
sync::Arc,
time::{Duration, Instant},
};
use tokio::sync::watch;
use tracing::{debug, warn};
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
pub struct KeyboardInfo {
pub keymap: KeymapFile,
pub state: xkb::State,
pub mods: ModifiersState,
keymap: KeymapFile,
state: xkb::State,
mods: ModifiersState,
keys: FxHashSet<u32>,
}
impl KeyboardInfo {
pub fn new(keymap: &Keymap) -> Self {
KeyboardInfo {
state: xkb::State::new(keymap),
keymap: KeymapFile::new(keymap, None),
keymap: KeymapFile::new(keymap),
mods: ModifiersState::default(),
keys: FxHashSet::default(),
}
}
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<()> {
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<usize> {
let wl_key_state = match state {
0 => KeyState::Released,
1 => KeyState::Pressed,
_ => anyhow::bail!("Invalid key state!"),
_ => color_eyre::eyre::bail!("Invalid key state!"),
};
let xkb_key_state = match state {
0 => xkb::KeyDirection::Up,
1 => xkb::KeyDirection::Down,
_ => anyhow::bail!("Invalid key state!"),
_ => color_eyre::eyre::bail!("Invalid key state!"),
};
let state_components = self.state.update_key(key + 8, xkb_key_state);
if state_components != 0 {
@@ -64,30 +72,204 @@ impl KeyboardInfo {
0,
);
}
keyboard.key(0, 0, key, wl_key_state);
Ok(())
keyboard.key(SERIAL_COUNTER.inc(), 0, key, wl_key_state);
match wl_key_state {
KeyState::Pressed => {
self.keys.insert(key);
}
KeyState::Released => {
self.keys.remove(&key);
}
_ => unimplemented!(),
}
Ok(self.keys.len())
}
}
unsafe impl Send for KeyboardInfo {}
pub struct SeatDelegate;
#[derive(Debug, Clone, Copy)]
pub enum PointerEvent {
Motion(Vector2<f32>),
Button {
button: u32,
state: u32,
},
Scroll {
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>,
},
}
#[derive(Debug, Clone)]
pub enum KeyboardEvent {
Keymap,
Key { key: u32, state: u32 },
}
#[derive(Clone)]
pub struct SeatData(Arc<SeatDataInner>);
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1);
struct SurfaceInfo {
wl_surface: WlWeak<WlSurface>,
cursor_sender: watch::Sender<Option<CursorInfo>>,
pointer_queue: VecDeque<PointerEvent>,
pointer_latest_event: Instant,
keyboard_queue: VecDeque<KeyboardEvent>,
keyboard_info: Option<KeyboardInfo>,
}
impl SurfaceInfo {
fn new(wl_surface: &WlSurface, cursor_sender: watch::Sender<Option<CursorInfo>>) -> Self {
SurfaceInfo {
wl_surface: wl_surface.downgrade(),
cursor_sender,
pointer_queue: VecDeque::new(),
pointer_latest_event: Instant::now(),
keyboard_queue: VecDeque::new(),
keyboard_info: None,
}
}
fn flush(&self) {
if let Some(client) = self.wl_surface.upgrade().ok().and_then(|s| s.client()) {
if let Some(client_data) = client.get_data::<ClientState>() {
client_data.flush();
}
}
}
fn handle_pointer_events(&mut self, pointer: &WlPointer, mut locked: bool) -> bool {
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
let Some(core_surface) = CoreSurface::from_wl_surface(&focus) else { return false; };
let Some(focus_size) = core_surface.size() else { return false; };
if !self.pointer_queue.is_empty() {
self.pointer_latest_event = Instant::now();
}
while let Some(event) = self.pointer_queue.pop_front() {
match (locked, event) {
(false, PointerEvent::Motion(pos)) => {
pointer.enter(
SERIAL_COUNTER.inc(),
&focus,
(pos.x as f64).clamp(0.0, focus_size.x as f64),
(pos.y as f64).clamp(0.0, focus_size.y as f64),
);
locked = true;
}
(true, PointerEvent::Motion(pos)) => {
pointer.motion(
0,
(pos.x as f64).clamp(0.0, focus_size.x as f64),
(pos.y as f64).clamp(0.0, focus_size.y as f64),
);
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(true, PointerEvent::Button { button, state }) => {
pointer.button(
0,
0,
button,
match state {
0 => ButtonState::Released,
1 => ButtonState::Pressed,
_ => continue,
},
);
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(
true,
PointerEvent::Scroll {
axis_continuous,
axis_discrete,
},
) => {
if let Some(axis_continuous) = axis_continuous {
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64);
}
if pointer.version() >= wl_pointer::EVT_AXIS_DISCRETE_SINCE {
if let Some(axis_discrete) = axis_discrete {
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32);
}
}
if pointer.version() >= wl_pointer::EVT_AXIS_STOP_SINCE
&& axis_discrete.is_none()
&& axis_continuous.is_none()
{
pointer.axis_stop(0, Axis::HorizontalScroll);
pointer.axis_stop(0, Axis::VerticalScroll);
}
if pointer.version() >= wl_pointer::EVT_FRAME_SINCE {
pointer.frame();
}
}
(locked, event) => {
warn!(locked, ?event, "Invalid pointer event!");
}
}
}
if self.pointer_latest_event.elapsed() > POINTER_EVENT_TIMEOUT {
pointer.leave(SERIAL_COUNTER.inc(), &focus);
locked = false;
}
self.flush();
locked
}
fn handle_keyboard_events(&mut self, keyboard: &WlKeyboard, mut locked: bool) -> bool {
let Ok(focus) = self.wl_surface.upgrade() else { return false; };
let Some(info) = self.keyboard_info.as_mut() else { return true; };
if !locked {
keyboard.enter(0, &focus, vec![]);
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
locked = info.keymap.send(keyboard).is_ok();
}
while let Some(event) = self.keyboard_queue.pop_front() {
debug!(locked, ?event, "Process keyboard event");
match (locked, event) {
(true, KeyboardEvent::Keymap) => {
let _ = info.keymap.send(keyboard);
}
(true, KeyboardEvent::Key { key, state }) => {
if let Ok(key_count) = info.process(key, state, keyboard) {
if key_count == 0 {
keyboard.leave(SERIAL_COUNTER.inc(), &focus);
return false;
}
}
}
(locked, event) => {
warn!(locked, ?event, "Invalid keyboard event!");
}
}
}
self.flush();
locked
}
}
pub struct SeatData {
pub client: OnceCell<ClientId>,
global_id: OnceCell<GlobalId>,
surfaces: Mutex<FxHashMap<ObjectId, SurfaceInfo>>,
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
touch: OnceCell<WlTouch>,
}
impl SeatData {
pub fn new(dh: &DisplayHandle, client: ClientId) -> Self {
let seat_data = SeatData(Arc::new(SeatDataInner {
client,
pub fn new(dh: &DisplayHandle) -> Arc<Self> {
let seat_data = Arc::new(SeatData {
client: OnceCell::new(),
global_id: OnceCell::new(),
panel_item: OnceCell::new(),
cursor: Mutex::new(None),
cursor_changed: Mutex::new(false),
surfaces: Mutex::new(FxHashMap::default()),
pointer: OnceCell::new(),
pointer_active: Mutex::new(false),
keyboard: OnceCell::new(),
keyboard_info: Mutex::new(None),
touch: OnceCell::new(),
}));
});
seat_data
.global_id
@@ -96,53 +278,155 @@ impl SeatData {
seat_data
}
}
impl Deref for SeatData {
type Target = SeatDataInner;
fn deref(&self) -> &Self::Target {
&self.0
pub fn set_keymap_str(&self, keymap: &str, surfaces: Vec<WlSurface>) -> Result<()> {
let context = xkb::Context::new(0);
let keymap =
Keymap::new_from_string(&context, keymap.to_string(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
self.set_keymap(&keymap, surfaces);
Ok(())
}
}
pub fn set_keymap(&self, keymap: &Keymap, surfaces: Vec<WlSurface>) {
let mut panels = self.surfaces.lock();
let Some((_, focus)) = self.keyboard.get() else {return};
for surface in surfaces {
let Some(surface_info) = panels.get_mut(&surface.id()) else {continue};
surface_info
.keyboard_info
.replace(KeyboardInfo::new(keymap));
pub struct SeatDataInner {
client: ClientId,
pub global_id: OnceCell<GlobalId>,
pub panel_item: OnceCell<Weak<Item>>,
pub cursor: Mutex<Option<Arc<Mutex<Cursor>>>>,
pub cursor_changed: Mutex<bool>,
pointer: OnceCell<WlPointer>,
pub pointer_active: Mutex<bool>,
keyboard: OnceCell<WlKeyboard>,
pub keyboard_info: Mutex<Option<KeyboardInfo>>,
touch: OnceCell<WlTouch>,
}
impl SeatDataInner {
pub fn pointer(&self) -> Option<&WlPointer> {
self.pointer.get()
if *focus.lock() == surface.id() {
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
}
}
}
pub fn keyboard(&self) -> Option<&WlKeyboard> {
self.keyboard.get()
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
let mut surfaces = self.surfaces.lock();
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
surface_info.pointer_queue.push_back(event);
drop(surfaces);
self.handle_pointer_events();
}
#[allow(dead_code)]
pub fn touch(&self) -> Option<&WlTouch> {
self.touch.get()
pub fn keyboard_event(&self, surface: &WlSurface, event: KeyboardEvent) {
let mut surfaces = self.surfaces.lock();
let Some(surface_info) = surfaces.get_mut(&surface.id()) else {return};
surface_info.keyboard_queue.push_back(event);
drop(surfaces);
self.handle_keyboard_events();
}
fn handle_pointer_events(&self) {
let mut surfaces = self.surfaces.lock();
let Some((pointer, pointer_focus)) = self.pointer.get() else {return};
let mut pointer_focus = pointer_focus.lock();
loop {
let locked = !pointer_focus.is_null();
// Pick a pointer to focus on if there is none
if pointer_focus.is_null() {
*pointer_focus = surfaces
.iter()
.filter(|(_k, v)| !v.pointer_queue.is_empty())
.map(|(k, _v)| k)
.choose(&mut thread_rng())
.cloned()
.unwrap_or(ObjectId::null());
}
if pointer_focus.is_null() {
// If there's still none, guess we're done with pointer events for the time being
break;
}
let Some(surface_info) = surfaces.get_mut(&pointer_focus) else {break};
if surface_info.handle_pointer_events(pointer, locked) {
// We haven't gotten to a point where we can switch the focus
break;
} else {
*pointer_focus = ObjectId::null();
}
}
}
fn handle_keyboard_events(&self) {
let mut surfaces = self.surfaces.lock();
let Some((keyboard, keyboard_focus)) = self.keyboard.get() else {return};
let mut keyboard_focus = keyboard_focus.lock();
loop {
let locked = !keyboard_focus.is_null();
// Pick a keyboard to focus on if there is none
if keyboard_focus.is_null() {
*keyboard_focus = surfaces
.iter()
.filter(|(_k, v)| v.keyboard_info.is_some())
.filter(|(_k, v)| !v.keyboard_queue.is_empty())
.map(|(k, _v)| k)
.choose(&mut thread_rng())
.cloned()
.unwrap_or(ObjectId::null());
}
// If there's still none, guess we're done with keyboard events for the time being
let Some(surface_info) = surfaces.get_mut(&keyboard_focus) else {break};
if surface_info.handle_keyboard_events(keyboard, locked) {
// We haven't gotten to a point where we can switch the focus
break;
} else {
*keyboard_focus = ObjectId::null();
}
}
}
pub fn new_surface(&self, surface: &WlSurface) -> watch::Receiver<Option<CursorInfo>> {
let (tx, rx) = watch::channel(None);
self.surfaces
.lock()
.insert(surface.id(), SurfaceInfo::new(surface, tx));
rx
}
pub fn drop_surface(&self, surface: &WlSurface) {
self.surfaces.lock().remove(&surface.id());
if let Some((_, pointer_focus)) = self.pointer.get() {
let mut pointer_focus = pointer_focus.lock();
if *pointer_focus == surface.id() {
*pointer_focus = ObjectId::null();
}
}
if let Some((_, keyboard_focus)) = self.keyboard.get() {
let mut keyboard_focus = keyboard_focus.lock();
if *keyboard_focus == surface.id() {
*keyboard_focus = ObjectId::null();
}
}
}
}
impl Drop for SeatDataInner {
impl Drop for SeatData {
fn drop(&mut self) {
let id = self.global_id.take().unwrap();
tokio::spawn(async move { GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await });
let _ = task::new(|| "global destroy queue garbage collection", async move {
GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await
});
}
}
impl GlobalDispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
pub struct CursorInfo {
pub surface: WlWeak<WlSurface>,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CursorInfo {
pub fn cursor_data(&self) -> Option<(Vector2<u32>, Vector2<i32>)> {
let cursor_size = CoreSurface::from_wl_surface(&self.surface.upgrade().ok()?)?.size()?;
Some((cursor_size, [self.hotspot_x, self.hotspot_y].into()))
}
}
impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlSeat>,
data: &SeatData,
data: &Arc<SeatData>,
data_init: &mut DataInit<'_, WaylandState>,
) {
let resource = data_init.init(resource, data.clone());
@@ -154,48 +438,50 @@ impl GlobalDispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
resource.capabilities(Capability::Pointer | Capability::Keyboard);
}
fn can_view(client: Client, data: &SeatData) -> bool {
client.id() == data.0.client
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {
let Some(seat_client) = data.client.get().cloned() else {return false};
client.id() == seat_client
}
}
delegate_global_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
impl Dispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
impl Dispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlSeat,
request: <WlSeat as Resource>::Request,
data: &SeatData,
request: wl_seat::Request,
data: &Arc<SeatData>,
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_seat::Request::GetPointer { id } => {
let _ = data.0.pointer.set(data_init.init(id, data.clone()));
let pointer = data_init.init(id, data.clone());
let _ = data.pointer.set((pointer, Mutex::new(ObjectId::null())));
}
wl_seat::Request::GetKeyboard { id } => {
let keyboard = data_init.init(id, data.clone());
keyboard.repeat_info(0, 0);
let _ = data.0.keyboard.set(keyboard);
if keyboard.version() >= wl_keyboard::EVT_REPEAT_INFO_SINCE {
keyboard.repeat_info(0, 0);
}
let _ = data.keyboard.set((keyboard, Mutex::new(ObjectId::null())));
}
wl_seat::Request::GetTouch { id } => {
let _ = data.0.touch.set(data_init.init(id, data.clone()));
let _ = data.touch.set(data_init.init(id, data.clone()));
}
wl_seat::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
impl Dispatch<WlPointer, SeatData, WaylandState> for SeatDelegate {
impl Dispatch<WlPointer, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
_state: &mut WaylandState,
_client: &Client,
_resource: &WlPointer,
request: <WlPointer as Resource>::Request,
seat_data: &SeatData,
request: wl_pointer::Request,
seat_data: &Arc<SeatData>,
dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
@@ -206,54 +492,39 @@ impl Dispatch<WlPointer, SeatData, WaylandState> for SeatDelegate {
hotspot_x,
hotspot_y,
} => {
if !*seat_data.pointer_active.lock() {
return;
}
*seat_data.0.cursor_changed.lock() = true;
if let Some(surface) = surface.as_ref() {
CoreSurface::add_to(dh.clone(), surface, || (), |_| ());
compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| {
CoreSurface::new(
&state.weak_ref.upgrade().unwrap(),
&state.display,
dh.clone(),
surface,
)
});
if !data.data_map.insert_if_missing_threadsafe(|| {
Arc::new(Mutex::new(Cursor {
core_surface: Arc::downgrade(
data.data_map.get::<Arc<CoreSurface>>().unwrap(),
),
hotspot: Vector2::from([hotspot_x, hotspot_y]),
}))
}) {
let mut cursor =
data.data_map.get::<Arc<Mutex<Cursor>>>().unwrap().lock();
cursor.hotspot = Vector2::from([hotspot_x, hotspot_y]);
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1);
}
})
}
*seat_data.cursor.lock() = surface.and_then(|surf| {
compositor::with_states(&surf, |data| {
data.data_map.get::<Arc<Mutex<Cursor>>>().cloned()
})
let Some((_, focus)) = seat_data.pointer.get() else {return};
let focus = focus.lock();
let surfaces = seat_data.surfaces.lock();
let Some(surface_info) = surfaces.get(&focus) else {return};
let cursor_info = surface.map(|surface| CursorInfo {
surface: surface.downgrade(),
hotspot_x,
hotspot_y,
});
let _ = surface_info.cursor_sender.send_replace(cursor_info);
}
wl_pointer::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlPointer: SeatData] => SeatDelegate);
impl Dispatch<WlKeyboard, SeatData, WaylandState> for SeatDelegate {
impl Dispatch<WlKeyboard, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
_data: &SeatData,
_data: &Arc<SeatData>,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
@@ -263,15 +534,14 @@ impl Dispatch<WlKeyboard, SeatData, WaylandState> for SeatDelegate {
}
}
}
delegate_dispatch!(WaylandState: [WlKeyboard: SeatData] => SeatDelegate);
impl Dispatch<WlTouch, SeatData, WaylandState> for SeatDelegate {
impl Dispatch<WlTouch, Arc<SeatData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlTouch,
request: <WlTouch as Resource>::Request,
_data: &SeatData,
_data: &Arc<SeatData>,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
@@ -281,4 +551,3 @@ impl Dispatch<WlTouch, SeatData, WaylandState> for SeatDelegate {
}
}
}
delegate_dispatch!(WaylandState: [WlTouch: SeatData] => SeatDelegate);

View File

@@ -0,0 +1,16 @@
#version 320 es
precision mediump float;
precision highp int;
layout(binding = 0) uniform highp sampler2D diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 _entryPointOutput;
void main()
{
highp vec4 _101 = texture(diffuse, fs_uv);
highp vec3 _104 = pow(_101.xyz, vec3(2.2000000476837158203125));
_entryPointOutput = vec4(_104.x, _104.y, _104.z, _101.w);
}

View File

@@ -5,3 +5,9 @@
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
// Simula text shader (fragment)
// pub const SIMULA_FRAG_STR: &str = include_str!("simula.frag");
// Simula text shader (vertex)
// pub const SIMULA_VERT_STR: &str = include_str!("simula.vert");

View File

@@ -28,7 +28,7 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
o.uv = (input.uv) + uv_offset * uv_scale;
o.uv = (input.uv + uv_offset) * uv_scale;
return o;
}
float4 ps(psIn input) : SV_TARGET {

View File

@@ -4,19 +4,19 @@
//--name = stardust/text_shader
//--diffuse = white
//--fcFactor = 1.0
//--ripple = 4.0
//--uv_offset = 0.0, 0.0
//--uv_scale = 1.0, 1.0
//--fcFactor = 1.0
//--ripple = 4.0
//--alpha_min = 0.0
//--alpha_max = 1.0
Texture2D diffuse : register(t0);
SamplerState diffuse_s : register(s0);
float4 diffuse_i;
float fcFactor;
float ripple;
float2 uv_scale;
float2 uv_offset;
float fcFactor;
float ripple;
float alpha_min;
float alpha_max;
@@ -39,7 +39,7 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
o.uv = (input.uv) + uv_offset * uv_scale;
o.uv = (input.uv + uv_offset) * uv_scale;
return o;
}

View File

@@ -0,0 +1,75 @@
#version 320 es
#extension GL_OES_EGL_image_external : require
precision mediump float;
precision highp int;
layout(binding = 0, std140) uniform _Global
{
highp vec4 diffuse_i;
highp vec2 uv_scale;
highp vec2 uv_offset;
highp float fcFactor;
highp float ripple;
highp float alpha_min;
highp float alpha_max;
} uniforms;
layout(binding = 0) uniform highp samplerExternalOES diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 fragColor;
void main()
{
highp vec2 dx_uv = dFdx(fs_uv);
highp vec2 dy_uv = dFdy(fs_uv);
highp vec2 width = fs_uv * uniforms.diffuse_i.xy;
ivec2 _475 = ivec2(width);
highp vec2 _477 = clamp(floor(abs(vec2(dx_uv.x, dy_uv.y)) * uniforms.diffuse_i.xy), vec2(1.0), vec2(2.0));
ivec2 _480 = ivec2(_477);
ivec2 _481 = _475 - _480;
ivec2 _485 = _475 + _480;
int _487 = _481.y;
highp vec4 _671;
highp float _672;
_672 = 0.0;
_671 = vec4(0.0);
highp vec4 _679;
highp float _681;
for (int _670 = _487; _670 <= _485.y; _672 = _681, _671 = _679, _670++)
{
int _496 = _481.x;
_681 = _672;
_679 = _671;
highp vec4 _553;
highp float _556;
for (int _673 = _496; _673 <= _485.x; _681 = _556, _679 = _553, _673++)
{
highp float _509 = float(_673);
highp float _514 = (uniforms.fcFactor * (width.x - _509)) / _477.x;
highp float _520 = float(_670);
highp float _525 = (uniforms.fcFactor * (width.y - _520)) / _477.y;
highp float _533 = sqrt((_514 * _514) + (_525 * _525));
highp float _675;
do
{
if (_533 > 1.0)
{
_675 = 0.0;
break;
}
highp float _592 = pow(uniforms.ripple * sqrt(1.0 - (_533 * _533)), 2.0);
_675 = 1.0 + (_592 * (0.25 + (_592 * (0.015625 + (_592 * (0.00043402801384218037128448486328125 + (_592 * (6.7816799855791032314300537109375e-06 + (_592 * (6.7816799287356843706220388412476e-08 + (_592 * (4.709500012189948847662890329957e-10 + (_592 * (2.4028099388645474121517509047408e-12 + (_592 * (9.3859703944590075486154034933861e-15 + (_592 * (2.8968999943407451927966655969016e-17 + (7.242260299760125752555485045131e-20 * _592)))))))))))))))))));
break;
} while(false);
_553 = _679 + (texture2D(diffuse, (vec2(_509, _520) + vec2(0.5)) / uniforms.diffuse_i.xy) * _675);
_556 = _681 + _675;
}
}
highp vec4 _568 = _671 / vec4(_672);
highp vec3 _417 = pow(_568.xyz, vec3(2.2000000476837158203125));
highp vec4 _669 = vec4(_417.x, _417.y, _417.z, _568.w);
_669.w = uniforms.alpha_min + (_568.w * (uniforms.alpha_max - uniforms.alpha_min));
fragColor = _669;
}

View File

@@ -0,0 +1,61 @@
#version 320 es
// #ifdef GL_AMD_vertex_shader_layer
// #extension GL_AMD_vertex_shader_layer : enable
// #elif defined(GL_NV_viewport_array2)
// #extension GL_NV_viewport_array2 : enable
// #else
// #define gl_Layer int _dummy_gl_layer_var
// #endif
struct Inst
{
mat4 world;
vec4 color;
};
layout(binding = 1, std140) uniform StereoKitBuffer
{
layout(row_major) mat4 sk_view[2];
layout(row_major) mat4 sk_proj[2];
layout(row_major) mat4 sk_proj_inv[2];
layout(row_major) mat4 sk_viewproj[2];
vec4 sk_lighting_sh[9];
vec4 sk_camera_pos[2];
vec4 sk_camera_dir[2];
vec4 sk_fingertip[2];
vec4 sk_cubemap_i;
float sk_time;
uint sk_view_count;
} _38;
layout(binding = 2, std140) uniform TransformBuffer
{
layout(row_major) Inst sk_inst[819];
} _56;
layout(binding = 0, std140) uniform _Global
{
vec4 diffuse_i;
vec2 uv_scale;
vec2 uv_offset;
float fcFactor;
float ripple;
float alpha_min;
float alpha_max;
} _91;
layout(location = 0) in vec4 input_pos;
layout(location = 1) in vec3 input_norm;
layout(location = 2) in vec2 input_uv;
layout(location = 0) out vec2 fs_uv;
mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; }
void main()
{
uint _155 = uint(gl_InstanceID) % _38.sk_view_count;
gl_Position = spvWorkaroundRowMajor(_38.sk_viewproj[_155]) * vec4((spvWorkaroundRowMajor(_56.sk_inst[uint(gl_InstanceID) / _38.sk_view_count].world) * vec4(input_pos.xyz, 1.0)).xyz, 1.0);
fs_uv = (input_uv + _91.uv_offset) * _91.uv_scale;
// gl_Layer = int(_155);
}

View File

@@ -1,41 +1,59 @@
use crate::wayland::seat::SeatData;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use slog::Logger;
use smithay::{
delegate_output, delegate_shm,
output::{Output, Scale, Subpixel},
backend::{
allocator::dmabuf::Dmabuf,
egl::EGLDevice,
renderer::{gles::GlesRenderer, ImportDma},
},
delegate_dmabuf, delegate_output, delegate_shm,
output::{Mode, Output, Scale, Subpixel},
reexports::{
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode,
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
shell::server::xdg_wm_base::XdgWmBase,
},
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_data_device_manager::WlDataDeviceManager,
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
Display, DisplayHandle,
},
},
utils::Size,
utils::{Size, Transform},
wayland::{
buffer::BufferHandler,
compositor::CompositorState,
output::OutputManagerState,
shell::{
kde::decoration::KdeDecorationState,
xdg::{decoration::XdgDecorationState, XdgShellState},
compositor::{CompositorClientState, CompositorState},
dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
ImportError,
},
shell::kde::decoration::KdeDecorationState,
shm::{ShmHandler, ShmState},
xdg_activation::XdgActivationState,
},
};
use std::sync::{Arc, Weak};
use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn};
pub struct ClientState;
pub struct ClientState {
pub compositor_state: CompositorClientState,
pub display: Weak<Mutex<Display<WaylandState>>>,
pub seat: Arc<SeatData>,
}
impl ClientState {
pub fn flush(&self) {
let Some(display) = self.display.upgrade() else {return};
let _ = display.lock().flush_clients();
}
}
impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) {
println!("Wayland client {:?} connected", client_id);
info!("Wayland client {:?} connected", client_id);
}
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
println!(
info!(
"Wayland client {:?} disconnected because {:#?}",
client_id, reason
);
@@ -48,30 +66,64 @@ pub struct WaylandState {
pub display_handle: DisplayHandle,
pub compositor_state: CompositorState,
pub xdg_activation_state: XdgActivationState,
pub xdg_decoration_state: XdgDecorationState,
// pub xdg_activation_state: XdgActivationState,
pub kde_decoration_state: KdeDecorationState,
pub xdg_shell_state: XdgShellState,
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
dmabuf_tx: UnboundedSender<Dmabuf>,
pub output: Output,
pub seats: FxHashMap<ClientId, SeatData>,
}
impl WaylandState {
pub fn new(
log: Logger,
display: Arc<Mutex<Display<WaylandState>>>,
display_handle: DisplayHandle,
renderer: &GlesRenderer,
dmabuf_tx: UnboundedSender<Dmabuf>,
) -> Arc<Mutex<Self>> {
let compositor_state = CompositorState::new::<Self, _>(&display_handle, log.clone());
let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle, log.clone());
let xdg_shell_state = XdgShellState::new::<Self, _>(&display_handle, log.clone());
let xdg_decoration_state = XdgDecorationState::new::<Self, _>(&display_handle, log.clone());
let compositor_state = CompositorState::new::<Self>(&display_handle);
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
let kde_decoration_state =
KdeDecorationState::new::<Self, _>(&display_handle, Mode::Server, log.clone());
let shm_state = ShmState::new::<Self, _>(&display_handle, vec![], log.clone());
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
KdeDecorationState::new::<Self>(&display_handle, DecorationMode::Server);
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
.and_then(|device| device.try_get_render_node());
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => {
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
let dmabuf_default_feedback =
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => {
warn!("failed to query render node, dmabuf will use v3");
None
}
Err(err) => {
warn!(?err, "failed to egl device for display, dmabuf will use v3");
None
}
};
// if we failed to build dmabuf feedback we fall back to dmabuf v3
// Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display)
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<WaylandState>(
&display_handle,
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
} else {
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
};
let output = Output::new(
"1x".to_owned(),
smithay::output::PhysicalProperties {
@@ -80,13 +132,24 @@ impl WaylandState {
make: "Virtual XR Display".to_owned(),
model: "Your Headset Name Here".to_owned(),
},
log.clone(),
);
let _global = output.create_global::<Self>(&display_handle);
output.change_current_state(None, None, Some(Scale::Integer(2)), None);
let _output_global = output.create_global::<Self>(&display_handle);
let mode = Mode {
size: (2048, 2048).into(),
refresh: 60000,
};
output.change_current_state(
Some(mode),
Some(Transform::Normal),
Some(Scale::Integer(2)),
None,
);
output.set_preferred(mode);
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
println!("Init Wayland compositor");
info!("Init Wayland compositor");
Arc::new_cyclic(|weak| {
Mutex::new(WaylandState {
@@ -95,39 +158,42 @@ impl WaylandState {
display_handle,
compositor_state,
xdg_activation_state,
xdg_decoration_state,
// xdg_activation_state,
kde_decoration_state,
xdg_shell_state,
shm_state,
output_manager_state,
dmabuf_state,
dmabuf_tx,
output,
seats: FxHashMap::default(),
})
})
}
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 {
fn drop(&mut self) {
println!("Cleanly shut down the Wayland compositor");
info!("Cleanly shut down the Wayland compositor");
}
}
impl BufferHandler for WaylandState {
fn buffer_destroyed(
&mut self,
_buffer: &smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer,
) {
}
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
}
impl ShmHandler for WaylandState {
fn shm_state(&self) -> &smithay::wayland::shm::ShmState {
fn shm_state(&self) -> &ShmState {
&self.shm_state
}
}
impl DmabufHandler for WaylandState {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.dmabuf_state.0
}
fn dmabuf_imported(
&mut self,
_global: &DmabufGlobal,
dmabuf: Dmabuf,
) -> Result<(), dmabuf::ImportError> {
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed)
}
}
delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState);
delegate_output!(WaylandState);

View File

@@ -1,137 +1,116 @@
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
use crate::{
core::{destroy_queue, registry::Registry},
nodes::drawable::model::Model,
core::{delta::Delta, destroy_queue, registry::Registry},
nodes::drawable::model::ModelPart,
};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use slog::Logger;
use smithay::{
backend::renderer::{
gles2::{Gles2Renderer, Gles2Texture},
gles::{GlesRenderer, GlesTexture},
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
Texture,
Renderer, Texture,
},
desktop::utils::send_frames_surface_tree,
reexports::wayland_server::{
backend::ObjectId, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
},
output::Output,
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, DisplayHandle, Resource},
wayland::compositor::{self, SurfaceData},
};
use std::sync::{Arc, Weak};
use std::{ffi::c_void, sync::Arc, time::Duration};
use stereokit::{
material::{Material, Transparency},
shader::Shader,
texture::{Texture as SKTexture, TextureAddress, TextureFormat, TextureSample, TextureType},
StereoKit,
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
Transparency,
};
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
pub struct CoreSurfaceData {
wl_tex: Option<SendWrapper<Gles2Texture>>,
sk_tex: Option<SendWrapper<SKTexture>>,
sk_mat: Option<Arc<SendWrapper<Material>>>,
wl_tex: Option<SendWrapper<GlesTexture>>,
pub size: Vector2<u32>,
}
impl CoreSurfaceData {
fn new(sk: &StereoKit) -> Self {
let sk_tex = SendWrapper::new(
SKTexture::create(sk, TextureType::ImageNoMips, TextureFormat::RGBA32).unwrap(),
);
let sk_mat = {
let shader = Shader::from_mem(sk, PANEL_SHADER_BYTES).unwrap();
let mat = Material::create(sk, &shader).unwrap();
mat.set_parameter("diffuse", &*sk_tex);
mat.set_transparency(Transparency::Blend);
Arc::new(SendWrapper::new(mat))
};
CoreSurfaceData {
wl_tex: None,
sk_tex: Some(sk_tex),
sk_mat: Some(sk_mat),
size: Vector2::from([0, 0]),
}
}
fn update_tex(&mut self, data: &RendererSurfaceStateUserData, renderer: &Gles2Renderer) {
if let Some(surface_size) = data.borrow().surface_size() {
self.size = Vector2::from([surface_size.w as u32, surface_size.h as u32]);
}
self.wl_tex = data
.borrow()
.texture(renderer)
.cloned()
.map(SendWrapper::new);
if let Some(smithay_tex) = self.wl_tex.as_ref() {
let sk_tex = self.sk_tex.as_ref().unwrap();
unsafe {
sk_tex.set_native(
smithay_tex.tex_id() as usize,
smithay::backend::renderer::gles2::ffi::RGBA8.into(),
TextureType::Image,
smithay_tex.width(),
smithay_tex.height(),
false,
);
sk_tex.set_sample(TextureSample::Point);
sk_tex.set_address_mode(TextureAddress::Clamp);
}
}
}
}
impl Drop for CoreSurfaceData {
fn drop(&mut self) {
destroy_queue::add(self.wl_tex.take());
destroy_queue::add(self.sk_tex.take());
destroy_queue::add(self.sk_mat.take());
}
}
pub struct CoreSurface {
display: Weak<Mutex<Display<WaylandState>>>,
pub state: Weak<Mutex<WaylandState>>,
pub dh: DisplayHandle,
pub surface_id: ObjectId,
pub mapped_data: Mutex<Option<CoreSurfaceData>>,
pub pending_material_applications: Mutex<Vec<(Arc<Model>, u32)>>,
pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceCell<SendWrapper<Tex>>,
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
material_offset: Mutex<Delta<u32>>,
on_mapped: Box<dyn Fn() + Send + Sync>,
on_commit: Box<dyn Fn(u32) + Send + Sync>,
pub pending_material_applications: Registry<ModelPart>,
}
impl CoreSurface {
pub fn new(
state: &Arc<Mutex<WaylandState>>,
display: &Arc<Mutex<Display<WaylandState>>>,
pub fn add_to(
dh: DisplayHandle,
surface: &WlSurface,
) -> Arc<Self> {
CORE_SURFACES.add(CoreSurface {
display: Arc::downgrade(display),
state: Arc::downgrade(state),
dh,
surface_id: surface.id(),
mapped_data: Mutex::new(None),
pending_material_applications: Mutex::new(Vec::new()),
on_mapped: impl Fn() + Send + Sync + 'static,
on_commit: impl Fn(u32) + Send + Sync + 'static,
) {
compositor::with_states(surface, |data| {
data.data_map.insert_if_missing_threadsafe(|| {
CORE_SURFACES.add(CoreSurface {
dh,
weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
material_offset: Mutex::new(Delta::new(0)),
on_mapped: Box::new(on_mapped) as Box<dyn Fn() + Send + Sync>,
on_commit: Box::new(on_commit) as Box<dyn Fn(u32) + Send + Sync>,
pending_material_applications: Registry::new(),
})
});
});
}
pub fn commit(&self, count: u32) {
(self.on_commit)(count);
}
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
compositor::with_states(surf, |data| {
data.data_map.get::<Arc<CoreSurface>>().cloned()
})
}
pub fn process<F: FnOnce(&SurfaceData), M: FnOnce(&SurfaceData)>(
&self,
sk: &StereoKit,
renderer: &mut Gles2Renderer,
time_ms: u32,
log: &Logger,
on_mapped: F,
if_mapped: M,
) {
// Let Smithay handle all the buffer maintenance
on_commit_buffer_handler(&self.wl_surface());
// Import all surface buffers into textures
import_surface_tree(renderer, &self.wl_surface(), log).unwrap();
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {return};
let mapped = compositor::with_states(&self.wl_surface(), |data| {
let sk_tex = self.sk_tex.get_or_init(|| {
SendWrapper::new(sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32))
});
self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES).unwrap();
// let _ = renderer.with_context(|c| unsafe {
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
// });
let mat = sk.material_create(&shader);
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
sk.material_set_transparency(&mat, Transparency::Blend);
Arc::new(SendWrapper::new(mat))
});
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
on_commit_buffer_handler::<WaylandState>(&wl_surface);
// Import all surface buffers into textures
if import_surface_tree(renderer, &wl_surface).is_err() {
return;
}
let mapped = compositor::with_states(&wl_surface, |data| {
data.data_map
.get::<RendererSurfaceStateUserData>()
.map(|surface_states| surface_states.borrow().wl_buffer().is_some())
.map(|surface_states| surface_states.borrow().buffer().is_some())
.unwrap_or(false)
});
@@ -141,80 +120,99 @@ impl CoreSurface {
let mut mapped_data = self.mapped_data.lock();
let just_mapped = mapped_data.is_none();
if just_mapped {
*mapped_data = Some(CoreSurfaceData::new(sk));
}
drop(mapped_data);
self.with_states(|data| {
self.with_data(|mapped_data| {
mapped_data.update_tex(
data.data_map.get::<RendererSurfaceStateUserData>().unwrap(),
renderer,
let renderer_surface_state = data
.data_map
.get::<RendererSurfaceStateUserData>()
.unwrap()
.borrow();
let smithay_tex = renderer_surface_state
.texture::<GlesRenderer>(renderer.id())
.unwrap()
.clone();
let sk_tex = self.sk_tex.get().unwrap();
let sk_mat = self.sk_mat.get().unwrap();
unsafe {
sk.tex_set_surface(
sk_tex.as_ref(),
smithay_tex.tex_id() as usize as *mut c_void,
TextureType::IMAGE_NO_MIPS,
smithay::backend::renderer::gles::ffi::RGBA8.into(),
smithay_tex.width() as i32,
smithay_tex.height() as i32,
1,
false,
);
});
self.apply_surface_materials();
if just_mapped {
on_mapped(data);
sk.tex_set_sample(sk_tex.as_ref(), TextureSample::Point);
sk.tex_set_address(sk_tex.as_ref(), TextureAddress::Clamp);
}
if let Some(material_offset) = self.material_offset.lock().delta() {
sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32);
}
if_mapped(data);
});
send_frames_surface_tree(&self.wl_surface(), time_ms);
let surface_size = renderer_surface_state.surface_size().unwrap();
let new_mapped_data = CoreSurfaceData {
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
wl_tex: Some(SendWrapper::new(smithay_tex)),
};
*mapped_data = Some(new_mapped_data);
});
drop(mapped_data);
if just_mapped {
(self.on_mapped)();
}
self.apply_surface_materials();
}
pub fn apply_material(&self, model: Arc<Model>, material_idx: u32) {
self.pending_material_applications
.lock()
.push((model, material_idx));
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
let Some(wl_surface) = self.wl_surface() else {return};
send_frames_surface_tree(
&wl_surface,
&output,
Duration::from_secs_f64(sk.time_get()),
None,
|_, _| Some(output.clone()),
);
}
pub fn set_material_offset(&self, material_offset: u32) {
*self.material_offset.lock().value_mut() = material_offset;
}
pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
self.pending_material_applications.add_raw(model_part)
}
fn apply_surface_materials(&self) {
self.with_data(|mapped_data| {
let mut pending_material_applications = self.pending_material_applications.lock();
for (model, material_idx) in &*pending_material_applications {
model
.pending_material_replacements
.lock()
.insert(*material_idx, mapped_data.sk_mat.clone().unwrap());
}
pending_material_applications.clear();
});
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
}
self.pending_material_applications.clear();
}
pub fn wayland_state(&self) -> Arc<Mutex<WaylandState>> {
self.state.upgrade().unwrap()
pub fn wl_surface(&self) -> Option<WlSurface> {
self.weak_surface.upgrade().ok()
}
pub fn wl_surface(&self) -> WlSurface {
WlSurface::from_id(&self.dh, self.surface_id.clone()).unwrap()
}
pub fn with_states<F, T>(&self, f: F) -> T
pub fn with_states<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&SurfaceData) -> T,
{
compositor::with_states(&self.wl_surface(), f)
self.wl_surface()
.map(|wl_surface| compositor::with_states(&wl_surface, f))
}
pub fn with_data<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&mut CoreSurfaceData) -> T,
{
self.mapped_data.lock().as_mut().map(f)
}
pub fn flush_clients(&self) {
self.display
.upgrade()
.unwrap()
.lock()
.flush_clients()
.unwrap();
pub fn size(&self) -> Option<Vector2<u32>> {
self.mapped_data.lock().as_ref().map(|d| d.size)
}
}
impl Drop for CoreSurface {
fn drop(&mut self) {
CORE_SURFACES.remove(self);
destroy_queue::add(self.sk_tex.take());
destroy_queue::add(self.sk_mat.take());
}
}

File diff suppressed because it is too large Load Diff

398
src/wayland/xwayland.rs Normal file
View File

@@ -0,0 +1,398 @@
use super::{
seat::{KeyboardEvent, PointerEvent, SeatData},
state::ClientState,
xdg_shell::PopupData,
};
use crate::{
nodes::{
drawable::model::ModelPart,
items::panel::{Backend, PanelItem, RecommendedState, SurfaceID},
Message,
},
wayland::surface::CoreSurface,
};
use color_eyre::eyre::Result;
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use smithay::{
reexports::{
calloop::{EventLoop, LoopSignal},
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource, WEnum},
x11rb::protocol::xproto::Window,
},
utils::{Logical, Rectangle},
wayland::compositor,
xwayland::{
xwm::{Reorder, ResizeEdge, XwmId},
X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler,
},
};
use stardust_xr::schemas::flex::serialize;
use std::{ffi::OsStr, iter::empty, sync::Arc, time::Duration};
use tokio::sync::oneshot;
use tracing::debug;
pub static DISPLAY: OnceCell<String> = OnceCell::new();
pub struct XWaylandState {
pub display: u32,
event_loop_signal: LoopSignal,
}
impl XWaylandState {
pub fn create(dh: &DisplayHandle) -> Result<Self> {
let dh = dh.clone();
let (tx, rx) = oneshot::channel();
tokio::task::spawn_blocking(move || {
let mut event_loop: EventLoop<XWaylandHandler> = EventLoop::try_new()?;
let (xwayland, connection) = XWayland::new(&dh);
let handle = event_loop.handle();
event_loop
.handle()
.insert_source(connection, {
let dh = dh.clone();
move |event, _, handler| match event {
XWaylandEvent::Ready {
connection,
client,
client_fd: _,
display: _,
} => {
handler.seat = client.get_data::<ClientState>().map(|s| s.seat.clone());
handler.wm =
X11Wm::start_wm(handle.clone(), dh.clone(), connection, client)
.ok();
}
XWaylandEvent::Exited => (),
}
})
.map_err(|e| e.error)?;
let display = xwayland.start(
event_loop.handle(),
None,
empty::<(&OsStr, &OsStr)>(),
true,
|_| (),
)?;
let _ = tx.send(XWaylandState {
display,
event_loop_signal: event_loop.get_signal(),
});
let mut handler = XWaylandHandler {
wayland_display_handle: dh,
wm: None,
seat: None,
};
event_loop.run(Duration::from_millis(100), &mut handler, |_| ())
});
let state = rx.blocking_recv()?;
let _ = DISPLAY.set(format!(":{}", state.display));
Ok(state)
}
}
impl Drop for XWaylandState {
fn drop(&mut self) {
self.event_loop_signal.stop();
}
}
struct XWaylandHandler {
wayland_display_handle: DisplayHandle,
wm: Option<X11Wm>,
seat: Option<Arc<SeatData>>,
}
impl XWaylandHandler {
fn panel_item(&self, window: &X11Surface) -> Option<Arc<PanelItem<X11Backend>>> {
compositor::with_states(&window.wl_surface()?, |s| {
s.data_map.get::<Arc<PanelItem<X11Backend>>>().cloned()
})
}
}
impl XwmHandler for XWaylandHandler {
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
self.wm.as_mut().unwrap()
}
fn new_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "New X window");
}
fn new_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "New X override redirect window");
}
fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map window request");
window.set_mapped(true).unwrap();
}
fn map_window_notify(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map window notify");
let dh = self.wayland_display_handle.clone();
let seat = self.seat.clone().unwrap();
CoreSurface::add_to(
self.wayland_display_handle.clone(),
&window.wl_surface().unwrap(),
{
let window = window.clone();
move || {
let Some(wl_surface) = window.wl_surface() else {return};
let seat = seat.clone();
window.user_data().insert_if_missing_threadsafe(|| {
let (_node, panel_item) = PanelItem::create(
Box::new(X11Backend {
toplevel_parent: None,
toplevel: window.clone(),
seat,
_pointer_grab: Mutex::new(None),
_keyboard_grab: Mutex::new(None),
}),
wl_surface
.client()
.and_then(|c| c.get_credentials(&dh).ok())
.map(|c| c.pid),
);
panel_item
});
}
},
move |_| {
let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>() else {return};
panel_item.commit_toplevel();
},
);
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map override redirect window");
}
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Unmap X window");
}
fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Destroy X window");
}
fn configure_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
x: Option<i32>,
y: Option<i32>,
w: Option<u32>,
h: Option<u32>,
reorder: Option<Reorder>,
) {
debug!(?window, x, y, w, h, ?reorder, "Configure X window");
}
fn configure_notify(
&mut self,
_xwm: XwmId,
window: X11Surface,
geometry: Rectangle<i32, Logical>,
above: Option<Window>,
) {
debug!(?window, ?geometry, above, "Configure X window");
}
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, button: u32) {
let Some(panel_item) = self.panel_item(&window) else {return};
debug!(?window, button, "X window requests move");
panel_item.recommend_toplevel_state(RecommendedState::Move);
}
fn resize_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
button: u32,
resize_edge: ResizeEdge,
) {
let Some(panel_item) = self.panel_item(&window) else {return};
debug!(?window, button, ?resize_edge, "X window requests resize");
panel_item.recommend_toplevel_state(RecommendedState::Resize(
WEnum::Value(match resize_edge {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom,
ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left,
ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft,
ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right,
ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight,
ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight,
})
.into(),
));
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(true));
}
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(false));
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Minimize);
}
}
pub struct X11Backend {
pub toplevel_parent: Option<X11Surface>,
pub toplevel: X11Surface,
pub seat: Arc<SeatData>,
_pointer_grab: Mutex<Option<SurfaceID>>,
_keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl X11Backend {
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => None,
SurfaceID::Toplevel => self.toplevel.wl_surface(),
SurfaceID::Popup(_) => None,
}
}
// fn flush_client(&self) {
// let Some(client) = self.toplevel.wl_surface().and_then(|s| s.client()) else {return};
// if let Some(client_state) = client.get_data::<ClientState>() {
// client_state.flush();
// }
// }
}
impl Backend for X11Backend {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
let size = (
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
);
let toplevel_state = (
None::<String>,
self.toplevel.title(),
None::<String>,
(
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
),
self.toplevel.min_size().map(|s| (s.w as u32, s.h as u32)),
self.toplevel.max_size().map(|s| (s.w as u32, s.w as u32)),
((0_i32, 0_i32), size),
vec![0_u32; 0],
);
let info = (
None::<(Vector2<u32>, Vector2<i32>)>,
toplevel_state,
Vec::<PopupData>::new(),
None::<SurfaceID>,
None::<SurfaceID>,
);
Ok(serialize((id, info))?.into())
}
fn serialize_toplevel(&self) -> Result<Message> {
let toplevel_state = (
None::<String>,
self.toplevel.title(),
None::<String>,
(
self.toplevel.geometry().size.w,
self.toplevel.geometry().size.h,
),
self.toplevel.min_size().map(|s| (s.w, s.h)),
self.toplevel.max_size().map(|s| (s.w, s.w)),
);
let data = serialize(&toplevel_state)?;
Ok(data.into())
}
fn set_toplevel_capabilities(&self, _capabilities: Vec<u8>) {}
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
_bounds: Option<Vector2<u32>>,
) {
let _ = self.toplevel.configure(
size.map(|s| Rectangle::from_loc_and_size((0, 0), (s.x as i32, s.y as i32))),
);
let _ = self.toplevel.set_maximized(states.contains(&1));
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
core_surface.apply_material(model_part);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
button,
state: if pressed { 1 } else { 0 },
},
)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
axis_continuous: scroll_distance,
axis_discrete: scroll_steps,
},
)
}
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
self.seat.set_keymap_str(
&keymap,
if let Some(toplevel) = self.toplevel.wl_surface() {
vec![toplevel]
} else {
vec![]
},
)
}
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key,
state: if state { 1 } else { 0 },
},
)
}
}

View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Name=Stardust XR
Comment=FOSS XR system UI (display server) project
Exec=stardust-xr-server
Type=Application