188 Commits

Author SHA1 Message Date
Nova
8cc20e054c feat: version bump 2023-06-11 01:37:21 -04:00
Nova
b12b171b53 feat: spatial bounds 2023-06-11 00:38:05 -04:00
Nova
0e61d51072 feat(input): custom pointers 2023-05-31 08:50:15 -04:00
Nova
e61c04960e fix(node): better send remote signal 2023-05-31 08:48:59 -04:00
Nova
5dc82be1a3 fix(pointer): proper direction 2023-05-31 08:48:24 -04:00
Nova
6861b92972 fix(main): make eye pointer not work in flatscreen 2023-05-31 08:47:16 -04:00
Nova
f68f350cd2 fix(scenegraph): recurse through aliases 2023-05-31 08:47:02 -04:00
Nova
2820415373 feat: readd dmabufs 2023-05-30 02:20:27 -04:00
Nova
f721a57604 fix: janky dmabuf hack 2023-05-27 09:48:34 -04:00
Nova
fb4149eaa7 feat: eye gaze support 2023-05-23 18:56:46 -04:00
matthewcroughan
d3746ef787 ci: print flatland revision for gnome-graphical-test in Discord message 2023-05-20 11:08:43 +01:00
matthewcroughan
9d4b4bee4d 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
5390b0effb 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
13da4c8d60 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
1740d55f9c 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
52d5e97de6 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
633df045d4 feat: appimage support!! 2023-05-19 18:12:22 -04:00
Nova
415bf5bb04 feat: clean up main function 2023-05-19 18:11:52 -04:00
Nova
4e2d4a15c9 feat: flat wayland display env var 2023-05-18 06:00:53 -04:00
Nova
ef0142183d fix: better pointer compare distance algorithm 2023-05-10 23:44:36 -04:00
Nova
e5dfd9d3df fix(model): copy on create to make unique 2023-05-10 23:44:23 -04:00
Nova
6773fe2cf3 feat: instant model loading 2023-05-10 20:10:31 -04:00
Nova
5a6e7e02ca fix(wayland): stop crash 2023-05-10 19:14:57 -04:00
Nova
c5d8ec2ef1 fix: remove dbg statement 2023-05-10 19:02:09 -04:00
Nova
a31781146e fix: upgrade smithay 2023-05-10 16:51:39 -04:00
Nova
cb9368cb8e fix: model nodes 2023-05-10 16:49:56 -04:00
Nova
629c05e507 feat: model nodes 2023-05-10 08:38:21 -04:00
Matthew Croughan
9123153bf3 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
f3dc632ffc feat: order inputs 2023-05-02 21:58:53 -04:00
Astavie
c369100d8a 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
e10d40ef5e 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
d6ca367187 feat: FUNDING.yml 2023-05-01 17:31:03 +00:00
Nova
88ac8a8b86 better panel item startup settings order 2023-05-01 12:59:49 -04:00
Nova
70fef89e2d fix: launch env vars to launch as much stuff in wayland as possible 2023-05-01 00:05:00 -04:00
Nova
4d79a59b20 fix(objects/hand): hand enabled when controller not 2023-04-30 18:28:40 -04:00
Nova
c776c1b712 feat: new stereokit 2023-04-30 13:25:13 -04:00
Saphira Kai
d4de15e0b3 remove broken Debug derivation for XdgSurfaceData 2023-04-24 13:12:44 -03:00
Nova
9d220ec235 feat(startup): get environment 2023-04-24 09:53:20 -04:00
Nova
09c6c010e2 feat: cargo lock update 2023-04-24 08:31:28 -04:00
Nova
c9e185e9f3 feat: dependency updates 2023-04-24 08:31:07 -04:00
Nova
4737149c85 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
648451b47e fix: mouse pointer 2023-04-23 09:34:43 -04:00
Nova
a9ef2d6f4b feat: custom startup script 2023-04-23 09:34:43 -04:00
Astavie
d6ffcadd76 fix: nix flake
* nix flake

* workflow

* remove flake-utils

* update flake
2023-04-20 10:35:50 +00:00
Nova
448b7489e8 feat: desktop file 2023-03-25 03:06:50 -04:00
Nova
622cf60a65 feat: upgrade stereokit-rs 2023-03-23 14:12:48 -04:00
Astavie
1ab11f1660 feat: nix support & github workflow
* nix flake

* workflow

* remove flake-utils
2023-03-14 19:35:40 +00:00
Nova
9654e6cc59 fix: unignore cargo.lock 2023-03-13 13:44:12 -07:00
Nova
44d177858f fix(input/hand): correct serialization transform matrix order 2023-03-08 01:45:03 -05:00
Nova
be41f11b83 feat(input): new system 2023-02-25 16:39:30 -05:00
Nova
dd2bffc2b1 refactor(input): make all inputs have nodes 2023-02-24 11:43:06 -05:00
Nova
d2ef508607 feat: update everything, clean dependencies 2023-02-23 08:41:40 -05:00
Nova
0cc7c7bc24 refactor(model): remove shader use 2023-02-23 07:17:36 -05:00
Nova
8d65e304cb fix(model): make default pbr shader clip 2023-02-20 18:03:02 -05:00
Nova
b0dbccbd18 fix(items): proper drop 2023-02-20 10:24:59 -05:00
Nova
a823fbfb57 fix(mouse pointer): keyboard 2023-02-18 02:06:17 -05:00
Nova
4a864e6519 fix(cargo.toml): upgrade stereokit 2023-02-17 13:10:23 -05:00
Nova
e23d847449 fix(mouse pointer): proper pointer transform 2023-02-17 13:10:08 -05:00
Nova
8ba199f053 fix: order of operations on wayland material properties 2023-02-16 14:03:13 -05:00
Nova
23925b4475 fix(item): send acceptors to new item ui 2023-02-16 14:02:43 -05:00
Nova
7ea0220f33 fix: update stereokit 2023-02-16 14:02:21 -05:00
Nova
969e4de882 feat: disabled/enabled 2023-02-16 00:30:25 -05:00
Nova
e5acb3013f fix: node aspect drawable 2023-02-09 03:58:56 -05:00
Nova
3d57bed1c0 refactor: node aspect drawable 2023-02-08 21:06:24 -05:00
Nova
45839ebf60 fix: cargo fmt 2023-02-07 18:04:20 -05:00
Nova
0bb5b53e02 fix(dev profile): optimization level 0 2023-02-07 16:15:37 -05:00
Nova
4f966b6d71 feat(model): pbr clip shader 2023-02-07 16:15:20 -05:00
awtterpip
2687a393b5 fixed bug where sound wasn't stoppable 2023-02-07 10:34:10 -06:00
awtterpip
5c605932ef gave audio interface proper name 2023-02-07 09:13:55 -06:00
piper
bccdc8221e feat: audio! 2023-02-04 20:39:08 -05:00
Nova
bddf17bbef fix(stereokit): version 2023-02-04 20:38:10 -05:00
Nova
e8511e8759 refactor(cargo.toml): profile optimizations 2023-02-02 16:32:43 -05:00
Nova
85296f538b feat(spatial): fields_distance, normal, closest_point 2023-02-01 19:21:35 -05:00
Nova
f8ff80b781 fix(items): make acceptor fields non-optional 2023-01-28 11:12:51 -05:00
Nova
932fef87f5 refactor: change logic_step to frame event 2023-01-26 09:08:40 -05:00
Nova
742780e34e refactor: remove many unwrap calls 2023-01-25 11:50:53 -05:00
Nova
41ede661f7 feat(material): auto copy on change parameter 2023-01-25 09:23:01 -05:00
Nova
8d85460803 feat: make event loop multithreaded 2023-01-25 09:17:52 -05:00
Nova
ac71581db8 fix(spatial): moving object relative to itself 2023-01-25 05:40:37 -05:00
Nova
2f894c4058 fix(spatial): parse_transform returned result 2023-01-25 05:40:19 -05:00
Nova
2b97c98a6e refactor(wayland): remove SeatData wrapper 2023-01-22 02:38:40 -05:00
Nova
98d9f491ba fix(wayland): update pointer scroll 2023-01-22 02:30:12 -05:00
Nova
16d710e106 fix(panel_item): allow surfaces with size of 0,0 2023-01-22 00:58:25 -05:00
Nova
9ad202e778 refactor(spatial): get/set parent methods 2023-01-22 00:42:04 -05:00
Nova
b3747d623c fix(spatial): reference space can be self 2023-01-22 00:24:38 -05:00
Nova
d5ff9281e6 feat: update/clean dependencies 2023-01-22 00:24:13 -05:00
Nova
18ebd8c522 feat: input multiplexing 2023-01-21 18:07:25 -05:00
Nova
411f71c217 feat: startup script 2023-01-17 18:51:46 -05:00
Nova
3027ae20a9 feat(spatial): keep track of children 2023-01-16 11:17:12 -05:00
Nova
fbce321426 refactor(main): make arrays tuples 2023-01-16 11:03:46 -05:00
Nova
74bc3a306e feat: update stereokit to have fancy tracing 2023-01-15 04:23:26 -05:00
Nova
a950ad59f1 fix(input): grab issues 2023-01-15 04:04:09 -05:00
Nova
cf840da444 feat(stereokit): log filtering 2023-01-15 04:03:21 -05:00
Nova
173fba35fa feat: even more tracing 2023-01-15 01:13:22 -05:00
Nova
97fbbec0fe feat: adaptive sleep delay 2023-01-14 23:48:49 -05:00
Nova
400f3a23bf feat: spatial tracing 2023-01-14 22:59:00 -05:00
Nova
1ad3336b6f feat: span tracing!!! 2023-01-14 22:32:41 -05:00
Nova
8e9956abe1 refactor(input): more compact registry contains 2023-01-14 20:29:33 -05:00
Nova
6ca93ea24c fix(event loop, client): better async 2023-01-14 12:38:05 -05:00
Nova
49810e8fd1 refactor(cargo.toml): remove unneeded deps and features 2023-01-14 11:17:52 -05:00
Nova
afd0946558 fix(input): reduce latency by several frames 2023-01-14 11:03:47 -05:00
Nova
fd31d0cd99 feat(tokio): profiling 2023-01-14 10:38:39 -05:00
Nova
2f380da62f refactor(wayland): remove commented out code\ 2023-01-07 10:15:56 -05:00
Nova
1c6971cd11 feat(model): use resource ID for texture 2023-01-06 09:05:30 -05:00
Nova
da4cf084d2 fix: remove stereokit patch 2023-01-05 21:49:03 -05:00
Nova
ca95ed5461 feat(model): set material parameter 2023-01-05 21:46:25 -05:00
Nova
1b06cb6952 fix(drawable/lines): properly make cyclic point 2023-01-05 08:28:29 -05:00
Nova
df89c826bb fix: remove opt level 3 for dev 2023-01-05 07:59:20 -05:00
Nova
21f7f66440 feat: update stereokit 2023-01-04 23:51:48 -05:00
Nova
3f1bad18c8 feat(wayland/surface): geometry resizing, unused 2023-01-04 21:36:55 -05:00
Nova
0c190cc833 feat(delta): mark_changed 2023-01-04 21:36:25 -05:00
Nova
5f0df8e7c1 fix(wayland): SSD all the things 2023-01-04 08:26:10 -05:00
Nova
d715f2f9ed fix(wayland): toplevel states bytemucked to u8 2023-01-04 08:25:54 -05:00
Nova
d7fa4e62b8 feat(wayland): proper surface geometry 2023-01-04 07:25:33 -05:00
Nova
568ebb0060 feat(wayland): serial counter 2023-01-04 07:25:22 -05:00
Nova
42efc67625 feat(delta): const 2023-01-04 07:23:23 -05:00
Nova
dd4b0097a1 feat(wayland): set toplevel capabilities 2023-01-03 10:08:39 -05:00
Nova
a483cdbc7d feat(wayland): recommended_state 2023-01-02 18:53:58 -05:00
Nova
84a7546442 refactor(wayland): remove xdg output manager 2023-01-02 03:49:29 -05:00
Nova
dd43f238ff refactor(wayland): comment out xdg activation protocol 2023-01-02 03:43:50 -05:00
Nova
4f057358c8 fix(wayland): drop panel item correctly 2023-01-01 14:37:11 -05:00
Nova
e20971aef7 feat(wayland): switch pointer focus dynamically 2023-01-01 14:36:46 -05:00
Nova
eb0d3c5bcf fix(wayland): set seat cursor 2023-01-01 14:34:18 -05:00
Nova
a18222e3df fix(wayland): xdg surface size when not set 2023-01-01 14:33:07 -05:00
Nova
93ca932da9 feat(wayland/xdg_shell): set surface states None 2022-12-26 11:15:37 -05:00
Nova
c512b2fef5 feat(wayland): make state fields optional 2022-12-26 08:22:09 -05:00
Nova
f53c684377 fix(wayland): panel item configure toplevel 2022-12-25 16:19:22 -05:00
Nova
3552166207 feat(wayland): configure and commit for toplevel] 2022-12-25 16:01:23 -05:00
Nova
0b6eb147c5 feat(input): allow scaling input handlers 2022-12-21 05:34:34 -05:00
Nova
1833ed50f3 feat: update stardust-xr 2022-12-17 02:29:32 -05:00
Nova
6cdbfb3bad refactor(data): identical values for mask 2022-12-17 02:28:06 -05:00
Nova
a5e0cb19c9 refactor(startup): auto acceptor on item add to 2022-12-10 10:26:26 -05:00
Nova
519ab94312 refactor(startup): generate startup token 2022-12-10 09:46:47 -05:00
Nova
f2a8c0ed13 refactor(startup): rename to STARTUP_SETTINGS 2022-12-10 09:27:37 -05:00
Nova
b3998f315d feat(startup): automatic acceptors 2022-12-10 09:18:02 -05:00
Nova
40bcd61b98 feat(startup settings): use /proc/{pid}/environ 2022-12-10 09:17:37 -05:00
Nova
7a4d557c61 fix(wayland): dmabuf formats 2022-12-10 00:30:53 -05:00
Nova
303b3f3ca2 refactor(wayland): remove manual dmabuf importing 2022-12-09 07:04:50 -05:00
Nova
ac5e949614 fix: drain all dmabufs 2022-12-08 05:40:54 -05:00
Nova
60baabb850 feat: optimization level 3 for debug 2022-12-07 14:47:25 -05:00
Nova
248e48fd8e feat(wayland): dmabuf 2022-12-07 14:30:48 -05:00
Nova
b9baee7e5f feat(resources): list of extensions to check 2022-12-05 22:44:04 -05:00
Nova
3598ffdbb1 refactor(sk_hand): use snake case for datamap keys 2022-12-03 17:18:27 -05:00
Nova
c171d9e6db feat: tracing 2022-12-02 20:46:28 -05:00
Nova
d7a607a663 switch to color_eyre instead of anyhow 2022-12-02 13:58:54 -05:00
Nova
03ccf9127d fix(input): O(n log n) instead of O(n^2) 2022-12-02 11:09:23 -05:00
Nova
6a3024657f fix(items): give aliases a proper lifetime 2022-11-30 22:34:16 -05:00
Nova
a0058fcc2e fix(alias): make output optional 2022-11-30 07:04:06 -05:00
Nova
410cc13c4f fix(lines): use sRGB colors 2022-11-28 00:32:41 -05:00
Nova
bc259dbe01 fix(lines): convert f32 to u8 colors correctly 2022-11-26 15:22:23 -05:00
Nova
3730e20248 fix(lines): accept f32 colors 2022-11-26 00:43:04 -05:00
Nova
1be413065d fix: text not working 2022-11-26 00:34:37 -05:00
Nova
2721c20c8b fix(wayland): update smithay version 2022-11-25 23:58:22 -05:00
Nova
80130f6ffd feat(fields): torus field 2022-11-24 18:57:11 -05:00
Nova
8da778eaba refactor(fields): use let else for getting field 2022-11-24 18:56:59 -05:00
Nova
3c708d1aaf feat(drawable): lines 2022-11-21 16:39:28 -05:00
Nova
1ae1bef3c1 refactor(items): move capture to item acceptor 2022-11-20 13:34:15 -05:00
Nova
7fd0c1fddb feat(items): acceptor 2022-11-19 11:25:10 -05:00
Nova
fd9957b784 fix(wayland): cursor material queue higher 2022-11-14 11:53:08 -05:00
Nova
57da02dbad refactor(stereokit): upgrade version 2022-11-14 09:05:36 -05:00
Nova
83a5b36ddc refactor(wayland): make code cleaner 2022-11-12 11:53:11 -05:00
Nova
8c36d73775 fix: upgrade rust version 2022-11-11 13:25:57 -05:00
Nova
8396b98f67 fix(wayland): pointer_motion works when inactive 2022-11-11 13:25:48 -05:00
Nova
46d989ce7f fix(wayland): remove unwraps 2022-11-11 12:52:51 -05:00
Nova
75ac570486 refactor(wayland): s/ObjectId/Weak<WlSurface>/ 2022-11-09 13:05:21 -05:00
Nova
242def9d06 feat: wayland feature 2022-11-09 11:13:07 -05:00
Nova
959f32009b fix(wayland): account for surface data map panic 2022-11-09 11:02:48 -05:00
Nova
57796c217d fix(panel item): set cursor full of snake 2022-11-08 21:16:03 -05:00
Nova
cea3390e36 refactor(items): genericize item acceptors/ui 2022-11-08 20:25:43 -05:00
Nova
a756e80064 fix: make aliased signals snake case 2022-11-08 20:17:15 -05:00
Nova
1f61d32877 refactor: use snake case for method names 2022-11-08 06:10:03 -05:00
Nova
da7e2c5e6e fix(wayland): upgrade smithay version 2022-11-06 16:49:30 -05:00
Nova
fd0940bfe9 feat(objects/input): add keyboard to mouse_pointer 2022-11-05 17:56:52 -04:00
Nova
2b4a495c07 refactor(data): simplify 2022-11-05 17:56:34 -04:00
Nova
cffb968d2e refactor: remove item alias remote_methods 2022-11-05 17:56:09 -04:00
Nova
201ab3aee8 feat(field): ray_march 2022-11-05 17:55:27 -04:00
Nova
cfa3584dda feat(field): expose ray marching to clients 2022-11-01 07:59:49 -04:00
Nova
f19ba93958 refactor(client): use new messenger 2022-10-30 00:14:24 -04:00
Nova
09a2572c3b refactor(node): return Result<&T> from get aspect 2022-10-29 07:32:51 -04:00
Nova
c6316b4e8b feat: terrible hack for moses 2022-10-26 05:19:20 -04:00
Nova
9cd900b23f fix(wayland): remove wayland crate pinning 2022-10-25 16:21:22 -04:00
Nova
a6d30cb366 refactor: use master smithay branch 2022-10-25 16:07:36 -04:00
Nova
3e94a3f62a fix(wayland): set default output size 2022-10-25 12:56:59 -04:00
Nova
060f8264ff fix(data): send "data" to receiver 2022-10-25 07:32:25 -04:00
Nova
b7b3907647 feat: pulse sender/receiver 2022-10-21 06:21:56 -04:00
Nova
1550555df1 fix: clippy 2022-10-21 06:21:49 -04:00
Nova
c42a29a034 feat: zones 2022-10-20 11:32:33 -04:00
Nova King
621bf6b82a Merge pull request #2 from philpax/fix-build
fix: remove path dependency for stereokit
2022-10-19 05:55:53 +00:00
65 changed files with 9286 additions and 2467 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']

10
.gitignore vendored
View File

@@ -2,11 +2,13 @@
# will have compiled files and executables # will have compiled files and executables
/target/ /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 # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
.vscode/ .vscode/
# Ignore build results from Nix
*result*
/libs/
*.AppImage

2798
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

@@ -19,4 +19,27 @@ cargo build
## Install ## Install
```bash ```bash
cargo install cargo install
``` ```
## 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

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,201 @@
use super::{eventloop::EventLoop, scenegraph::Scenegraph}; use super::scenegraph::Scenegraph;
use crate::{ use crate::{
core::registry::Registry, core::{registry::OwnedRegistry, task},
nodes::{data, drawable, fields, hmd, input, items, root::Root, spatial, startup, Node}, 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 lazy_static::lazy_static;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use stardust_xr::messenger::Messenger; use rustc_hash::FxHashMap;
use std::{ use stardust_xr::messenger::{self, MessageSenderHandle};
path::PathBuf, use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
sync::{Arc, Weak}, use tokio::{net::UnixStream, task::JoinHandle};
}; use tracing::info;
use tokio::{net::UnixStream, sync::Notify, task::JoinHandle};
lazy_static! { 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 { pub static ref INTERNAL_CLIENT: Arc<Client> = CLIENTS.add(Client {
event_loop: Weak::new(), pid: None,
index: 0, // env: None,
exe: None,
stop_notifier: Default::default(), dispatch_join_handle: OnceCell::new(),
join_handle: OnceCell::new(), flush_join_handle: OnceCell::new(),
disconnect_status: OnceCell::new(),
messenger: None, message_sender_handle: None,
scenegraph: Default::default(), scenegraph: Default::default(),
root: OnceCell::new(), root: OnceCell::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
startup_settings: None,
}); });
} }
pub struct Client { pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
event_loop: Weak<EventLoop>, let env = fs::read_to_string(format!("/proc/{pid}/environ"))?;
index: usize, Ok(FxHashMap::from_iter(
stop_notifier: Arc<Notify>, env.split('\0')
join_handle: OnceCell<JoinHandle<Result<()>>>, .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 struct Client {
pub scenegraph: Scenegraph, 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 root: OnceCell<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>, pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub startup_settings: Option<StartupSettings>,
} }
impl Client { impl Client {
pub fn from_connection( pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
index: usize, let pid = connection.peer_cred().ok().and_then(|c| c.pid());
event_loop: &Arc<EventLoop>, let env = pid.and_then(|pid| get_env(pid).ok());
connection: UnixStream, let exe = pid.and_then(|pid| fs::read_link(format!("/proc/{}/exe", pid)).ok());
) -> Arc<Self> { info!(
println!("New client connected"); pid,
let client = CLIENTS.add(Client { exe = exe
event_loop: Arc::downgrade(event_loop), .as_ref()
index, .and_then(|exe| exe.to_str().map(|s| s.to_string())),
stop_notifier: Default::default(), "New client connected"
join_handle: OnceCell::new(), );
messenger: Some(Messenger::new( let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
tokio::runtime::Handle::current(), let scenegraph = Arc::new(Scenegraph::default());
connection, let startup_settings = env.as_ref().and_then(startup_settings);
)),
scenegraph: Default::default(), 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(), root: OnceCell::new(),
base_resource_prefixes: Default::default(), base_resource_prefixes: Default::default(),
startup_settings,
}); });
let _ = client.scenegraph.client.set(Arc::downgrade(&client)); let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client)); let _ = client.root.set(Root::create(&client)?);
hmd::make_alias(&client); hmd::make_alias(&client)?;
spatial::create_interface(&client); spatial::create_interface(&client)?;
fields::create_interface(&client); fields::create_interface(&client)?;
drawable::create_interface(&client); drawable::create_interface(&client)?;
data::create_interface(&client); audio::create_interface(&client)?;
items::create_interface(&client); data::create_interface(&client)?;
input::create_interface(&client); items::create_interface(&client)?;
startup::create_interface(&client); input::create_interface(&client)?;
startup::create_interface(&client)?;
let _ = client.join_handle.set(tokio::spawn({ let pid_printable = pid
let client = client.clone(); .map(|pid| pid.to_string())
async move { .unwrap_or_else(|| "??".to_string());
let dispatch_loop = async { let exe_printable = exe
loop { .and_then(|exe| {
client.dispatch().await? 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! { Ok(client)
_ = client.stop_notifier.notified() => Ok(()),
e = dispatch_loop => e,
e = flush_loop => e,
};
client.disconnect().await;
result
}
}));
client
} }
#[inline] #[inline]
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> { pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
self.scenegraph self.scenegraph
.get_node(path) .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> { pub fn disconnect(&self, reason: Result<()>) {
match &self.messenger { let _ = self.disconnect_status.set(reason);
Some(messenger) => messenger.dispatch(&self.scenegraph).await, if let Some(dispatch_join_handle) = self.dispatch_join_handle.get() {
None => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)), dispatch_join_handle.abort();
} }
} if let Some(flush_join_handle) = self.flush_join_handle.get() {
flush_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);
} }
CLIENTS.remove(self);
} }
} }
impl Drop for Client { impl Drop for Client {
fn drop(&mut self) { fn drop(&mut self) {
self.stop_notifier.notify_one(); info!(
CLIENTS.remove(self); pid = self.pid,
println!("Client disconnected"); 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 super::client::Client;
use anyhow::Result; use super::task;
use slab::Slab; use color_eyre::eyre::Result;
use stardust_xr::server; use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::Arc; use std::sync::Arc;
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tokio::sync::{Mutex, Notify};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tracing::error;
pub static FRAME: AtomicU64 = AtomicU64::new(0);
pub struct EventLoop { pub struct EventLoop {
pub socket_path: String, join_handle: JoinHandle<()>,
stop_notifier: Arc<Notify>,
pub clients: Mutex<Slab<Arc<Client>>>,
} }
impl EventLoop { impl EventLoop {
pub fn new() -> Result<(Arc<Self>, JoinHandle<Result<()>>)> { pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
let socket_path = server::get_free_socket_path() let socket = UnixListener::bind(socket_path)?;
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::Other))?;
let socket = UnixListener::bind(socket_path.clone())?;
let event_loop = Arc::new(EventLoop { let join_handle = task::new(|| "event loop", async move {
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 {
loop { loop {
let (socket, _) = socket.accept().await?; let Ok((socket, _)) = socket.accept().await else { continue };
let mut clients = event_loop.clients.lock().await; if let Err(e) = Client::from_connection(socket) {
let vacant_client = clients.vacant_entry(); error!(?e, "Unable to create client from connection");
let idx = vacant_client.key(); }
vacant_client.insert(Client::from_connection(idx, &event_loop, socket));
} }
}; })?;
let event_loop = Arc::new(EventLoop { join_handle });
tokio::select! { Ok(event_loop)
_ = event_loop.stop_notifier.notified() => Ok(()),
e = event_loop_async => e,
}
} }
} }
impl Drop for EventLoop { impl Drop for EventLoop {
fn drop(&mut self) { fn drop(&mut self) {
self.stop_notifier.notify_one(); self.join_handle.abort();
} }
} }

View File

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

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)] #![allow(dead_code)]
use parking_lot::{const_mutex, MappedMutexGuard, Mutex, MutexGuard};
use rustc_hash::FxHashMap;
use std::ptr; use std::ptr;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use once_cell::sync::Lazy; pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
pub struct Registry<T: Send + Sync + ?Sized>(Lazy<Mutex<FxHashMap<usize, Weak<T>>>>);
impl<T: Send + Sync + ?Sized> Registry<T> { impl<T: Send + Sync + ?Sized> Registry<T> {
pub const fn new() -> Self { 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> pub fn add(&self, t: T) -> Arc<T>
where where
@@ -22,23 +25,81 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
t_arc t_arc
} }
pub fn add_raw(&self, t: &Arc<T>) { pub fn add_raw(&self, t: &Arc<T>) {
self.0 self.lock()
.lock()
.insert(Arc::as_ptr(t) as *const () as usize, Arc::downgrade(t)); .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>> { pub fn get_valid_contents(&self) -> Vec<Arc<T>> {
self.0 self.lock()
.lock()
.iter() .iter()
.filter_map(|pair| pair.1.upgrade()) .filter_map(|pair| pair.1.upgrade())
.collect() .collect()
} }
pub fn remove(&self, t: &T) { pub fn take_valid_contents(&self) -> Vec<Arc<T>> {
self.0 self.0
.lock() .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)); .remove(&(ptr::addr_of!(*t) as *const () as usize));
} }
pub fn clear(&self) { 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 serde::{de::Visitor, Deserialize};
use std::path::PathBuf; use std::{ffi::OsStr, path::PathBuf};
#[derive(Debug)]
pub enum ResourceID { pub enum ResourceID {
File(PathBuf), File(PathBuf),
Namespaced { namespace: String, path: PathBuf }, Namespaced { namespace: String, path: PathBuf },
} }
impl ResourceID { impl ResourceID {
pub fn get_file(&self, prefixes: &[PathBuf]) -> Option<PathBuf> { pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
match self { 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 } => { ResourceID::Namespaced { namespace, path } => {
for prefix in prefixes { let file_name = path.file_name()?;
let mut test_path = prefix.clone(); prefixes
test_path.push(namespace.clone()); .iter()
test_path.push(path.clone()); .filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
if test_path.as_path().exists() { let parent = prefixed_path.parent()?;
return Some(test_path); std::fs::read_dir(parent).ok()
} })
} .flatten()
None .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 { impl<'de> Deserialize<'de> for ResourceID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -56,9 +70,7 @@ impl<'de> Visitor<'de> for ResourceVisitor {
path: PathBuf::from(path), path: PathBuf::from(path),
} }
} else { } else {
return Err(serde::de::Error::custom(anyhow!( return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
"Invalid format for string"
)));
}) })
} }

View File

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

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
}

View File

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

View File

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

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

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

View File

@@ -3,27 +3,28 @@ use super::fields::Field;
use super::spatial::{parse_transform, Spatial}; use super::spatial::{parse_transform, Spatial};
use super::{Alias, Node}; use super::{Alias, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::nodelist::LifeLinkedNodeList; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; 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 crate::nodes::spatial::find_spatial_parent;
use anyhow::{anyhow, ensure, Result}; use color_eyre::eyre::{ensure, eyre, Result};
use glam::vec3a; use glam::vec3a;
use parking_lot::Mutex; use mint::{Quaternion, Vector3};
use serde::Deserialize; use nanoid::nanoid;
use stardust_xr::schemas::flex::{deserialize, serialize}; use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
use stardust_xr::values::Transform; use stardust_xr::values::Transform;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new(); 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<_> { (|| -> Result<_> {
for key in mask_map_lesser.get_mask()?.iter_keys() { for key in mask_map_lesser.get_mask()?.iter_keys() {
let lesser_key_type = mask_map_lesser.get_mask()?.index(key)?.flexbuffer_type(); let lesser_key = mask_map_lesser.get_mask()?.index(key)?;
let greater_key_type = mask_map_greater.get_mask()?.index(key)?.flexbuffer_type(); let greater_key = mask_map_greater.get_mask()?.index(key)?;
if lesser_key_type != greater_key_type { if lesser_key.to_string() != greater_key.to_string() {
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into()); return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
} }
} }
@@ -32,120 +33,140 @@ fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
.is_ok() .is_ok()
} }
type MaskMapGetFn = fn(&[u8]) -> Result<flexbuffers::MapReader<&[u8]>>; pub struct Mask(pub Vec<u8>);
pub struct Mask {
binary: Vec<u8>,
get_fn: MaskMapGetFn,
}
impl Mask { impl Mask {
pub fn get_mask(&self) -> Result<flexbuffers::MapReader<&[u8]>> { 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 { pub struct PulseSender {
mask: Mutex<Mask>, uid: String,
aliases: LifeLinkedNodeList, node: Weak<Node>,
pub mask: Mask,
aliases: LifeLinkedNodeMap<String>,
} }
impl PulseSender { impl PulseSender {
pub fn add_to(node: &Arc<Node>) -> Result<()> { pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> {
ensure!( ensure!(
node.spatial.get().is_some(), node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!" "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 sender = PULSE_SENDER_REGISTRY.add(sender);
let _ = node.pulse_sender.set(sender); let _ = node.pulse_sender.set(sender.clone());
node.add_local_signal("setMask", PulseSender::set_mask_flex); node.add_local_signal("send_data", PulseSender::send_data_flex);
node.add_local_method("getReceivers", PulseSender::get_receivers_flex); for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
Ok(()) sender.handle_new_receiver(&receiver);
}
Ok(sender.clone())
} }
pub fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn handle_new_receiver(&self, receiver: &PulseReceiver) {
ensure!( if !mask_matches(&self.mask, &receiver.mask) {
node.pulse_sender.get().is_some(), return;
"Internal: Node does not have a pulse sender aspect" }
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 if let Ok(rx_alias) = rx_alias {
.get() self.aliases.add(receiver.uid.clone(), &rx_alias);
.unwrap()
.mask if let Some(rx_field_node) = receiver.field.spatial_ref().node.upgrade() {
.lock() // Receiver's field
.set_mask(data.to_vec(), mask_get_map_at_root); let rx_field_alias = Alias::create(
Ok(()) &tx_client,
} rx_alias.get_path(),
fn get_receivers_flex( "field",
node: &Node, &rx_field_node,
calling_client: Arc<Client>, FIELD_ALIAS_INFO.clone(),
_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()
},
); );
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() #[derive(Serialize)]
}) struct NewReceiverInfo<'a> {
.collect(); uid: &'a str,
distance: f32,
position: Vector3<f32>,
rotation: Quaternion<f32>,
}
serialize(uids).map_err(|e| e.into()) let (_, rotation, position) = Spatial::space_to_space_matrix(
rx_node.spatial.get().map(|s| s.as_ref()),
tx_node.spatial.get().map(|s| s.as_ref()),
)
.to_scale_rotation_translation();
let info = NewReceiverInfo {
uid: &receiver.uid,
distance: receiver
.field
.distance(tx_node.spatial.get().unwrap(), vec3a(0.0, 0.0, 0.0)),
position: position.into(),
rotation: rotation.into(),
};
let Ok(data) = serialize(info) else {return};
let _ = tx_node.send_remote_signal("new_receiver", &data);
}
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str();
self.aliases.remove(uid);
self.aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {return};
let Ok(data) = serialize(&uid) else {return};
let _ = tx_node.send_remote_signal("drop_receiver", &data);
}
fn send_data_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let info: SendDataInfo = deserialize(data)?;
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
let receiver =
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
let receiver_mask = &receiver_node
.get_aspect("Pulse receiver", "pulse receiver", |node| {
&node.pulse_receiver
})?
.mask;
let data_mask = Mask(info.data);
data_mask.get_mask()?;
ensure!(
mask_matches(receiver_mask, &data_mask),
"Message does not contain the same keys as the receiver's mask"
);
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
} }
} }
impl Drop for PulseSender { impl Drop for PulseSender {
@@ -156,62 +177,36 @@ impl Drop for PulseSender {
pub struct PulseReceiver { pub struct PulseReceiver {
uid: String, uid: String,
node: Weak<Node>, pub node: Weak<Node>,
pub mask: Mutex<Mask>, pub field: Arc<Field>,
field: Weak<Field>, pub mask: Mask,
} }
impl PulseReceiver { 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<()> {
ensure!( ensure!(
node.spatial.get().is_some(), node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!" "Internal: Node does not have a spatial attached!"
); );
let receiver = PulseReceiver { let receiver = PulseReceiver {
uid: node.uid.clone(), uid: nanoid!(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
field: Arc::downgrade(&field), field,
mask: Default::default(), mask,
}; };
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver); let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
let _ = node.pulse_receiver.set(receiver);
node.add_local_signal("setMask", PulseReceiver::set_mask_flex); for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
node.add_local_signal("sendData", PulseReceiver::send_data_flex); sender.handle_new_receiver(&receiver);
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"
));
} }
drop(receiver_mask); let _ = node.pulse_receiver.set(receiver);
node.send_remote_signal("pulse", data)?;
Ok(()) Ok(())
} }
fn set_mask_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
ensure!( pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> {
node.pulse_receiver.get().is_some(), if let Some(node) = self.node.upgrade() {
"Internal: Node does not have a pulse receiver aspect" node.send_remote_signal("data", &serialize(SendDataInfo { uid, data })?)?;
); }
node.pulse_receiver
.get()
.unwrap()
.mask
.lock()
.set_mask(data.to_vec(), mask_get_map_at_root);
Ok(()) Ok(())
} }
} }
@@ -219,24 +214,19 @@ impl PulseReceiver {
impl Drop for PulseReceiver { impl Drop for PulseReceiver {
fn drop(&mut self) { fn drop(&mut self) {
PULSE_RECEIVER_REGISTRY.remove(self); PULSE_RECEIVER_REGISTRY.remove(self);
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); let node = Node::create(client, "", "data", false);
node.add_local_signal("createPulseSender", create_pulse_sender_flex); node.add_local_signal("create_pulse_sender", create_pulse_sender_flex);
node.add_local_signal("createPulseReceiver", create_pulse_receiver_flex); node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex);
node.add_to_scenegraph(); 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( pub fn create_pulse_sender_flex(
_node: &Node, _node: &Node,
calling_client: Arc<Client>, calling_client: Arc<Client>,
@@ -247,27 +237,22 @@ pub fn create_pulse_sender_flex(
name: &'a str, name: &'a str,
parent_path: &'a str, parent_path: &'a str,
transform: Transform, transform: Transform,
mask: Vec<u8>,
} }
let info: CreatePulseSenderInfo = deserialize(data)?; let info: CreatePulseSenderInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/data/sender", info.name, true); let node = Node::create(&calling_client, "/data/sender", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; 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 mask = Mask(info.mask);
PulseSender::add_to(&node)?; mask.get_mask()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
PulseSender::add_to(&node, mask)?;
Ok(()) 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( pub fn create_pulse_receiver_flex(
_node: &Node, _node: &Node,
calling_client: Arc<Client>, calling_client: Arc<Client>,
@@ -279,15 +264,18 @@ pub fn create_pulse_receiver_flex(
parent_path: &'a str, parent_path: &'a str,
transform: Transform, transform: Transform,
field_path: &'a str, field_path: &'a str,
mask: Vec<u8>,
} }
let info: CreatePulseReceiverInfo = deserialize(data)?; let info: CreatePulseReceiverInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/data/sender", info.name, true); let node = Node::create(&calling_client, "/data/receiver", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; let 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 field = find_field(&calling_client, info.field_path)?;
let mask = Mask(info.mask);
mask.get_mask()?;
let node = node.add_to_scenegraph(); let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform)?; Spatial::add_to(&node, Some(parent), transform, false)?;
PulseReceiver::add_to(&node, field)?; PulseReceiver::add_to(&node, field, mask)?;
Ok(()) Ok(())
} }

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

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

View File

@@ -1,47 +1,53 @@
pub mod lines;
pub mod model; pub mod model;
pub mod text; pub mod text;
use self::{
lines::Lines,
model::{Model, ModelPart},
text::Text,
};
use super::Node; use super::Node;
use crate::core::client::Client; use crate::core::client::Client;
use anyhow::Result; use color_eyre::eyre::Result;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::deserialize;
use std::{path::PathBuf, sync::Arc}; 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); let node = Node::create(client, "", "drawable", false);
node.add_local_signal("createModel", model::create_flex); node.add_local_signal("create_lines", lines::create_flex);
node.add_local_signal("createText", text::create_flex); node.add_local_signal("create_model", model::create_flex);
node.add_local_signal("setSkyFile", set_sky_file_flex); node.add_local_signal("create_text", text::create_flex);
node.add_to_scenegraph(); 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) { pub enum Drawable {
model::draw_all(sk, draw_ctx); Lines(Arc<Lines>),
text::draw_all(sk, draw_ctx); Model(Arc<Model>),
ModelPart(Arc<ModelPart>),
Text(Arc<Text>),
}
let new_skytex = QUEUED_SKYTEX.lock().take(); #[instrument(level = "debug", skip(sk))]
let mut new_skylight = QUEUED_SKYLIGHT.lock().take(); pub fn draw(sk: &impl StereoKitDraw) {
let same_file = new_skytex == new_skylight; lines::draw_all(sk);
model::draw_all(sk);
text::draw_all(sk);
if let Some(skytex) = new_skytex { if let Some(skytex) = QUEUED_SKYTEX.lock().take() {
if let Some((skytex, skylight)) = if let Ok((_skylight, skytex)) = sk.tex_create_cubemap_file(&skytex, true, i32::MAX) {
Texture::from_cubemap_equirectangular(sk, &skytex, true, i32::MAX) sk.render_set_skytex(&skytex);
{
sk.set_skytex(&skytex);
if same_file {
sk.set_skylight(&skylight);
new_skylight = None;
}
} }
} }
if let Some(skylight) = new_skylight { if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() {
if let Some((_, skylight)) = if let Ok((skylight, _)) = sk.tex_create_cubemap_file(&skylight, true, i32::MAX) {
Texture::from_cubemap_equirectangular(sk, &skylight, true, i32::MAX) sk.render_set_skylight(skylight);
{
sk.set_skylight(&skylight);
} }
} }
} }

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
use super::{Field, FieldTrait, Node}; use super::{Field, FieldTrait, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use anyhow::{ensure, Result}; use color_eyre::eyre::{ensure, Result};
use glam::{vec3, vec3a, Vec3, Vec3A}; use glam::{vec3, vec3a, Vec3, Vec3A};
use mint::Vector3; use mint::Vector3;
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -30,7 +30,7 @@ impl BoxField {
size: Mutex::new(size.into()), size: Mutex::new(size.into()),
}; };
box_field.add_field_methods(node); box_field.add_field_methods(node);
node.add_local_signal("setSize", BoxField::set_size_flex); node.add_local_signal("set_size", BoxField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Box(box_field))); let _ = node.field.set(Arc::new(Field::Box(box_field)));
Ok(()) Ok(())
} }
@@ -40,9 +40,9 @@ impl BoxField {
} }
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { 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() { let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
box_field.set_size(deserialize(data)?); box_field.set_size(deserialize(data)?);
}
Ok(()) Ok(())
} }
} }
@@ -74,9 +74,9 @@ pub fn create_box_field_flex(_node: &Node, calling_client: Arc<Client>, data: &[
let info: CreateFieldInfo = deserialize(data)?; let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true); let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; 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(); let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform)?; Spatial::add_to(&node, Some(parent), transform, false)?;
BoxField::add_to(&node, info.size)?; BoxField::add_to(&node, info.size)?;
Ok(()) Ok(())
} }

View File

@@ -1,7 +1,7 @@
use super::{Field, FieldTrait, Node}; use super::{Field, FieldTrait, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use anyhow::{ensure, Result}; use color_eyre::eyre::{ensure, Result};
use glam::{swizzles::*, vec2, Vec3A}; use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32; use portable_atomic::AtomicF32;
use serde::Deserialize; use serde::Deserialize;
@@ -33,7 +33,7 @@ impl CylinderField {
radius: AtomicF32::new(radius.abs()), radius: AtomicF32::new(radius.abs()),
}; };
cylinder_field.add_field_methods(node); 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))); let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field)));
Ok(()) Ok(())
} }
@@ -44,10 +44,9 @@ impl CylinderField {
} }
pub fn set_size_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { 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 Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (length, radius) = deserialize(data)?; let (length, radius) = deserialize(data)?;
cylinder_field.set_size(length, radius); cylinder_field.set_size(length, radius);
}
Ok(()) Ok(())
} }
} }
@@ -81,9 +80,9 @@ pub fn create_cylinder_field_flex(
let info: CreateFieldInfo = deserialize(data)?; let info: CreateFieldInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/field", info.name, true); let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; 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(); let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform)?; Spatial::add_to(&node, Some(parent), transform, false)?;
CylinderField::add_to(&node, dbg!(info.length), dbg!(info.radius))?; CylinderField::add_to(&node, info.length, info.radius)?;
Ok(()) Ok(())
} }

View File

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

View File

@@ -1,7 +1,7 @@
use super::{Field, FieldTrait, Node}; use super::{Field, FieldTrait, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, Spatial}; use crate::nodes::spatial::{find_spatial_parent, Spatial};
use anyhow::{ensure, Result}; use color_eyre::eyre::{ensure, Result};
use glam::{Mat4, Vec3A}; use glam::{Mat4, Vec3A};
use mint::Vector3; use mint::Vector3;
use portable_atomic::AtomicF32; use portable_atomic::AtomicF32;
@@ -30,7 +30,7 @@ impl SphereField {
radius: AtomicF32::new(radius), radius: AtomicF32::new(radius),
}; };
sphere_field.add_field_methods(node); 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))); let _ = node.field.set(Arc::new(Field::Sphere(sphere_field)));
Ok(()) Ok(())
} }
@@ -40,10 +40,8 @@ impl SphereField {
} }
pub fn set_radius_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { 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; let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
if let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() { sphere_field.set_radius(deserialize(data)?);
sphere_field.set_radius(radius);
}
Ok(()) Ok(())
} }
} }
@@ -83,8 +81,8 @@ pub fn create_sphere_field_flex(
.unwrap_or_else(|| Vector3::from([0.0; 3])) .unwrap_or_else(|| Vector3::from([0.0; 3]))
.into(), .into(),
); );
let node = node.add_to_scenegraph(); let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform)?; Spatial::add_to(&node, Some(parent), transform, false)?;
SphereField::add_to(&node, info.radius)?; SphereField::add_to(&node, info.radius)?;
Ok(()) Ok(())
} }

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

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

View File

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

View File

@@ -11,7 +11,10 @@ pub struct Hand {
pub base: FlatHand, pub base: FlatHand,
} }
impl InputSpecialization for Hand { 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; let mut min_distance = f32::MAX;
for tip in [ for tip in [
@@ -60,9 +63,8 @@ impl InputSpecialization for Hand {
]); ]);
for joint in joints { for joint in joints {
let joint_matrix = let joint_matrix = local_to_handler_matrix
Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into()) * Mat4::from_rotation_translation(joint.rotation.into(), joint.position.into());
* local_to_handler_matrix;
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation(); let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into(); joint.position = position.into();
joint.rotation = rotation.into(); joint.rotation = rotation.into();

View File

@@ -6,30 +6,37 @@ use self::hand::Hand;
use self::pointer::Pointer; use self::pointer::Pointer;
use self::tip::Tip; use self::tip::Tip;
use super::fields::Field; use super::{
use super::spatial::{find_spatial_parent, parse_transform, Spatial}; alias::{Alias, AliasInfo},
use super::Node; fields::{find_field, Field, FIELD_ALIAS_INFO},
use crate::core::client::Client; spatial::{find_spatial_parent, parse_transform, Spatial},
use crate::core::eventloop::FRAME; Node,
use crate::core::registry::Registry; };
use crate::nodes::fields::find_field; use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
use anyhow::{ensure, Result}; use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry};
use color_eyre::eyre::{ensure, Result};
use glam::Mat4; use glam::Mat4;
use nanoid::nanoid; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::AtomicBool;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType};
use stardust_xr::schemas::{flat::InputData, flex::deserialize}; use stardust_xr::schemas::{flat::InputData, flex::deserialize};
use stardust_xr::schemas::{
flat::{Datamap, InputDataType},
flex::serialize,
};
use stardust_xr::values::Transform; use stardust_xr::values::Transform;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tracing::{debug_span, instrument};
static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new(); static INPUT_METHOD_REGISTRY: Registry<InputMethod> = Registry::new();
static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new(); static INPUT_HANDLER_REGISTRY: Registry<InputHandler> = Registry::new();
pub trait InputSpecialization: Send + Sync { 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( fn serialize(
&self, &self,
distance_link: &DistanceLink, distance_link: &DistanceLink,
@@ -53,63 +60,133 @@ impl Deref for InputType {
} }
pub struct InputMethod { pub struct InputMethod {
pub uid: String, node: Weak<Node>,
uid: String,
pub enabled: Mutex<bool>, pub enabled: Mutex<bool>,
pub spatial: Arc<Spatial>, pub spatial: Arc<Spatial>,
pub specialization: Mutex<InputType>, pub specialization: Mutex<InputType>,
pub captures: Registry<InputHandler>, captures: Registry<InputHandler>,
pub datamap: Mutex<Option<Datamap>>, pub datamap: Mutex<Option<Datamap>>,
handler_aliases: LifeLinkedNodeMap<String>,
handler_order: OnceCell<Mutex<Vec<Weak<InputHandler>>>>,
} }
impl InputMethod { 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)] #[allow(dead_code)]
pub fn add_to( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
specialization: InputType, specialization: InputType,
datamap: Option<Datamap>, datamap: Option<Datamap>,
) -> Result<()> { ) -> Result<Arc<InputMethod>> {
ensure!( ensure!(
node.spatial.get().is_some(), node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!" "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 { let method = InputMethod {
node: Arc::downgrade(node),
uid: node.uid.clone(), uid: node.uid.clone(),
enabled: Mutex::new(true), enabled: Mutex::new(true),
spatial: node.spatial.get().unwrap().clone(), spatial: node.spatial.get().unwrap().clone(),
specialization: Mutex::new(specialization), specialization: Mutex::new(specialization),
captures: Registry::new(), captures: Registry::new(),
datamap: Mutex::new(datamap), 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 method = INPUT_METHOD_REGISTRY.add(method);
let _ = node.input_method.set(method); let _ = node.input_method.set(method.clone());
Ok(method)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Method", "input method", |n| &n.input_method)
.cloned()
}
fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let method = InputMethod::get(node)?;
let handler = InputHandler::find(&calling_client, deserialize(data)?)?;
method.captures.add_raw(&handler);
node.send_remote_signal("capture", data)
}
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let method = InputMethod::get(node)?;
method.datamap.lock().replace(Datamap::new(data.to_vec())?);
Ok(())
}
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let method = InputMethod::get(node)?;
let handler_paths: Vec<&str> = deserialize(data)?;
let handlers: Vec<Weak<InputHandler>> = handler_paths
.into_iter()
.filter_map(|p| InputHandler::find(&calling_client, p).ok())
.map(|h| Arc::downgrade(&h))
.collect();
*method
.handler_order
.get_or_init(|| Mutex::new(Vec::new()))
.lock() = handlers;
Ok(()) Ok(())
} }
fn set_datamap(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn compare_distance(&self, to: &Field) -> f32 {
node.input_method self.specialization
.get()
.unwrap()
.datamap
.lock() .lock()
.replace(Datamap::new(data.to_vec())?); .compare_distance(&self.spatial, to)
Ok(()) }
fn true_distance(&self, to: &Field) -> f32 {
self.specialization.lock().true_distance(&self.spatial, to)
} }
fn distance(&self, to: &Field) -> f32 { fn handle_new_handler(&self, handler: &InputHandler) {
self.specialization.lock().distance(&self.spatial, to) 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 { impl Drop for InputMethod {
@@ -119,26 +196,37 @@ impl Drop for InputMethod {
} }
pub struct DistanceLink { pub struct DistanceLink {
pub distance: f32, distance: f32,
pub method: Arc<InputMethod>, method: Arc<InputMethod>,
pub handler: Arc<InputHandler>, handler: Arc<InputHandler>,
pub handler_field: Arc<Field>,
} }
impl DistanceLink { impl DistanceLink {
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> { 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 { Some(DistanceLink {
distance: method.distance(&handler_field), distance: method.compare_distance(&handler.field),
method, method,
handler, handler,
handler_field,
}) })
} }
fn send_input(&self, frame: u64, datamap: Datamap) { fn send_input(&self, order: u32, datamap: Datamap) {
self.handler.send_input(frame, self, 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( let input = self.method.specialization.lock().serialize(
self, self,
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)), Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
@@ -147,17 +235,21 @@ impl DistanceLink {
let root = InputData { let root = InputData {
uid: self.method.uid.clone(), uid: self.method.uid.clone(),
input, input,
distance: self.distance, distance: self.method.true_distance(&self.handler.field),
datamap, datamap,
order,
}; };
root.serialize() root.serialize()
} }
} }
pub struct InputHandler { pub struct InputHandler {
enabled: Arc<AtomicBool>,
uid: String,
node: Weak<Node>, node: Weak<Node>,
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
pub field: Weak<Field>, field: Arc<Field>,
method_aliases: LifeLinkedNodeList,
} }
impl InputHandler { impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> { pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
@@ -167,52 +259,54 @@ impl InputHandler {
); );
let handler = InputHandler { let handler = InputHandler {
enabled: node.enabled.clone(),
uid: node.uid.clone(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
spatial: node.spatial.get().unwrap().clone(), 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 handler = INPUT_HANDLER_REGISTRY.add(handler);
let _ = node.input_handler.set(handler); let _ = node.input_handler.set(handler);
Ok(()) 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) { #[instrument(level = "debug", skip(self, distance_link))]
let data = distance_link.serialize(datamap); fn send_input(&self, order: u32, distance_link: &DistanceLink, datamap: Datamap) {
let node = self.node.upgrade().unwrap(); let Some(node) = self.node.upgrade() else {return};
let method = Arc::downgrade(&distance_link.method); let _ = node.send_remote_signal("input", &distance_link.serialize(order, datamap));
let handler = Arc::downgrade(&distance_link.handler); }
}
tokio::spawn(async move { impl PartialEq for InputHandler {
let data = node.execute_remote_method("input", data).await; fn eq(&self, other: &Self) -> bool {
if frame == FRAME.load(Ordering::Relaxed) { self.spatial == other.spatial
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);
}
}
}
}
}
});
} }
} }
impl Drop for InputHandler { impl Drop for InputHandler {
fn drop(&mut self) { fn drop(&mut self) {
INPUT_HANDLER_REGISTRY.remove(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); let node = Node::create(client, "", "input", false);
node.add_local_signal("createInputHandler", create_input_handler_flex); node.add_local_signal("create_input_handler", create_input_handler_flex);
node.add_local_signal("createInputMethodTip", tip::create_tip_flex); node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
node.add_to_scenegraph(); node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
node.add_to_scenegraph().map(|_| ())
} }
pub fn create_input_handler_flex( pub fn create_input_handler_flex(
@@ -229,50 +323,73 @@ pub fn create_input_handler_flex(
} }
let info: CreateInputHandlerInfo = deserialize(data)?; let info: CreateInputHandlerInfo = deserialize(data)?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?; 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 field = find_field(&calling_client, info.field_path)?;
let node = Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph(); let node =
Spatial::add_to(&node, Some(parent), transform)?; 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)?; InputHandler::add_to(&node, &field)?;
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug")]
pub fn process_input() { pub fn process_input() {
for method in INPUT_METHOD_REGISTRY // Iterate over all valid input methods
.get_valid_contents() let methods = debug_span!("Get valid methods").in_scope(|| {
.into_iter() INPUT_METHOD_REGISTRY
.filter(|method| *method.enabled.lock())
.filter(|method| method.datamap.lock().is_some())
{
let mut distance_links: Vec<DistanceLink> = INPUT_HANDLER_REGISTRY
.get_valid_contents() .get_valid_contents()
.into_iter() .into_iter()
.filter(|handler| handler.field.upgrade().is_some()) .filter(|method| *method.enabled.lock())
.filter_map(|handler| DistanceLink::from(method.clone(), handler)) .filter(|method| method.datamap.lock().is_some())
.collect(); });
distance_links.sort_unstable_by(|a, b| { let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
a.distance for handler in &handlers {
.abs() handler.method_aliases.clear();
.partial_cmp(&b.distance.abs()) }
.unwrap() for method in methods {
.reverse() 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; // Sort the distance links by their distance in ascending order
let frame = FRAME.load(Ordering::Relaxed); debug_span!("Sort distance links").in_scope(|| {
let captures = method.captures.get_valid_contents(); distance_links.sort_unstable_by(|a, b| {
for distance_link in distance_links { a.distance.abs().partial_cmp(&b.distance.abs()).unwrap()
distance_link.send_input(frame, method.datamap.lock().clone().unwrap()); });
if last_distance != distance_link.distance });
&& captures
.iter() distance_links
.any(|c| Arc::ptr_eq(c, &distance_link.handler)) }
{ });
break;
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 super::{DistanceLink, InputSpecialization};
use crate::nodes::fields::{ray_march, Field, Ray, RayMarchResult}; use crate::core::client::Client;
use crate::nodes::spatial::Spatial; use crate::nodes::fields::{Field, Ray, RayMarchResult};
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Node;
use glam::{vec3, Mat4}; use 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; use std::sync::Arc;
#[derive(Default)] #[derive(Default)]
pub struct Pointer {} pub struct Pointer;
// impl Default for Pointer { // impl Default for Pointer {
// fn default() -> Self { // fn default() -> Self {
// Pointer { // Pointer {
@@ -17,20 +23,24 @@ pub struct Pointer {}
// } // }
impl Pointer { impl Pointer {
fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult { fn ray_march(&self, space: &Arc<Spatial>, field: &Field) -> RayMarchResult {
ray_march( field.ray_march(Ray {
Ray { origin: vec3(0.0, 0.0, 0.0),
origin: vec3(0_f32, 0_f32, 0_f32), direction: vec3(0.0, 0.0, -1.0),
direction: vec3(0_f32, 0_f32, 1_f32), space: space.clone(),
space: space.clone(), })
},
field,
)
} }
} }
impl InputSpecialization for Pointer { impl InputSpecialization for Pointer {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 { fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
self.ray_march(space, field).distance 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( fn serialize(
&self, &self,
@@ -38,11 +48,8 @@ impl InputSpecialization for Pointer {
local_to_handler_matrix: Mat4, local_to_handler_matrix: Mat4,
) -> InputDataType { ) -> InputDataType {
let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation(); let (_, orientation, origin) = local_to_handler_matrix.to_scale_rotation_translation();
let direction = local_to_handler_matrix.transform_vector3(vec3(0_f32, 0_f32, 1_f32)); let direction = local_to_handler_matrix.transform_vector3(vec3(0.0, 0.0, -1.0));
let ray_march = self.ray_march( let ray_march = self.ray_march(&distance_link.method.spatial, &distance_link.handler.field);
&distance_link.method.spatial,
&distance_link.handler.field.upgrade().unwrap(),
);
let deepest_point = (direction * ray_march.deepest_point_distance) + origin; let deepest_point = (direction * ray_march.deepest_point_distance) + origin;
InputDataType::Pointer(FlatPointer { InputDataType::Pointer(FlatPointer {
@@ -52,3 +59,30 @@ impl InputSpecialization for Pointer {
}) })
} }
} }
pub fn create_pointer_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> color_eyre::eyre::Result<()> {
#[derive(Deserialize)]
struct CreatePointerInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
datamap: Option<Vec<u8>>,
}
let info: CreatePointerInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to(
&node,
InputType::Pointer(Pointer),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
)?;
Ok(())
}

View File

@@ -4,7 +4,7 @@ use crate::nodes::fields::Field;
use crate::nodes::input::{InputMethod, InputType}; use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial}; use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Node; use crate::nodes::Node;
use anyhow::Result; use color_eyre::eyre::Result;
use glam::{vec3a, Mat4}; use glam::{vec3a, Mat4};
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip}; use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip};
@@ -25,7 +25,10 @@ impl Tip {
} }
} }
impl InputSpecialization for Tip { impl InputSpecialization for Tip {
fn distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 { fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0)).abs()
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
field.distance(space, vec3a(0.0, 0.0, 0.0)) field.distance(space, vec3a(0.0, 0.0, 0.0))
} }
fn serialize( fn serialize(
@@ -54,10 +57,10 @@ pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -
let info: CreateTipInfo = deserialize(data)?; let info: CreateTipInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/input/method/tip", info.name, true); let node = Node::create(&calling_client, "/input/method/tip", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?; 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(); let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform)?; Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to( InputMethod::add_to(
&node, &node,
InputType::Tip(Tip { 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()), 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(()) Ok(())
} }

View File

@@ -1,19 +1,37 @@
use super::{Item, ItemSpecialization, ItemType, ITEM_TYPE_INFO_ENVIRONMENT}; use super::{Item, ItemSpecialization, ItemType};
use crate::{ use crate::{
core::client::{Client, INTERNAL_CLIENT}, core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{ nodes::{
items::TypeInfo,
spatial::{find_spatial_parent, parse_transform, Spatial}, spatial::{find_spatial_parent, parse_transform, Spatial},
Node, Node,
}, },
}; };
use anyhow::{anyhow, Result}; use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::{ use stardust_xr::{
schemas::flex::{deserialize, serialize}, schemas::flex::{deserialize, flexbuffers, serialize},
values::Transform, values::Transform,
}; };
use std::sync::Arc; 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 { pub struct EnvironmentItem {
path: String, path: String,
} }
@@ -21,18 +39,18 @@ impl EnvironmentItem {
pub fn add_to(node: &Arc<Node>, path: String) { pub fn add_to(node: &Arc<Node>, path: String) {
Item::add_to( Item::add_to(
node, node,
nanoid!(),
&ITEM_TYPE_INFO_ENVIRONMENT, &ITEM_TYPE_INFO_ENVIRONMENT,
ItemType::Environment(EnvironmentItem { path }), 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>> { 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 { let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
ItemType::Environment(env) => Ok(env.path.clone()), return Err(eyre!("Wrong item type?"))
_ => Err(anyhow!("")),
}; };
Ok(flexbuffers::singleton(path?.as_str())) Ok(flexbuffers::singleton(environment_item.path.as_str()))
} }
} }
impl ItemSpecialization for EnvironmentItem { impl ItemSpecialization for EnvironmentItem {
@@ -54,32 +72,17 @@ pub(super) fn create_environment_item_flex(
item_data: String, item_data: String,
} }
let info: CreateEnvironmentItemInfo = deserialize(data)?; let info: CreateEnvironmentItemInfo = deserialize(data)?;
let parent_name = format!("/item/{}/item/", ITEM_TYPE_INFO_ENVIRONMENT.type_name); let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let node = Node::create(&INTERNAL_CLIENT, &parent_name, info.name, true);
let space = find_spatial_parent(&calling_client, info.parent_path)?; 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 node = node.add_to_scenegraph();
Spatial::add_to(&node, None, transform * space.global_transform())?; 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); EnvironmentItem::add_to(&node, info.item_data);
node.item node.item
.get() .get()
.unwrap() .unwrap()
.make_alias(&calling_client, &parent_name); .make_alias_named(&calling_client, &parent_name, info.name)?;
Ok(()) 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,59 @@
mod environment; mod environment;
use self::environment::EnvironmentItem; use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
use super::fields::Field; use super::fields::Field;
use super::spatial::{find_spatial_parent, parse_transform, Spatial}; use super::spatial::{find_spatial_parent, parse_transform, Spatial};
use super::{Alias, Node}; use super::{Alias, Node};
use crate::core::client::{Client, INTERNAL_CLIENT}; use crate::core::client::Client;
use crate::core::nodelist::LifeLinkedNodeList; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo; use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field; use crate::nodes::fields::find_field;
use crate::wayland::panel_item::{register_panel_item_ui_flex, PanelItem}; #[cfg(feature = "wayland")]
use anyhow::{anyhow, ensure, Result}; use crate::wayland::panel_item::{PanelItem, ITEM_TYPE_INFO_PANEL};
use color_eyre::eyre::{ensure, eyre, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use nanoid::nanoid; use nanoid::nanoid;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::Ordering;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
use stardust_xr::values::Transform; use stardust_xr::values::Transform;
use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
lazy_static! { lazy_static! {
static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![ static ref ITEM_ALIAS_LOCAL_SIGNALS: Vec<&'static str> = vec![
"getTransform", "get_bounds",
"setTransform", "get_transform",
"setSpatialParent", "set_transform",
"setSpatialParentInPlace", "set_spatial_parent",
"setZoneable", "set_spatial_parent_in_place",
"set_zoneable",
"release", "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_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>) { pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if item.captured_acceptor.lock().upgrade().is_some() { if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
release(item); release(item, Some(&acceptor));
} }
*item.captured_acceptor.lock() = Arc::downgrade(acceptor); *item.captured_acceptor.lock() = Arc::downgrade(acceptor);
acceptor.handle_capture(item); acceptor.handle_capture(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() { 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>) { fn release(item: &Item, acceptor: Option<&ItemAcceptor>) {
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() { let mut captured_acceptor = item.captured_acceptor.lock();
*item.captured_acceptor.lock() = Weak::default(); if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) {
*captured_acceptor = Weak::default();
acceptor.handle_release(item); acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() { if let Some(ui) = item.type_info.ui.lock().upgrade() {
ui.handle_release(item); ui.handle_release_item(item, &acceptor);
} }
} }
} }
@@ -70,27 +63,21 @@ pub struct TypeInfo {
pub aliased_local_signals: Vec<&'static str>, pub aliased_local_signals: Vec<&'static str>,
pub aliased_local_methods: Vec<&'static str>, pub aliased_local_methods: Vec<&'static str>,
pub aliased_remote_signals: Vec<&'static str>, pub aliased_remote_signals: Vec<&'static str>,
pub aliased_remote_methods: Vec<&'static str>,
pub ui: Mutex<Weak<ItemUI>>, pub ui: Mutex<Weak<ItemUI>>,
pub items: Registry<Item>, pub items: Registry<Item>,
pub acceptors: Registry<ItemAcceptor>, pub acceptors: Registry<ItemAcceptor>,
} }
impl Hash for TypeInfo {
fn capture_into_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let acceptor_path = flexbuffers::Reader::get_root(data)? self.type_name.hash(state);
.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 PartialEq for TypeInfo {
fn eq(&self, other: &Self) -> bool {
self.type_name == other.type_name
}
}
impl Eq for TypeInfo {}
pub struct Item { pub struct Item {
node: Weak<Node>, node: Weak<Node>,
@@ -102,55 +89,82 @@ pub struct Item {
impl Item { impl Item {
pub fn add_to( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
uid: String,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
specialization: ItemType, specialization: ItemType,
) -> Arc<Self> { ) -> Arc<Self> {
node.add_local_signal("captureInto", capture_into_flex);
let item = Item { let item = Item {
node: Arc::downgrade(node), node: Arc::downgrade(node),
uid: nanoid!(), uid,
type_info, type_info,
captured_acceptor: Default::default(), captured_acceptor: Default::default(),
specialization, specialization,
}; };
let item = type_info.items.add(item); let item = type_info.items.add(item);
node.add_local_signal("release", Item::release_flex);
if let Some(ui) = type_info.ui.lock().upgrade() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item); ui.handle_create_item(&item);
} }
let _ = node.item.set(item.clone()); 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 item
} }
fn make_alias(&self, client: &Arc<Client>, parent: &str) -> (Arc<Node>, Arc<Alias>) { fn make_alias_named(
let node = Alias::new( &self,
client: &Arc<Client>,
parent: &str,
name: &str,
) -> Result<Arc<Node>> {
Alias::create(
client, client,
parent, parent,
&self.uid, name,
&self.node.upgrade().unwrap(), &self.node.upgrade().unwrap(),
AliasInfo { AliasInfo {
local_signals: [ server_signals: [
&self.type_info.aliased_local_signals, &self.type_info.aliased_local_signals,
ITEM_ALIAS_LOCAL_SIGNALS.as_slice(), ITEM_ALIAS_LOCAL_SIGNALS.as_slice(),
] ]
.concat(), .concat(),
local_methods: [ server_methods: [
&self.type_info.aliased_local_methods, &self.type_info.aliased_local_methods,
ITEM_ALIAS_LOCAL_METHODS.as_slice(), ITEM_ALIAS_LOCAL_METHODS.as_slice(),
] ]
.concat(), .concat(),
remote_signals: [ client_signals: [
&self.type_info.aliased_remote_signals, &self.type_info.aliased_remote_signals,
ITEM_ALIAS_REMOTE_SIGNALS.as_slice(), ITEM_ALIAS_REMOTE_SIGNALS.as_slice(),
] ]
.concat(), .concat(),
}, },
); )
let alias = node.alias.get().unwrap().clone(); }
(node, alias) fn make_alias(&self, client: &Arc<Client>, parent: &str) -> Result<Arc<Node>> {
self.make_alias_named(client, parent, &self.uid)
}
fn release_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
let item = node.get_aspect("Item", "item", |n| &n.item)?;
release(item, None);
Ok(())
} }
} }
impl Drop for Item { impl Drop for Item {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.items.remove(self); self.type_info.items.remove(self);
release(self, None);
if let Some(ui) = self.type_info.ui.lock().upgrade() { if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_item(self); ui.handle_destroy_item(self);
} }
@@ -163,7 +177,8 @@ pub trait ItemSpecialization {
pub enum ItemType { pub enum ItemType {
Environment(EnvironmentItem), Environment(EnvironmentItem),
Panel(PanelItem), #[cfg(feature = "wayland")]
Panel(Arc<PanelItem>),
} }
impl Deref for ItemType { impl Deref for ItemType {
type Target = dyn ItemSpecialization; type Target = dyn ItemSpecialization;
@@ -171,7 +186,8 @@ impl Deref for ItemType {
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match self { match self {
ItemType::Environment(item) => item, ItemType::Environment(item) => item,
ItemType::Panel(item) => item, #[cfg(feature = "wayland")]
ItemType::Panel(item) => item.as_ref(),
} }
} }
} }
@@ -179,7 +195,9 @@ impl Deref for ItemType {
pub struct ItemUI { pub struct ItemUI {
node: Weak<Node>, node: Weak<Node>,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
aliases: LifeLinkedNodeList, item_aliases: LifeLinkedNodeMap<String>,
acceptor_aliases: LifeLinkedNodeMap<String>,
acceptor_field_aliases: LifeLinkedNodeMap<String>,
} }
impl ItemUI { impl ItemUI {
fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> { fn add_to(node: &Arc<Node>, type_info: &'static TypeInfo) -> Result<()> {
@@ -191,7 +209,9 @@ impl ItemUI {
let ui = Arc::new(ItemUI { let ui = Arc::new(ItemUI {
node: Arc::downgrade(node), node: Arc::downgrade(node),
type_info, 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); *type_info.ui.lock() = Arc::downgrade(&ui);
let _ = node.item_ui.set(ui.clone()); let _ = node.item_ui.set(ui.clone());
@@ -199,6 +219,9 @@ impl ItemUI {
for item in type_info.items.get_valid_contents() { for item in type_info.items.get_valid_contents() {
ui.handle_create_item(&item); ui.handle_create_item(&item);
} }
for acceptor in type_info.acceptors.get_valid_contents() {
ui.handle_create_acceptor(&acceptor);
}
Ok(()) Ok(())
} }
fn send_state(&self, state: &str, name: &str) { fn send_state(&self, state: &str, name: &str) {
@@ -210,40 +233,56 @@ impl ItemUI {
} }
fn handle_create_item(&self, item: &Item) { fn handle_create_item(&self, item: &Item) {
let node = self.node.upgrade().unwrap(); let Some(node) = self.node.upgrade() else { return };
if let Some(client) = node.get_client() { let Some(client) = node.get_client() else { return };
let (alias_node, _) =
item.make_alias(&client, &(node.get_path().to_string() + "/item"));
self.aliases.add(Arc::downgrade(&alias_node));
let _ = node.send_remote_signal( if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
"create", self.item_aliases.add(item.uid.clone(), &alias_node);
&item.specialization.serialize_start_data(&item.uid),
);
} }
let _ = node.send_remote_signal(
"create_item",
&item.specialization.serialize_start_data(&item.uid),
);
} }
fn handle_destroy_item(&self, item: &Item) { 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) { fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
self.send_state("capture", item.uid.as_str()); let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal(
"capture_item",
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
);
} }
fn handle_release(&self, item: &Item) { fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
self.send_state("release", item.uid.as_str()); let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal(
"release_item",
&serialize((item.uid.as_str(), acceptor.uid.as_str())).unwrap(),
);
} }
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) { fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let node = self.node.upgrade().unwrap(); let Some(node) = self.node.upgrade() else { return };
if let Some(client) = node.get_client() { let Some(client) = node.get_client() else { return };
let aliases = acceptor.make_aliases(
&client, let Ok((alias, field_alias)) = acceptor.make_aliases(
&format!("/item/{}/acceptor", self.type_info.type_name), &client,
); &format!("/item/{}/acceptor", self.type_info.type_name),
aliases ) else {return};
.iter() self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
.for_each(|alias| self.aliases.add(Arc::downgrade(alias))); self.acceptor_field_aliases
} .add(acceptor.uid.clone(), &field_alias);
let _ = node.send_remote_signal("create_acceptor", &serialize(&acceptor.uid).unwrap());
}
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
self.send_state("destroy_acceptor", acceptor.uid.as_str());
self.acceptor_aliases.remove(&acceptor.uid);
self.acceptor_field_aliases.remove(&acceptor.uid);
} }
fn handle_destroy_acceptor(&self, _acceptor: &ItemAcceptor) {}
} }
impl Drop for ItemUI { impl Drop for ItemUI {
fn drop(&mut self) { fn drop(&mut self) {
@@ -252,127 +291,156 @@ impl Drop for ItemUI {
} }
pub struct ItemAcceptor { pub struct ItemAcceptor {
uid: String,
node: Weak<Node>, node: Weak<Node>,
type_info: &'static TypeInfo, pub type_info: &'static TypeInfo,
field: Mutex<Weak<Field>>, field: Arc<Field>,
accepted: Registry<Item>, accepted_aliases: LifeLinkedNodeMap<String>,
accepted_registry: Registry<Item>,
} }
impl ItemAcceptor { 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 { let acceptor = type_info.acceptors.add(ItemAcceptor {
uid: nanoid!(),
node: Arc::downgrade(node), node: Arc::downgrade(node),
type_info, type_info,
field: Mutex::new(field), field,
accepted: Registry::new(), 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() { if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor); ui.handle_create_acceptor(&acceptor);
} }
let _ = node.item_acceptor.set(acceptor); let _ = node.item_acceptor.set(acceptor);
} }
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Vec<Arc<Node>> {
let mut aliases = Vec::new(); fn capture_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if !node.enabled.load(Ordering::Relaxed) {
return Ok(());
}
let acceptor = node.item_acceptor.get().unwrap();
let item_path: &str = deserialize(data)?;
let item_node = calling_client.get_node("Item", item_path)?;
let item = item_node.get_aspect("Item", "item", |n| &n.item)?;
capture(item, acceptor);
Ok(())
}
fn make_aliases(&self, client: &Arc<Client>, parent: &str) -> Result<(Arc<Node>, Arc<Node>)> {
let acceptor_node = &self.node.upgrade().unwrap(); let acceptor_node = &self.node.upgrade().unwrap();
let acceptor_alias = Alias::new( let acceptor_alias = Alias::create(
client, client,
parent, parent,
acceptor_node.uid.as_str(), &self.uid,
acceptor_node, acceptor_node,
AliasInfo { AliasInfo {
local_signals: vec!["release"], server_signals: vec!["capture"],
..Default::default() ..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); let acceptor_field_alias = Alias::create(
} client,
aliases.push(acceptor_alias); acceptor_alias.get_path(),
aliases "field",
} &self.field.spatial_ref().node.upgrade().unwrap(),
fn send_event(&self, state: &str, name: &str) { AliasInfo::default(),
let _ = self )?;
.node
.upgrade() Ok((acceptor_alias, acceptor_field_alias))
.unwrap()
.send_remote_signal(state, flexbuffers::singleton(name).as_slice());
} }
fn handle_capture(&self, item: &Arc<Item>) { fn handle_capture(&self, item: &Arc<Item>) {
self.accepted.add_raw(item); let Some(node) = self.node.upgrade() else { return };
self.send_event("capture", item.uid.as_str()); let Some(client) = node.get_client() else { return };
self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
self.accepted_aliases.add(item.uid.clone(), &alias_node);
}
let _ = node.send_remote_signal(
"capture",
&item.specialization.serialize_start_data(&item.uid),
);
} }
fn handle_release(&self, item: &Item) { fn handle_release(&self, item: &Item) {
self.accepted.remove(item); let Some(node) = self.node.upgrade() else { return };
self.send_event("release", item.uid.as_str());
self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid);
let _ = node.send_remote_signal("release", &serialize(&item.uid).unwrap());
} }
} }
impl Drop for ItemAcceptor { impl Drop for ItemAcceptor {
fn drop(&mut self) { fn drop(&mut self) {
self.type_info.acceptors.remove(self); self.type_info.acceptors.remove(self);
for item in self.accepted_registry.get_valid_contents() {
release(&item, Some(self));
}
if let Some(ui) = self.type_info.ui.lock().upgrade() { if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_acceptor(self); ui.handle_destroy_acceptor(self);
} }
} }
} }
pub fn create_interface(client: &Arc<Client>) { pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "item", false); let node = Node::create(client, "", "item", false);
node.add_local_signal( node.add_local_signal(
"createEnvironmentItem", "create_environment_item",
environment::create_environment_item_flex, environment::create_environment_item_flex,
); );
node.add_local_signal( node.add_local_signal("register_item_ui", register_item_ui_flex);
"registerEnvironmentItemUI", node.add_local_signal("create_item_acceptor", create_item_acceptor_flex);
environment::register_environment_item_ui_flex, node.add_to_scenegraph().map(|_| ())
);
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();
} }
pub(self) fn create_item_acceptor_flex( fn type_info(name: &str) -> Result<&'static TypeInfo> {
calling_client: Arc<Client>, match name {
data: &[u8], "environment" => Ok(&ITEM_TYPE_INFO_ENVIRONMENT),
type_info: &'static TypeInfo, #[cfg(feature = "wayland")]
) -> Result<()> { "panel" => Ok(&ITEM_TYPE_INFO_PANEL),
_ => Err(eyre!("Invalid item type")),
}
}
pub fn register_item_ui_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)]
struct RegisterItemUIInfo<'a> {
item_type: &'a str,
}
let info: RegisterItemUIInfo = deserialize(data)?;
let type_info = type_info(info.item_type)?;
let ui =
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?;
Ok(())
}
fn create_item_acceptor_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)] #[derive(Deserialize)]
struct CreateItemAcceptorInfo<'a> { struct CreateItemAcceptorInfo<'a> {
name: &'a str, name: &'a str,
parent_path: &'a str, parent_path: &'a str,
transform: Transform, transform: Transform,
field_path: &'a str, field_path: &'a str,
item_type: &'a str,
} }
let info: CreateItemAcceptorInfo = deserialize(data)?; let info: CreateItemAcceptorInfo = deserialize(data)?;
let parent_name = format!("/item/{}/acceptor/", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let space = find_spatial_parent(&calling_client, info.parent_path)?; let 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 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(); let node = Node::create(
Spatial::add_to(&node, Some(space), transform)?; &calling_client,
ItemAcceptor::add_to(&node, type_info, Arc::downgrade(&field)); &format!("/item/{}/acceptor", type_info.type_name),
node.item info.name,
.get() true,
.unwrap() )
.make_alias(&calling_client, &parent_name); .add_to_scenegraph()?;
Ok(()) Spatial::add_to(&node, Some(space), transform, false)?;
} ItemAcceptor::add_to(&node, type_info, field);
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)?;
Ok(()) Ok(())
} }

View File

@@ -1,4 +1,5 @@
pub mod alias; pub mod alias;
pub mod audio;
pub mod data; pub mod data;
pub mod drawable; pub mod drawable;
pub mod fields; pub mod fields;
@@ -9,30 +10,33 @@ pub mod root;
pub mod spatial; pub mod spatial;
pub mod startup; pub mod startup;
use anyhow::{anyhow, Result}; use color_eyre::eyre::{eyre, Result};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use nanoid::nanoid; use nanoid::nanoid;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHasher;
use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError; use stardust_xr::scenegraph::ScenegraphError;
use std::sync::atomic::{AtomicBool, Ordering}; use stardust_xr::schemas::flex::deserialize;
use std::fmt::Debug;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::vec::Vec; use std::vec::Vec;
use tracing::{debug_span, instrument};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use rustc_hash::FxHasher;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use self::alias::Alias; use self::alias::Alias;
use self::audio::Sound;
use self::data::{PulseReceiver, PulseSender}; use self::data::{PulseReceiver, PulseSender};
use self::drawable::Drawable;
use self::drawable::model::Model;
use self::drawable::text::Text;
use self::fields::Field; use self::fields::Field;
use self::input::{InputHandler, InputMethod}; use self::input::{InputHandler, InputMethod};
use self::items::{Item, ItemAcceptor, ItemUI}; use self::items::{Item, ItemAcceptor, ItemUI};
use self::spatial::zone::Zone;
use self::spatial::Spatial; use self::spatial::Spatial;
use self::startup::StartupSettings; use self::startup::StartupSettings;
@@ -40,26 +44,29 @@ pub type Signal = fn(&Node, Arc<Client>, &[u8]) -> Result<()>;
pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>; pub type Method = fn(&Node, Arc<Client>, &[u8]) -> Result<Vec<u8>>;
pub struct Node { pub struct Node {
pub enabled: Arc<AtomicBool>,
pub(super) uid: String, pub(super) uid: String,
path: String, path: String,
client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize, // trailing_slash_pos: usize,
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>, local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>,
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>, local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>,
destroyable: AtomicBool, destroyable: bool,
pub alias: OnceCell<Arc<Alias>>, pub alias: OnceCell<Arc<Alias>>,
aliases: Registry<Alias>, aliases: Registry<Alias>,
pub spatial: OnceCell<Arc<Spatial>>, pub spatial: OnceCell<Arc<Spatial>>,
pub field: OnceCell<Arc<Field>>, pub field: OnceCell<Arc<Field>>,
pub zone: OnceCell<Arc<Zone>>,
// Data // Data
pub pulse_sender: OnceCell<Arc<PulseSender>>, pub pulse_sender: OnceCell<Arc<PulseSender>>,
pub pulse_receiver: OnceCell<Arc<PulseReceiver>>, pub pulse_receiver: OnceCell<Arc<PulseReceiver>>,
// Drawable // Drawable
pub model: OnceCell<Arc<Model>>, pub drawable: OnceCell<Drawable>,
pub text: OnceCell<Arc<Text>>,
// Input // Input
pub input_method: OnceCell<Arc<InputMethod>>, pub input_method: OnceCell<Arc<InputMethod>>,
@@ -70,10 +77,11 @@ pub struct Node {
pub item_acceptor: OnceCell<Arc<ItemAcceptor>>, pub item_acceptor: OnceCell<Arc<ItemAcceptor>>,
pub item_ui: OnceCell<Arc<ItemUI>>, pub item_ui: OnceCell<Arc<ItemUI>>,
// Sound
pub sound: OnceCell<Arc<Sound>>,
// Startup // Startup
pub startup_settings: OnceCell<Mutex<StartupSettings>>, pub startup_settings: OnceCell<Mutex<StartupSettings>>,
pub(crate) client: Weak<Client>,
} }
impl Node { impl Node {
@@ -86,53 +94,62 @@ impl Node {
pub fn get_path(&self) -> &str { pub fn get_path(&self) -> &str {
self.path.as_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 { pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self {
let mut path = parent.to_string(); let mut path = parent.to_string();
path.push('/'); path.push('/');
path.push_str(name); path.push_str(name);
let node = Node { let node = Node {
enabled: Arc::new(AtomicBool::new(true)),
uid: nanoid!(), uid: nanoid!(),
client: Arc::downgrade(client), client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(),
path, path,
// trailing_slash_pos: parent.len(), // trailing_slash_pos: parent.len(),
local_signals: Default::default(), local_signals: Default::default(),
local_methods: Default::default(), local_methods: Default::default(),
destroyable: AtomicBool::from(destroyable), destroyable,
alias: OnceCell::new(), alias: OnceCell::new(),
aliases: Registry::new(), aliases: Registry::new(),
spatial: OnceCell::new(), spatial: OnceCell::new(),
field: OnceCell::new(), field: OnceCell::new(),
zone: OnceCell::new(),
pulse_sender: OnceCell::new(), pulse_sender: OnceCell::new(),
pulse_receiver: OnceCell::new(), pulse_receiver: OnceCell::new(),
model: OnceCell::new(), drawable: OnceCell::new(),
text: OnceCell::new(),
input_method: OnceCell::new(), input_method: OnceCell::new(),
input_handler: OnceCell::new(), input_handler: OnceCell::new(),
item: OnceCell::new(), item: OnceCell::new(),
item_acceptor: OnceCell::new(), item_acceptor: OnceCell::new(),
item_ui: OnceCell::new(), item_ui: OnceCell::new(),
sound: OnceCell::new(),
startup_settings: 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.add_local_signal("destroy", Node::destroy_flex);
node node
} }
pub fn add_to_scenegraph(self) -> Arc<Node> { pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
self.get_client().unwrap().scenegraph.add_node(self) Ok(self
.get_client()
.ok_or_else(|| eyre!("Internal: Unable to get client"))?
.scenegraph
.add_node(self))
} }
pub fn destroy(&self) { pub fn destroy(&self) {
let _ = self if let Some(client) = self.get_client() {
.get_client() client.scenegraph.remove_node(self.get_path());
.map(|c| c.scenegraph.remove_node(self.get_path())); }
} }
pub fn set_enabled_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
node.enabled.store(deserialize(data)?, Ordering::Relaxed);
Ok(())
}
pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> { pub fn destroy_flex(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
if node.is_destroyable() { if node.destroyable {
node.destroy(); node.destroy();
} }
Ok(()) Ok(())
@@ -150,14 +167,13 @@ impl Node {
node_name: &'static str, node_name: &'static str,
aspect_type: &'static str, aspect_type: &'static str,
aspect_fn: F, aspect_fn: F,
) -> Result<Arc<T>> ) -> Result<&T>
where where
F: FnOnce(&Node) -> &OnceCell<Arc<T>>, F: FnOnce(&Node) -> &OnceCell<T>,
{ {
aspect_fn(self) aspect_fn(self)
.get() .get()
.ok_or_else(|| anyhow!("{} is not a {} node", node_name, aspect_type)) .ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
.cloned()
} }
pub fn send_local_signal( pub fn send_local_signal(
@@ -167,7 +183,7 @@ impl Node {
data: &[u8], data: &[u8],
) -> Result<(), ScenegraphError> { ) -> Result<(), ScenegraphError> {
if let Some(alias) = self.alias.get() { 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); return Err(ScenegraphError::SignalNotFound);
} }
alias alias
@@ -180,8 +196,9 @@ impl Node {
.local_signals .local_signals
.get(method) .get(method)
.ok_or(ScenegraphError::SignalNotFound)?; .ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, data) signal(self, calling_client, data).map_err(|error| ScenegraphError::SignalError {
.map_err(|error| ScenegraphError::SignalError { error }) error: error.to_string(),
})
} }
} }
pub fn execute_local_method( pub fn execute_local_method(
@@ -191,7 +208,7 @@ impl Node {
data: &[u8], data: &[u8],
) -> Result<Vec<u8>, ScenegraphError> { ) -> Result<Vec<u8>, ScenegraphError> {
if let Some(alias) = self.alias.get() { 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); return Err(ScenegraphError::MethodNotFound);
} }
alias alias
@@ -204,44 +221,58 @@ impl Node {
.local_methods .local_methods
.get(method) .get(method)
.ok_or(ScenegraphError::MethodNotFound)?; .ok_or(ScenegraphError::MethodNotFound)?;
method(self, calling_client, data)
.map_err(|error| ScenegraphError::MethodError { error }) debug_span!("Handle method").in_scope(|| {
method(self, calling_client, data).map_err(|error| ScenegraphError::MethodError {
error: error.to_string(),
})
})
} }
} }
#[instrument(level = "debug", skip_all)]
pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> { pub fn send_remote_signal(&self, method: &str, data: &[u8]) -> Result<()> {
self.aliases self.aliases
.get_valid_contents() .get_valid_contents()
.iter() .iter()
.filter(|alias| alias.info.remote_signals.iter().any(|e| e == &method)) .filter(|alias| alias.info.client_signals.iter().any(|e| e == &method))
.for_each(|alias| { .filter_map(|alias| alias.node.upgrade())
let _ = alias .for_each(|node| {
.node let _ = node.send_remote_signal(method, data);
.upgrade()
.unwrap()
.send_remote_signal(method, data);
}); });
let path = self.path.clone(); let path = self.path.clone();
let method = method.to_string(); let method = method.to_string();
let data = data.to_vec(); let data = data.to_vec();
if let Some(client) = self.get_client() { if let Some(handle) = self.message_sender_handle.as_ref() {
if let Some(messenger) = client.messenger.as_ref() { handle.signal(path.as_str(), method.as_str(), data.as_slice())?;
messenger.send_remote_signal(path.as_str(), method.as_str(), data.as_slice());
}
} }
Ok(()) Ok(())
} }
pub async fn execute_remote_method(&self, method: &str, data: Vec<u8>) -> Result<Vec<u8>> { // #[instrument(level = "debug", skip_all)]
if let Some(client) = self.get_client() { // pub fn execute_remote_method(
match client.messenger.as_ref() { // &self,
None => Err(anyhow!("Messenger does not exist for this node's client")), // method: &str,
Some(messenger) => { // data: Vec<u8>,
messenger // ) -> Result<impl Future<Output = Result<Vec<u8>>>> {
.execute_remote_method(self.path.as_str(), method, &data) // let message_sender_handle = self
.await // .message_sender_handle
} // .as_ref()
} // .ok_or(eyre!("Messenger does not exist for this node"))?;
} else {
Err(anyhow!("Client does not exist somehow?")) // 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::spatial::Spatial;
use super::startup::DESKTOP_STARTUP_IDS;
use super::Node; use super::Node;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use anyhow::{anyhow, Result}; use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use tracing::instrument;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
@@ -14,61 +14,57 @@ static ROOT_REGISTRY: Registry<Root> = Registry::new();
pub struct Root { pub struct Root {
node: Arc<Node>, node: Arc<Node>,
logic_step: AtomicBool, send_frame_event: AtomicBool,
} }
impl Root { 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); let node = Node::create(client, "", "", false);
node.add_local_signal("applyDesktopStartupID", Root::apply_desktop_startup_id); node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
node.add_local_signal("subscribeLogicStep", Root::subscribe_logic_step); node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
node.add_local_signal("setBasePrefixes", Root::set_base_prefixes); let node = node.add_to_scenegraph()?;
let node = node.add_to_scenegraph(); let _ = Spatial::add_to(
let _ = Spatial::add_to(&node, None, Mat4::IDENTITY); &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, node,
logic_step: AtomicBool::from(false), send_frame_event: AtomicBool::from(false),
}) }))
} }
fn apply_desktop_startup_id( fn subscribe_frame_flex(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let startup_settings = DESKTOP_STARTUP_IDS
.lock()
.remove(flexbuffers::Reader::get_root(data)?.get_str()?)
.ok_or_else(|| anyhow!("Desktop startup ID not found in the list!"))?;
node.spatial
.get()
.unwrap()
.set_local_transform(startup_settings.transform);
Ok(())
}
fn subscribe_logic_step(_node: &Node, calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
calling_client calling_client
.root .root
.get() .get()
.unwrap() .unwrap()
.logic_step .send_frame_event
.store(true, Ordering::Relaxed); .store(true, Ordering::Relaxed);
Ok(()) 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)) { if let Ok(data) = serialize((delta, 0.0)) {
for root in ROOT_REGISTRY.get_valid_contents() { for root in ROOT_REGISTRY.get_valid_contents() {
if root.logic_step.load(Ordering::Relaxed) { if root.send_frame_event.load(Ordering::Relaxed) {
let _ = root.node.send_remote_signal("logicStep", &data); let _ = root.node.send_remote_signal("frame", &data);
} }
} }
} }
} }
fn set_base_prefixes(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_base_prefixes_flex(
_node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(data)?; *calling_client.base_resource_prefixes.lock() = deserialize(data)?;
Ok(()) 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(())
}

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

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

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

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

View File

@@ -1,60 +1,96 @@
use crate::core::client::Client; use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
use super::Node; use super::{
use anyhow::{anyhow, Result}; items::{ItemAcceptor, TypeInfo},
spatial::find_spatial,
Node,
};
use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; 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! { 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 struct StartupSettings {
pub transform: Mat4, pub transform: Mat4,
pub acceptors: FxHashMap<&'static TypeInfo, Weak<ItemAcceptor>>,
} }
impl StartupSettings { impl StartupSettings {
pub fn add_to(node: &Arc<Node>) { pub fn add_to(node: &Arc<Node>) {
node.startup_settings let _ = node
.set(Mutex::new(StartupSettings::default())) .startup_settings
.unwrap(); .set(Mutex::new(StartupSettings::default()));
} }
fn set_root_flex(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> { 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 = find_spatial(&calling_client, "Root spatial", deserialize(data)?)?;
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"))?;
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform(); node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
Ok(()) Ok(())
} }
fn generate_desktop_startup_id_flex( fn add_automatic_acceptor_flex(
node: &Node,
calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let acceptor_node = calling_client.get_node("Item acceptor", deserialize(data)?)?;
let acceptor =
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
let mut startup_settings = node.startup_settings.get().unwrap().lock();
startup_settings
.acceptors
.insert(acceptor.type_info, Arc::downgrade(acceptor));
Ok(())
}
fn generate_startup_token_flex(
node: &Node, node: &Node,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
_data: &[u8], _data: &[u8],
) -> Result<Vec<u8>> { ) -> Result<Vec<u8>> {
let id = nanoid::nanoid!(); let id = nanoid::nanoid!();
let data = flexbuffers::singleton(id.as_str()); let data = serialize(&id)?;
DESKTOP_STARTUP_IDS STARTUP_SETTINGS
.lock() .lock()
.insert(id, node.startup_settings.get().unwrap().lock().clone()); .insert(id, node.startup_settings.get().unwrap().lock().clone());
Ok(data) Ok(data)
} }
} }
impl Debug for StartupSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StartupSettings")
.field("transform", &self.transform)
.field(
"acceptors",
&self
.acceptors
.iter()
.map(|(k, _)| k.type_name)
.collect::<Vec<_>>(),
)
.finish()
}
}
pub fn create_interface(client: &Arc<Client>) { pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "startup", false); let node = Node::create(client, "", "startup", false);
node.add_local_signal("createStartupSettings", create_startup_settings_flex); node.add_local_signal("create_startup_settings", create_startup_settings_flex);
node.add_to_scenegraph(); node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
node.add_to_scenegraph().map(|_| ())
} }
pub fn create_startup_settings_flex( pub fn create_startup_settings_flex(
@@ -62,15 +98,49 @@ pub fn create_startup_settings_flex(
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], data: &[u8],
) -> Result<()> { ) -> Result<()> {
let name = flexbuffers::Reader::get_root(data)?.get_str()?; let node = Node::create(
let node = Node::create(&calling_client, "/startup/settings", name, true).add_to_scenegraph(); &calling_client,
"/startup/settings",
deserialize(data)?,
true,
)
.add_to_scenegraph()?;
StartupSettings::add_to(&node); 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( node.add_local_method(
"generateDesktopStartupID", "generate_startup_token",
StartupSettings::generate_desktop_startup_id_flex, StartupSettings::generate_startup_token_flex,
); );
Ok(()) Ok(())
} }
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: &Node,
_calling_client: Arc<Client>,
_data: &[u8],
) -> Result<Vec<u8>> {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?)
}

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 mouse_pointer;
pub mod sk_controller; pub mod sk_controller;
pub mod sk_hand; pub mod sk_hand;

View File

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

275
src/objects/input/sk.kmp Normal file
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,40 +1,50 @@
use crate::nodes::{ use crate::{
input::{tip::Tip, InputMethod, InputType}, core::client::INTERNAL_CLIENT,
spatial::Spatial, nodes::{
input::{tip::Tip, InputMethod, InputType},
spatial::Spatial,
Node,
},
}; };
use color_eyre::eyre::Result;
use glam::Mat4; use glam::Mat4;
use stardust_xr::{schemas::flat::Datamap, values::Transform}; use nanoid::nanoid;
use std::sync::{Arc, Weak}; use stardust_xr::{
use stereokit::{ schemas::{flat::Datamap, flex::flexbuffers},
input::{ButtonState, Handed}, values::Transform,
StereoKit,
}; };
use std::sync::Arc;
use stereokit::{ButtonState, Handed, StereoKitMultiThread};
use tracing::instrument;
pub struct SkController { pub struct SkController {
tip: Arc<InputMethod>, _node: Arc<Node>,
input: Arc<InputMethod>,
handed: Handed, handed: Handed,
} }
impl SkController { impl SkController {
pub fn new(handed: Handed) -> Self { pub fn new(handed: Handed) -> Result<Self> {
SkController { let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
tip: InputMethod::new( Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
Spatial::new(Weak::new(), None, Mat4::IDENTITY), let tip = InputType::Tip(Tip::default());
InputType::Tip(Tip::default()), let input = InputMethod::add_to(&_node, tip, None)?;
), Ok(SkController {
_node,
input,
handed, handed,
} })
} }
pub fn update(&mut self, sk: &StereoKit) { #[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let controller = sk.input_controller(self.handed); let controller = sk.input_controller(self.handed);
*self.tip.enabled.lock() = controller.tracked.contains(ButtonState::Active); *self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
if *self.tip.enabled.lock() { if *self.input.enabled.lock() {
self.tip.spatial.set_local_transform_components( self.input.spatial.set_local_transform_components(
None, None,
Transform { Transform::from_position_rotation(
position: Some(controller.pose.position), controller.pose.position,
rotation: Some(controller.pose.orientation), controller.pose.orientation,
scale: None, ),
},
); );
} }
let mut fbb = flexbuffers::Builder::default(); let mut fbb = flexbuffers::Builder::default();
@@ -42,6 +52,6 @@ impl SkController {
map.push("select", controller.trigger); map.push("select", controller.trigger);
map.push("grab", controller.grip); map.push("grab", controller.grip);
map.end_map(); map.end_map();
*self.tip.datamap.lock() = Datamap::new(fbb.take_buffer()).ok(); *self.input.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
} }
} }

View File

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

View File

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

View File

@@ -1,34 +1,96 @@
use super::state::WaylandState; use super::state::WaylandState;
use smithay::{ use smithay::{
delegate_kde_decoration, delegate_xdg_decoration, delegate_kde_decoration,
reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, reexports::{
wayland::shell::{ wayland_protocols::xdg::{
self, kde::decoration::KdeDecorationHandler, xdg::decoration::XdgDecorationHandler, 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 { impl GlobalDispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
fn new_decoration(&mut self, toplevel: smithay::wayland::shell::xdg::ToplevelSurface) { fn bind(
toplevel.with_pending_state(|state| { _state: &mut WaylandState,
state.decoration_mode = Some(Mode::ServerSide); _handle: &DisplayHandle,
}); _client: &Client,
toplevel.send_configure(); resource: New<ZxdgDecorationManagerV1>,
} _global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
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,
) { ) {
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 { impl KdeDecorationHandler for WaylandState {
fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState { fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState {
&self.kde_decoration_state &self.kde_decoration_state
} }
fn new_decoration(&mut self, _surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) {
decoration.mode(KdeMode::Server);
}
fn request_mode(
&mut self,
_surface: &WlSurface,
decoration: &OrgKdeKwinServerDecoration,
_mode: WEnum<KdeMode>,
) {
decoration.mode(KdeMode::Server);
}
} }
delegate_kde_decoration!(WaylandState); delegate_kde_decoration!(WaylandState);

View File

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

View File

@@ -1,352 +1,380 @@
use super::{ use super::{
seat::{KeyboardInfo, SeatData}, seat::{Cursor, SeatData},
surface::CoreSurface, surface::CoreSurface,
xdg_shell::{PopupData, ToplevelData, XdgSurfaceData},
SERIAL_COUNTER,
}; };
use crate::{ use crate::{
core::{ core::{
client::{Client, INTERNAL_CLIENT}, client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
registry::Registry, registry::Registry,
}, },
nodes::{ nodes::{
items::{register_item_ui_flex, Item, ItemSpecialization, ItemType, TypeInfo}, drawable::Drawable,
items::{self, Item, ItemSpecialization, ItemType, TypeInfo},
spatial::Spatial, spatial::Spatial,
Node, Node,
}, },
wayland::seat::{KeyboardEvent, PointerEvent},
}; };
use anyhow::{anyhow, bail, Result}; use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4; use glam::Mat4;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mint::Vector2; use mint::Vector2;
use nanoid::nanoid; use nanoid::nanoid;
use serde::Deserialize; use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{
de::{Deserializer, Error, SeqAccess, Visitor},
ser::Serializer,
Deserialize, Serialize,
};
use smithay::{ use smithay::{
reexports::wayland_server::protocol::wl_pointer::{Axis, ButtonState}, reexports::{
utils::Size, wayland_protocols::xdg::shell::server::{
wayland::{compositor::SurfaceData, shell::xdg::XdgToplevelSurfaceData}, xdg_popup::XdgPopup,
xdg_surface::XdgSurface,
xdg_toplevel::{XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE},
},
wayland_server::{
backend::Credentials, protocol::wl_surface::WlSurface, Resource, Weak as WlWeak,
},
},
wayland::compositor,
}; };
use stardust_xr::schemas::flex::{deserialize, serialize}; use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tracing::debug;
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap}; use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
lazy_static! { lazy_static! {
static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel", type_name: "panel",
aliased_local_signals: vec![ aliased_local_signals: vec![
"applySurfaceMaterial", "apply_surface_material",
"applyCursorMaterial", "configure_toplevel",
"pointerDeactivate", "set_toplevel_capabilities",
"pointerScroll", "pointer_scroll",
"pointerButton", "pointer_button",
"pointerMotion", "pointer_motion",
"keyboardSetActive", "keyboard_key",
"keyboardSetKeyState", "keyboard_set_keymap_names",
"keyboardSetModifiers", "keyboard_set_keymap_string",
"resize",
"close", "close",
], ],
aliased_local_methods: vec![], aliased_local_methods: vec![],
aliased_remote_signals: vec!["resize", "setCursor",], aliased_remote_signals: vec![
aliased_remote_methods: vec![], "commit_toplevel",
"recommend_toplevel_state",
"set_cursor",
"new_popup",
"reposition_popup",
"drop_popup",
],
ui: Default::default(), ui: Default::default(),
items: Registry::new(), items: Registry::new(),
acceptors: Registry::new(), acceptors: Registry::new(),
}; };
} }
/// An ID for a surface inside this panel item
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum SurfaceID {
Cursor,
Toplevel,
Popup(String),
}
impl Default for SurfaceID {
fn default() -> Self {
Self::Toplevel
}
}
impl<'de> serde::Deserialize<'de> for SurfaceID {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_seq(SurfaceIDVisitor)
}
}
struct SurfaceIDVisitor;
impl<'de> Visitor<'de> for SurfaceIDVisitor {
type Value = SurfaceID;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("idk")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let Some(discrim) = seq.next_element()? else {
return Err(A::Error::missing_field("discrim"));
};
// idk if you wanna check for extraneous elements
// I didn't bother
match discrim {
"Cursor" => Ok(SurfaceID::Cursor),
"Toplevel" => Ok(SurfaceID::Toplevel),
"Popup" => {
let Some(text) = seq.next_element()? else {
return Err(A::Error::missing_field("popup_text"));
};
Ok(SurfaceID::Popup(text))
}
_ => Err(A::Error::unknown_variant(
discrim,
&["Cursor", "Toplevel", "Popup"],
)),
}
}
}
impl serde::Serialize for SurfaceID {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Cursor => ["Cursor"].serialize(serializer),
Self::Toplevel => ["Toplevel"].serialize(serializer),
Self::Popup(text) => ["Popup", text].serialize(serializer),
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(tag = "type", content = "content")]
pub enum RecommendedState {
Maximize(bool),
Fullscreen(bool),
Minimize,
Move,
Resize(u32),
}
pub struct PanelItem { pub struct PanelItem {
pub uid: String,
node: Weak<Node>, node: Weak<Node>,
core_surface: Weak<CoreSurface>, cursor: Mutex<Option<WlWeak<WlSurface>>>,
seat_data: SeatData, pub seat_data: Arc<SeatData>,
toplevel: WlWeak<XdgToplevel>,
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
pointer_grab: Mutex<Option<SurfaceID>>,
keyboard_grab: Mutex<Option<SurfaceID>>,
} }
impl PanelItem { impl PanelItem {
pub fn create(core_surface: &Arc<CoreSurface>, seat_data: SeatData) -> Arc<Node> { pub fn create(
toplevel: XdgToplevel,
wl_surface: WlSurface,
client_credentials: Option<Credentials>,
seat_data: Arc<SeatData>,
) -> (Arc<Node>, Arc<PanelItem>) {
debug!(?toplevel, ?client_credentials, "Create panel item");
let startup_settings = client_credentials
.and_then(|cred| get_env(cred.pid).ok())
.and_then(|env| startup_settings(&env));
let uid = nanoid!();
let node = Arc::new(Node::create( let node = Arc::new(Node::create(
&INTERNAL_CLIENT, &INTERNAL_CLIENT,
"/item/panel/item", "/item/panel/item",
&nanoid!(), &uid,
true, true,
)); ));
Spatial::add_to(&node, None, Mat4::IDENTITY).unwrap(); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let panel_item = Arc::new(PanelItem {
let specialization = ItemType::Panel(PanelItem { uid: uid.clone(),
node: Arc::downgrade(&node), node: Arc::downgrade(&node),
core_surface: Arc::downgrade(core_surface), cursor: Mutex::new(None),
seat_data, seat_data,
toplevel: toplevel.downgrade(),
popups: Mutex::new(FxHashMap::default()),
pointer_grab: Mutex::new(None),
keyboard_grab: Mutex::new(None),
}); });
let item = Item::add_to(&node, &ITEM_TYPE_INFO_PANEL, specialization);
if let ItemType::Panel(panel) = &item.specialization { if let Some(startup_settings) = &startup_settings {
let _ = panel.seat_data.panel_item.set(Arc::downgrade(&item)); spatial.set_local_transform(
spatial.global_transform().inverse() * startup_settings.transform,
);
} }
panel_item
.seat_data
.new_surface(&wl_surface, Arc::downgrade(&panel_item));
let item = Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(panel_item.clone()),
);
if let Some(startup_settings) = &startup_settings {
if let Some(acceptor) = startup_settings
.acceptors
.get(&*ITEM_TYPE_INFO_PANEL)
.and_then(|acc| acc.upgrade())
{
items::capture(&item, &acceptor);
}
}
node.add_local_signal( node.add_local_signal(
"applySurfaceMaterial", "apply_surface_material",
PanelItem::apply_surface_material_flex, PanelItem::apply_surface_material_flex,
); );
node.add_local_signal("applyCursorMaterial", PanelItem::apply_cursor_material_flex); node.add_local_signal("configure_toplevel", PanelItem::configure_toplevel_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( node.add_local_signal(
"keyboardActivateString", "set_toplevel_capabilities",
PanelItem::keyboard_activate_string_flex, PanelItem::set_toplevel_capabilities_flex,
);
node.add_local_signal("pointer_scroll", PanelItem::pointer_scroll_flex);
node.add_local_signal("pointer_button", PanelItem::pointer_button_flex);
node.add_local_signal("pointer_motion", PanelItem::pointer_motion_flex);
node.add_local_signal(
"keyboard_set_keymap_string",
PanelItem::keyboard_set_keymap_string_flex,
); );
node.add_local_signal( node.add_local_signal(
"keyboardActivateNames", "keyboard_set_keymap_names",
PanelItem::keyboard_activate_names_flex, PanelItem::keyboard_set_keymap_names_flex,
); );
node.add_local_signal("keyboardDeactivate", PanelItem::keyboard_deactivate_flex); node.add_local_signal("keyboard_key", PanelItem::keyboard_key_flex);
node.add_local_signal("keyboardKeyState", PanelItem::keyboard_key_state_flex);
node.add_local_signal("resize", PanelItem::resize_flex); (node, panel_item)
node
} }
pub fn from_node(node: &Node) -> &PanelItem { pub fn from_node(node: &Node) -> Option<Arc<PanelItem>> {
match &node.item.get().unwrap().specialization { let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
ItemType::Panel(panel_item) => panel_item, Some(panel_item.clone())
_ => unreachable!(), }
fn toplevel(&self) -> XdgToplevel {
self.toplevel.upgrade().unwrap()
}
fn toplevel_xdg_surface(&self) -> XdgSurface {
let toplevel = self.toplevel();
let data = ToplevelData::get(&toplevel).lock();
data.xdg_surface()
}
fn toplevel_wl_surface(&self) -> WlSurface {
XdgSurfaceData::get(&self.toplevel_xdg_surface())
.lock()
.wl_surface()
}
fn core_surface(&self) -> Option<Arc<CoreSurface>> {
compositor::with_states(&self.toplevel_wl_surface(), |data| {
data.data_map.get::<Arc<CoreSurface>>().cloned()
})
}
fn flush_clients(&self) {
if let Some(core_surface) = self.core_surface() {
core_surface.flush_clients();
} }
} }
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self.cursor.lock().clone()?.upgrade().ok(),
SurfaceID::Toplevel => Some(self.toplevel_wl_surface()),
SurfaceID::Popup(popup) => {
let popups = self.popups.lock();
let popup = popups.get(popup)?.upgrade().ok()?;
let surf = PopupData::get(&popup).lock().wl_surface();
Some(surf)
}
}
}
fn wl_surface_from_id_result(&self, id: &SurfaceID) -> Result<WlSurface> {
self.wl_surface_from_id(id)
.ok_or(eyre!("Surface with ID not found"))
}
fn apply_surface_material_flex( fn apply_surface_material_flex(
node: &Node, node: &Node,
calling_client: Arc<Client>, calling_client: Arc<Client>,
data: &[u8], data: &[u8],
) -> Result<()> { ) -> Result<()> {
#[derive(Deserialize)] let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct SurfaceMaterialInfo<'a> { struct SurfaceMaterialInfo<'a> {
model_path: &'a str, surface: SurfaceID,
idx: u32, model_node_path: &'a str,
} }
let info: SurfaceMaterialInfo = deserialize(data)?; let info: SurfaceMaterialInfo = deserialize(data)?;
let Some(wl_surface) = panel_item.wl_surface_from_id(&info.surface) else { return Ok(()) };
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { return Ok(()) };
let model_node = calling_client let model_node = calling_client
.scenegraph .scenegraph
.get_node(info.model_path) .get_node(info.model_node_path)
.ok_or_else(|| anyhow!("Model node not found"))?; .ok_or_else(|| eyre!("Model node not found"))?;
let model = model_node let Some(Drawable::ModelPart(model_node)) = model_node.drawable.get() else {bail!("Node is not a model")};
.model debug!(?info, "Apply surface material");
.get()
.ok_or_else(|| anyhow!("Node is not a model"))?;
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { core_surface.apply_material(model_node);
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(()) Ok(())
} }
fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn pointer_motion_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
if let Some(pointer) = panel_item.seat_data.pointer() {
if let Some(core_surface) = panel_item.core_surface.upgrade() { let (surface_id, position): (SurfaceID, Vector2<f64>) = deserialize(data)?;
if let Some(size) = core_surface.with_data(|data| data.size) { let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
let mut position: Vector2<f64> = deserialize(data)?; debug!(?surface_id, ?position, "Pointer deactivate");
position.x = position.x.clamp(0.0, size.x as f64);
position.y = position.y.clamp(0.0, size.y as f64); panel_item
let mut pointer_active = panel_item.seat_data.pointer_active.lock(); .seat_data
if *pointer_active { .pointer_event(&wl_surface, PointerEvent::Motion(position));
pointer.motion(0, position.x, position.y); panel_item.flush_clients();
} else {
pointer.enter(0, &core_surface.wl_surface(), position.x, position.y);
*pointer_active = true;
}
pointer.frame();
core_surface.flush_clients();
}
}
}
}
Ok(()) Ok(())
} }
fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn pointer_button_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() { let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(data)?;
if let Some(core_surface) = panel_item.core_surface.upgrade() { let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
let (button, state): (u32, u32) = deserialize(data)?; debug!(?surface_id, button, state, "Pointer button");
pointer.button(
0, panel_item
0, .seat_data
button, .pointer_event(&wl_surface, PointerEvent::Button { button, state });
match state { panel_item.flush_clients();
0 => ButtonState::Released,
1 => ButtonState::Pressed,
_ => {
bail!("Button state is out of bounds")
}
},
);
pointer.frame();
core_surface.flush_clients();
}
}
}
}
Ok(()) Ok(())
} }
fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn pointer_scroll_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
#[derive(Deserialize)] let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
struct PointerScrollArgs {
axis_continuous: Vector2<f32>, #[derive(Debug, Deserialize)]
struct PointerScrollInfo {
surface_id: SurfaceID,
axis_continuous: Option<Vector2<f32>>,
axis_discrete: Option<Vector2<f32>>, axis_discrete: Option<Vector2<f32>>,
} }
let args: PointerScrollArgs = deserialize(data)?; let info: PointerScrollInfo = deserialize(data)?;
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { let wl_surface = panel_item.wl_surface_from_id_result(&info.surface_id)?;
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() { debug!(?info, "Pointer scroll");
if let Some(core_surface) = panel_item.core_surface.upgrade() {
let flex = flexbuffers::Reader::get_root(data)?; panel_item.seat_data.pointer_event(
if flex.flexbuffer_type().is_null() { &wl_surface,
pointer.axis_stop(0, Axis::HorizontalScroll); PointerEvent::Scroll {
pointer.axis_stop(0, Axis::VerticalScroll); axis_continuous: info.axis_continuous,
} else { axis_discrete: info.axis_discrete,
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 { panel_item.flush_clients();
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(()) Ok(())
} }
fn keyboard_activate_string_flex( fn keyboard_set_keymap_string_flex(
node: &Node, node: &Node,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8], data: &[u8],
@@ -354,17 +382,16 @@ impl PanelItem {
let context = xkb::Context::new(0); let context = xkb::Context::new(0);
let keymap = let keymap =
Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0) Keymap::new_from_string(&context, deserialize(data)?, XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| anyhow!("Keymap is not valid"))?; .ok_or_else(|| eyre!("Keymap is not valid"))?;
PanelItem::keyboard_activate_flex(node, &keymap) PanelItem::keyboard_set_keymap_flex(node, &keymap)
} }
fn keyboard_set_keymap_names_flex(
fn keyboard_activate_names_flex(
node: &Node, node: &Node,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8], data: &[u8],
) -> Result<()> { ) -> Result<()> {
#[derive(Deserialize)] #[derive(Debug, Deserialize)]
struct Names<'a> { struct Names<'a> {
rules: &'a str, rules: &'a str,
model: &'a str, model: &'a str,
@@ -383,124 +410,246 @@ impl PanelItem {
names.options, names.options,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_FORMAT_TEXT_V1,
) )
.ok_or_else(|| anyhow!("Keymap is not valid"))?; .ok_or_else(|| eyre!("Keymap is not valid"))?;
PanelItem::keyboard_activate_flex(node, &keymap) PanelItem::keyboard_set_keymap_flex(node, &keymap)
} }
fn keyboard_set_keymap_flex(node: &Node, keymap: &Keymap) -> Result<()> {
let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
let toplevel = panel_item.toplevel_wl_surface();
debug!(?toplevel, "Keyboard set keymap");
fn keyboard_activate_flex(node: &Node, keymap: &Keymap) -> Result<()> { let mut surfaces = vec![toplevel];
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { surfaces.extend(panel_item.popups.lock().values().filter_map(|p| {
if let Some(keyboard) = panel_item.seat_data.keyboard() { let popup = p.upgrade().ok()?;
if let Some(core_surface) = panel_item.core_surface.upgrade() { let popup_data = PopupData::get(&popup).lock();
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock(); let xdg_surface = popup_data.xdg_surface();
if keyboard_info.is_none() { let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
keyboard.enter(0, &core_surface.wl_surface(), vec![]); Some(xdg_surface_data.wl_surface())
keyboard.repeat_info(0, 0); }));
}
keyboard_info.replace(KeyboardInfo::new(keymap)); panel_item.seat_data.set_keymap(keymap, surfaces);
keyboard_info.as_ref().unwrap().keymap.send(keyboard)?;
}
}
}
Ok(()) Ok(())
} }
fn keyboard_deactivate_flex( fn keyboard_key_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
node: &Node, let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
_calling_client: Arc<Client>, let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(data)?;
_data: &[u8], let wl_surface = panel_item.wl_surface_from_id_result(&surface_id)?;
) -> Result<()> { debug!(key, state, "Set keyboard key state");
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() { panel_item
if let Some(core_surface) = panel_item.core_surface.upgrade() { .seat_data
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock(); .keyboard_event(&wl_surface, KeyboardEvent::Key { key, state });
if keyboard_info.is_some() {
keyboard.leave(0, &core_surface.wl_surface());
*keyboard_info = None;
}
}
}
}
Ok(()) Ok(())
} }
fn keyboard_key_state_flex( fn configure_toplevel_flex(
node: &Node, node: &Node,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
data: &[u8], data: &[u8],
) -> Result<()> { ) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
if let Some(keyboard) = panel_item.seat_data.keyboard() { let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
let mut keyboard_info = panel_item.seat_data.keyboard_info.lock(); let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
if let Some(keyboard_info) = &mut *keyboard_info { let xdg_surface = panel_item.toplevel_xdg_surface();
let (key, state): (u32, u32) = deserialize(data)?;
keyboard_info.process(key, state, keyboard)?; #[derive(Debug, Deserialize)]
} struct ConfigureToplevelInfo {
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
}
let info: ConfigureToplevelInfo = deserialize(data)?;
debug!(info = ?&info, "Configure toplevel info");
if let Some(bounds) = info.bounds {
if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE {
xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32);
} }
} }
let size = info.size.unwrap_or(Vector2::from([0; 2]));
xdg_toplevel.configure(
size.x as i32,
size.y as i32,
info.states
.into_iter()
.flat_map(|state| state.to_ne_bytes())
.collect::<Vec<_>>(),
);
xdg_surface.configure(SERIAL_COUNTER.inc());
core_surface.flush_clients();
Ok(()) Ok(())
} }
fn resize_flex(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> { fn set_toplevel_capabilities_flex(
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization { node: &Node,
if let Some(core_surface) = panel_item.core_surface.upgrade() { _calling_client: Arc<Client>,
let size: Vector2<u32> = deserialize(data)?; data: &[u8],
) -> Result<()> {
let toplevel_surface = core_surface let Some(panel_item) = PanelItem::from_node(node) else { return Ok(()) };
.wayland_state() let Some(core_surface) = panel_item.core_surface() else { return Ok(()) };
.lock() let Ok(xdg_toplevel) = panel_item.toplevel.upgrade() else { return Ok(()) };
.xdg_shell_state if xdg_toplevel.version() < EVT_WM_CAPABILITIES_SINCE {
.toplevel_surfaces(|surfaces| { return Ok(());
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();
}
}
}
} }
let xdg_surface = panel_item.toplevel_xdg_surface();
let capabilities: Vec<u8> = deserialize(data)?;
debug!("Set toplevel capabilities");
xdg_toplevel.wm_capabilities(capabilities);
xdg_surface.configure(SERIAL_COUNTER.inc());
core_surface.flush_clients();
Ok(()) Ok(())
} }
pub fn commit_toplevel(&self) {
// let mapped_size = self.core_surface().and_then(|c| c.size());
let toplevel = self.toplevel();
let state = ToplevelData::get(&toplevel);
let state = state.lock();
// let mut queued_state = state.queued_state.take().unwrap();
// queued_state.mapped = mapped_size.is_some();
// if let Some(size) = mapped_size {
// queued_state.size = size;
// queued_state.geometry.update_to_surface_size(size);
// }
// *state = (*queued_state).clone();
// state.queued_state = Some(queued_state);
debug!(state = ?&*state, "Commit toplevel");
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("commit_toplevel", &serialize(&*state).unwrap());
}
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
let Some(node) = self.node.upgrade() else { return };
let data = serialize(state).unwrap();
debug!(?state, "Recommend toplevel state");
let _ = node.send_remote_signal("recommend_toplevel_state", &data);
}
pub fn new_popup(&self, popup: &XdgPopup, data: &PopupData) {
let uid = data.uid.clone();
self.popups.lock().insert(uid.clone(), popup.downgrade());
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("new_popup", &serialize(&(&uid, data)).unwrap());
}
// pub fn commit_popup(&self, data: &PopupData) {
// let xdg_surf = data.xdg_surface.upgrade().unwrap();
// let surf = xdg_surf
// .data::<XdgSurfaceData>()
// .unwrap()
// .wl_surface
// .upgrade()
// .unwrap();
// let core_surface =
// compositor::with_states(&surf, |s| s.data_map.get::<Arc<CoreSurface>>().cloned())
// .unwrap();
// let mut popup_state = data.state.lock();
// popup_state.mapped = core_surface.size().is_some();
// }
pub fn reposition_popup(&self, popup_state: &PopupData) {
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal(
"reposition_popup",
&serialize(popup_state.positioner_data().unwrap()).unwrap(),
);
}
pub fn drop_popup(&self, uid: &str) {
if let Some(popup) = self
.popups
.lock()
.remove(uid)
.and_then(|popup| popup.upgrade().ok()?.data::<Arc<PopupData>>().cloned())
{
self.seat_data.drop_surface(&popup.wl_surface());
}
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("drop_popup", &serialize(uid).unwrap());
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else { return };
let _ = node.send_remote_signal("grab_keyboard", &serialize(sid).unwrap());
}
pub fn set_cursor(&self, surface: Option<&WlSurface>, hotspot_x: i32, hotspot_y: i32) {
let Some(node) = self.node.upgrade() else { return };
debug!(?surface, hotspot_x, hotspot_y, "Set cursor size");
let mut data = serialize(()).unwrap();
let cursor_size = surface
.and_then(|c| CoreSurface::from_wl_surface(c))
.and_then(|c| c.size());
if let Some(size) = cursor_size {
data = serialize((size, (hotspot_x, hotspot_y))).unwrap();
}
let _ = node.send_remote_signal("set_cursor", &data);
*self.cursor.lock() = surface.map(|surf| surf.downgrade());
}
pub fn on_drop(&self) {
let toplevel = self.toplevel_wl_surface();
self.seat_data.drop_surface(&toplevel);
debug!("Drop panel item");
}
} }
impl ItemSpecialization for PanelItem { impl ItemSpecialization for PanelItem {
fn serialize_start_data(&self, id: &str) -> Vec<u8> { fn serialize_start_data(&self, id: &str) -> Vec<u8> {
// Panel size let cursor = self.cursor.lock().as_ref().and_then(|c| c.upgrade().ok());
let panel_size = self let cursor_size = cursor
.core_surface .as_ref()
.upgrade() .and_then(|c| CoreSurface::from_wl_surface(&c))
.unwrap() .and_then(|c| c.size());
.with_data(|data| data.size); let cursor_hotspot = cursor
.and_then(|c| {
compositor::with_states(&c, |data| data.data_map.get::<Arc<Cursor>>().cloned())
})
.map(|cursor| cursor.hotspot);
let cursor_lock = (*self.seat_data.cursor.lock()).clone(); let toplevel = self.toplevel();
let cursor_size = cursor_lock let toplevel_state = ToplevelData::get(&toplevel);
.clone() let toplevel_state = toplevel_state.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() let popups = self
.popups
.lock()
.values()
.filter_map(|v| Some(v.upgrade().ok()?.data::<Mutex<PopupData>>()?.lock().clone()))
.collect::<Vec<_>>();
let pointer_grab = self.pointer_grab.lock().clone();
let keyboard_grab = self.keyboard_grab.lock().clone();
serialize((
id,
(
cursor_size.zip(cursor_hotspot),
toplevel_state,
popups,
pointer_grab,
keyboard_grab,
),
))
.unwrap()
} }
} }
impl Drop for PanelItem {
pub fn register_panel_item_ui_flex( fn drop(&mut self) {
_node: &Node, // Dropped panel item, basically just a debug breakpoint place
calling_client: Arc<Client>, }
_data: &[u8],
) -> Result<()> {
register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL)
} }

View File

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

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; 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.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; return o;
} }
float4 ps(psIn input) : SV_TARGET { float4 ps(psIn input) : SV_TARGET {

View File

@@ -4,19 +4,19 @@
//--name = stardust/text_shader //--name = stardust/text_shader
//--diffuse = white //--diffuse = white
//--fcFactor = 1.0
//--ripple = 4.0
//--uv_offset = 0.0, 0.0 //--uv_offset = 0.0, 0.0
//--uv_scale = 1.0, 1.0 //--uv_scale = 1.0, 1.0
//--fcFactor = 1.0
//--ripple = 4.0
//--alpha_min = 0.0 //--alpha_min = 0.0
//--alpha_max = 1.0 //--alpha_max = 1.0
Texture2D diffuse : register(t0); Texture2D diffuse : register(t0);
SamplerState diffuse_s : register(s0); SamplerState diffuse_s : register(s0);
float4 diffuse_i; float4 diffuse_i;
float fcFactor;
float ripple;
float2 uv_scale; float2 uv_scale;
float2 uv_offset; float2 uv_offset;
float fcFactor;
float ripple;
float alpha_min; float alpha_min;
float alpha_max; 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; 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.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; return o;
} }

View File

@@ -1,41 +1,48 @@
use crate::wayland::seat::SeatData; use crate::wayland::seat::SeatData;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use slog::Logger;
use smithay::{ use smithay::{
delegate_output, delegate_shm, backend::{
output::{Output, Scale, Subpixel}, allocator::dmabuf::Dmabuf,
renderer::{gles::GlesRenderer, ImportDma},
},
delegate_dmabuf, delegate_output, delegate_shm,
output::{Mode, Output, Scale, Subpixel},
reexports::{ 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::{ wayland_server::{
backend::{ClientData, ClientId, DisconnectReason}, backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_data_device_manager::WlDataDeviceManager, protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
Display, DisplayHandle, Display, DisplayHandle,
}, },
}, },
utils::Size, utils::{Size, Transform},
wayland::{ wayland::{
buffer::BufferHandler, buffer::BufferHandler,
compositor::CompositorState, compositor::{CompositorClientState, CompositorState},
output::OutputManagerState, dmabuf::{self, DmabufGlobal, DmabufHandler, DmabufState},
shell::{ shell::kde::decoration::KdeDecorationState,
kde::decoration::KdeDecorationState,
xdg::{decoration::XdgDecorationState, XdgShellState},
},
shm::{ShmHandler, ShmState}, shm::{ShmHandler, ShmState},
xdg_activation::XdgActivationState,
}, },
}; };
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tracing::info;
pub struct ClientState; #[derive(Default)]
pub struct ClientState {
pub compositor_state: CompositorClientState,
}
impl ClientData for ClientState { impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) { 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) { fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
println!( info!(
"Wayland client {:?} disconnected because {:#?}", "Wayland client {:?} disconnected because {:#?}",
client_id, reason client_id, reason
); );
@@ -48,30 +55,32 @@ pub struct WaylandState {
pub display_handle: DisplayHandle, pub display_handle: DisplayHandle,
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
pub xdg_activation_state: XdgActivationState, // pub xdg_activation_state: XdgActivationState,
pub xdg_decoration_state: XdgDecorationState,
pub kde_decoration_state: KdeDecorationState, pub kde_decoration_state: KdeDecorationState,
pub xdg_shell_state: XdgShellState,
pub shm_state: ShmState, pub shm_state: ShmState,
pub output_manager_state: OutputManagerState, pub dmabuf_state: DmabufState,
pub dmabuf_global: DmabufGlobal,
pub pending_dmabufs: Vec<Dmabuf>,
pub output: Output, pub output: Output,
pub seats: FxHashMap<ClientId, SeatData>, pub seats: FxHashMap<ClientId, Arc<SeatData>>,
} }
impl WaylandState { impl WaylandState {
pub fn new( pub fn new(
log: Logger,
display: Arc<Mutex<Display<WaylandState>>>, display: Arc<Mutex<Display<WaylandState>>>,
display_handle: DisplayHandle, display_handle: DisplayHandle,
renderer: &GlesRenderer,
) -> Arc<Mutex<Self>> { ) -> Arc<Mutex<Self>> {
let compositor_state = CompositorState::new::<Self, _>(&display_handle, log.clone()); let compositor_state = CompositorState::new::<Self>(&display_handle);
let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle, log.clone()); // let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
let xdg_shell_state = XdgShellState::new::<Self, _>(&display_handle, log.clone());
let xdg_decoration_state = XdgDecorationState::new::<Self, _>(&display_handle, log.clone());
let kde_decoration_state = let kde_decoration_state =
KdeDecorationState::new::<Self, _>(&display_handle, Mode::Server, log.clone()); KdeDecorationState::new::<Self>(&display_handle, DecorationMode::Server);
let shm_state = ShmState::new::<Self, _>(&display_handle, vec![], log.clone()); let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle); let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state.create_global::<Self>(
&display_handle,
renderer.dmabuf_formats().collect::<Vec<_>>(),
);
let output = Output::new( let output = Output::new(
"1x".to_owned(), "1x".to_owned(),
smithay::output::PhysicalProperties { smithay::output::PhysicalProperties {
@@ -80,13 +89,24 @@ impl WaylandState {
make: "Virtual XR Display".to_owned(), make: "Virtual XR Display".to_owned(),
model: "Your Headset Name Here".to_owned(), model: "Your Headset Name Here".to_owned(),
}, },
log.clone(),
); );
let _global = output.create_global::<Self>(&display_handle); let _output_global = output.create_global::<Self>(&display_handle);
output.change_current_state(None, None, Some(Scale::Integer(2)), None); let mode = Mode {
size: (4096, 4096).into(),
refresh: 60000,
};
output.change_current_state(
Some(mode),
Some(Transform::Normal),
Some(Scale::Integer(2)),
None,
);
output.set_preferred(mode);
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ()); display_handle.create_global::<Self, 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| { Arc::new_cyclic(|weak| {
Mutex::new(WaylandState { Mutex::new(WaylandState {
@@ -95,12 +115,12 @@ impl WaylandState {
display_handle, display_handle,
compositor_state, compositor_state,
xdg_activation_state, // xdg_activation_state,
xdg_decoration_state,
kde_decoration_state, kde_decoration_state,
xdg_shell_state,
shm_state, shm_state,
output_manager_state, dmabuf_state,
dmabuf_global,
pending_dmabufs: Vec::new(),
output, output,
seats: FxHashMap::default(), seats: FxHashMap::default(),
}) })
@@ -114,20 +134,30 @@ impl WaylandState {
} }
impl Drop for WaylandState { impl Drop for WaylandState {
fn drop(&mut self) { fn drop(&mut self) {
println!("Cleanly shut down the Wayland compositor"); info!("Cleanly shut down the Wayland compositor");
} }
} }
impl BufferHandler for WaylandState { impl BufferHandler for WaylandState {
fn buffer_destroyed( fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
&mut self,
_buffer: &smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer,
) {
}
} }
impl ShmHandler for WaylandState { impl ShmHandler for WaylandState {
fn shm_state(&self) -> &smithay::wayland::shm::ShmState { fn shm_state(&self) -> &ShmState {
&self.shm_state &self.shm_state
} }
} }
impl DmabufHandler for WaylandState {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.dmabuf_state
}
fn dmabuf_imported(
&mut self,
_global: &DmabufGlobal,
_dmabuf: Dmabuf,
) -> Result<(), dmabuf::ImportError> {
Ok(())
}
}
delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState); delegate_shm!(WaylandState);
delegate_output!(WaylandState); delegate_output!(WaylandState);

View File

@@ -1,137 +1,118 @@
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState}; use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
use crate::{ use crate::{
core::{destroy_queue, registry::Registry}, core::{delta::Delta, destroy_queue, registry::Registry},
nodes::drawable::model::Model, nodes::drawable::model::ModelPart,
}; };
use mint::Vector2; use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use slog::Logger;
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
gles2::{Gles2Renderer, Gles2Texture}, gles::{GlesRenderer, GlesTexture},
utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData}, utils::{import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData},
Texture, Renderer, Texture,
}, },
desktop::utils::send_frames_surface_tree, desktop::utils::send_frames_surface_tree,
output::Output,
reexports::wayland_server::{ reexports::wayland_server::{
backend::ObjectId, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource, self, protocol::wl_surface::WlSurface, Display, DisplayHandle, Resource,
}, },
wayland::compositor::{self, SurfaceData}, wayland::compositor::{self, SurfaceData},
}; };
use std::sync::{Arc, Weak}; use std::{
ffi::c_void,
sync::{Arc, Weak},
time::Duration,
};
use stereokit::{ use stereokit::{
material::{Material, Transparency}, Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
shader::Shader, Transparency,
texture::{Texture as SKTexture, TextureAddress, TextureFormat, TextureSample, TextureType},
StereoKit,
}; };
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new(); pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
pub struct CoreSurfaceData { pub struct CoreSurfaceData {
wl_tex: Option<SendWrapper<Gles2Texture>>, wl_tex: Option<SendWrapper<GlesTexture>>,
sk_tex: Option<SendWrapper<SKTexture>>,
sk_mat: Option<Arc<SendWrapper<Material>>>,
pub size: Vector2<u32>, 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 { impl Drop for CoreSurfaceData {
fn drop(&mut self) { fn drop(&mut self) {
destroy_queue::add(self.wl_tex.take()); destroy_queue::add(self.wl_tex.take());
destroy_queue::add(self.sk_tex.take());
destroy_queue::add(self.sk_mat.take());
} }
} }
pub struct CoreSurface { pub struct CoreSurface {
display: Weak<Mutex<Display<WaylandState>>>, display: Weak<Mutex<Display<WaylandState>>>,
pub state: Weak<Mutex<WaylandState>>,
pub dh: DisplayHandle, pub dh: DisplayHandle,
pub surface_id: ObjectId, pub weak_surface: wayland_server::Weak<WlSurface>,
pub mapped_data: Mutex<Option<CoreSurfaceData>>, mapped_data: Mutex<Option<CoreSurfaceData>>,
pub pending_material_applications: Mutex<Vec<(Arc<Model>, u32)>>, sk_tex: OnceCell<SendWrapper<Tex>>,
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
material_offset: Mutex<Delta<u32>>,
on_commit: Box<dyn Fn(u32) + Send + Sync>,
pub pending_material_applications: Registry<ModelPart>,
} }
impl CoreSurface { impl CoreSurface {
pub fn new( pub fn add_to(
state: &Arc<Mutex<WaylandState>>,
display: &Arc<Mutex<Display<WaylandState>>>, display: &Arc<Mutex<Display<WaylandState>>>,
dh: DisplayHandle, dh: DisplayHandle,
surface: &WlSurface, surface: &WlSurface,
) -> Arc<Self> { on_commit: impl Fn(u32) + Send + Sync + 'static,
CORE_SURFACES.add(CoreSurface { ) {
display: Arc::downgrade(display), compositor::with_states(surface, |data| {
state: Arc::downgrade(state), data.data_map.insert_if_missing_threadsafe(|| {
dh, CORE_SURFACES.add(CoreSurface {
surface_id: surface.id(), display: Arc::downgrade(display),
mapped_data: Mutex::new(None), dh,
pending_material_applications: Mutex::new(Vec::new()), 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_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)>( pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
&self, let Some(wl_surface) = self.wl_surface() else { return };
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();
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 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 data.data_map
.get::<RendererSurfaceStateUserData>() .get::<RendererSurfaceStateUserData>()
.map(|surface_states| surface_states.borrow().wl_buffer().is_some()) .map(|surface_states| surface_states.borrow().buffer().is_some())
.unwrap_or(false) .unwrap_or(false)
}); });
@@ -140,68 +121,90 @@ impl CoreSurface {
} }
let mut mapped_data = self.mapped_data.lock(); 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_states(|data| {
self.with_data(|mapped_data| { // let just_mapped = mapped_data.is_none();
mapped_data.update_tex( // if just_mapped {
data.data_map.get::<RendererSurfaceStateUserData>().unwrap(), let renderer_surface_state = data
renderer, .data_map
.get::<RendererSurfaceStateUserData>()
.unwrap()
.borrow();
let smithay_tex = renderer_surface_state
.texture::<GlesRenderer>(renderer.id())
.unwrap()
.clone();
let sk_tex = self.sk_tex.get().unwrap();
let sk_mat = self.sk_mat.get().unwrap();
unsafe {
sk.tex_set_surface(
sk_tex.as_ref(),
smithay_tex.tex_id() as usize as *mut c_void,
TextureType::IMAGE_NO_MIPS,
smithay::backend::renderer::gles::ffi::RGBA8.into(),
smithay_tex.width() as i32,
smithay_tex.height() as i32,
1,
false,
); );
}); sk.tex_set_sample(sk_tex.as_ref(), TextureSample::Point);
self.apply_surface_materials(); sk.tex_set_address(sk_tex.as_ref(), TextureAddress::Clamp);
}
if just_mapped { if let Some(material_offset) = self.material_offset.lock().delta() {
on_mapped(data); 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);
});
self.apply_surface_materials();
} }
pub fn apply_material(&self, model: Arc<Model>, material_idx: u32) { pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
self.pending_material_applications let Some(wl_surface) = self.wl_surface() else { return };
.lock()
.push((model, material_idx)); send_frames_surface_tree(
&wl_surface,
&output,
Duration::from_secs_f64(sk.time_get()),
None,
|_, _| Some(output.clone()),
);
}
pub fn set_material_offset(&self, material_offset: u32) {
*self.material_offset.lock().value_mut() = material_offset;
}
pub fn apply_material(&self, model_node: &Arc<ModelPart>) {
self.pending_material_applications.add_raw(model_node)
} }
fn apply_surface_materials(&self) { fn apply_surface_materials(&self) {
self.with_data(|mapped_data| { for model_node in self.pending_material_applications.get_valid_contents() {
let mut pending_material_applications = self.pending_material_applications.lock(); model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
for (model, material_idx) in &*pending_material_applications { }
model self.pending_material_applications.clear();
.pending_material_replacements
.lock()
.insert(*material_idx, mapped_data.sk_mat.clone().unwrap());
}
pending_material_applications.clear();
});
} }
pub fn wayland_state(&self) -> Arc<Mutex<WaylandState>> { pub fn wl_surface(&self) -> Option<WlSurface> {
self.state.upgrade().unwrap() self.weak_surface.upgrade().ok()
} }
pub fn wl_surface(&self) -> WlSurface { pub fn with_states<F, T>(&self, f: F) -> Option<T>
WlSurface::from_id(&self.dh, self.surface_id.clone()).unwrap()
}
pub fn with_states<F, T>(&self, f: F) -> T
where where
F: FnOnce(&SurfaceData) -> T, 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> pub fn size(&self) -> Option<Vector2<u32>> {
where self.mapped_data.lock().as_ref().map(|d| d.size)
F: FnOnce(&mut CoreSurfaceData) -> T,
{
self.mapped_data.lock().as_mut().map(f)
} }
pub fn flush_clients(&self) { pub fn flush_clients(&self) {
@@ -216,5 +219,8 @@ impl CoreSurface {
impl Drop for CoreSurface { impl Drop for CoreSurface {
fn drop(&mut self) { fn drop(&mut self) {
CORE_SURFACES.remove(self); CORE_SURFACES.remove(self);
destroy_queue::add(self.sk_tex.take());
destroy_queue::add(self.sk_mat.take());
} }
} }

View File

@@ -1,69 +1,669 @@
use std::sync::Arc;
use crate::nodes::Node; use crate::nodes::Node;
use super::{panel_item::PanelItem, state::WaylandState, surface::CoreSurface}; use super::{
use smithay::{ panel_item::{PanelItem, RecommendedState, SurfaceID},
delegate_xdg_shell, state::WaylandState,
reexports::{ surface::CoreSurface,
wayland_protocols::xdg::{ SERIAL_COUNTER,
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, };
shell::server::xdg_toplevel::State, use mint::Vector2;
}, use nanoid::nanoid;
wayland_server::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, use parking_lot::Mutex;
use serde::{ser::SerializeSeq, Serialize, Serializer};
use smithay::reexports::{
wayland_protocols::xdg::shell::server::{
xdg_popup::{self, XdgPopup},
xdg_positioner::{self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner},
xdg_surface::{self, XdgSurface},
xdg_toplevel::{self, XdgToplevel, EVT_WM_CAPABILITIES_SINCE},
xdg_wm_base::{self, XdgWmBase},
}, },
utils::Serial, wayland_server::{
wayland::{ backend::{ClientId, ObjectId},
compositor, protocol::wl_surface::WlSurface,
shell::xdg::{ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, Weak as WlWeak,
XdgShellState,
},
}, },
}; };
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
use tracing::{debug, warn};
impl XdgShellHandler for WaylandState { impl GlobalDispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn xdg_shell_state(&mut self) -> &mut XdgShellState { fn bind(
&mut self.xdg_shell_state _state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<XdgWmBase>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
} }
}
fn new_toplevel(&mut self, surface: ToplevelSurface) { impl Dispatch<XdgWmBase, (), WaylandState> for WaylandState {
self.output fn request(
.enter(&self.display_handle, surface.wl_surface()); _state: &mut WaylandState,
surface.with_pending_state(|state| { _client: &Client,
state.states.set(State::Maximized); _resource: &XdgWmBase,
state.states.set(State::Activated); request: xdg_wm_base::Request,
state.decoration_mode = Some(Mode::ServerSide); _data: &(),
}); _dhandle: &DisplayHandle,
surface.send_configure(); data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_wm_base::Request::CreatePositioner { id } => {
let positioner = data_init.init(id, Mutex::new(PositionerData::default()));
debug!(?positioner, "Create XDG positioner");
}
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
let xdg_surface = data_init.init(id, Mutex::new(XdgSurfaceData::new(&surface)));
debug!(?xdg_surface, "Create XDG surface");
}
xdg_wm_base::Request::Pong { serial } => {
debug!(serial, "Client pong");
}
xdg_wm_base::Request::Destroy => {
debug!("Destroy XDG WM base");
}
_ => unreachable!(),
}
} }
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { }
match configure {
Configure::Toplevel(config) => { #[derive(Debug, Serialize, Clone, Copy)]
if let Some(size) = config.state.size { pub struct PositionerData {
compositor::with_states(&surface, |data| { size: Vector2<u32>,
if let Some(panel_node) = data.data_map.get::<Arc<Node>>() { anchor_rect_pos: Vector2<i32>,
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() { anchor_rect_size: Vector2<u32>,
let panel_item = PanelItem::from_node(panel_node); anchor: u32,
let has_data = core_surface gravity: u32,
.with_data(|data| { constraint_adjustment: u32,
data.size.x = size.w as u32; offset: Vector2<i32>,
data.size.y = size.h as u32; reactive: bool,
}) }
.is_some(); impl Default for PositionerData {
if has_data { fn default() -> Self {
panel_item.resize(); Self {
} size: Vector2::from([0; 2]),
} anchor_rect_pos: Vector2::from([0; 2]),
} anchor_rect_size: Vector2::from([0; 2]),
}) anchor: Anchor::None as u32,
gravity: Gravity::None as u32,
constraint_adjustment: ConstraintAdjustment::None.bits(),
offset: Vector2::from([0; 2]),
reactive: false,
}
}
}
impl Dispatch<XdgPositioner, Mutex<PositionerData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
positioner: &XdgPositioner,
request: xdg_positioner::Request,
data: &Mutex<PositionerData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_positioner::Request::SetSize { width, height } => {
debug!(?positioner, width, height, "Set positioner size");
data.lock().size = Vector2::from([width as u32, height as u32]);
}
xdg_positioner::Request::SetAnchorRect {
x,
y,
width,
height,
} => {
if width < 1 || height < 1 {
positioner.post_error(
xdg_positioner::Error::InvalidInput,
"Invalid size for positioner's anchor rectangle.",
);
warn!(
?positioner,
width, height, "Invalid size for positioner's anchor rectangle"
);
return;
}
debug!(
?positioner,
x, y, width, height, "Set positioner anchor rectangle"
);
let mut data = data.lock();
data.anchor_rect_pos = [x, y].into();
data.anchor_rect_size = [width as u32, height as u32].into();
}
xdg_positioner::Request::SetAnchor { anchor } => {
if let WEnum::Value(anchor) = anchor {
debug!(?positioner, ?anchor, "Set positioner anchor");
data.lock().anchor = anchor as u32;
} }
} }
Configure::Popup(_) => (), xdg_positioner::Request::SetGravity { gravity } => {
if let WEnum::Value(gravity) = gravity {
debug!(?positioner, ?gravity, "Set positioner gravity");
data.lock().gravity = gravity as u32;
}
}
xdg_positioner::Request::SetConstraintAdjustment {
constraint_adjustment,
} => {
debug!(
?positioner,
constraint_adjustment, "Set positioner constraint adjustment"
);
data.lock().constraint_adjustment = constraint_adjustment;
}
xdg_positioner::Request::SetOffset { x, y } => {
debug!(?positioner, x, y, "Set positioner offset");
data.lock().offset = [x, y].into();
}
xdg_positioner::Request::SetReactive => {
debug!(?positioner, "Set positioner reactive");
data.lock().reactive = true;
}
xdg_positioner::Request::SetParentSize {
parent_width,
parent_height,
} => {
debug!(
?positioner,
parent_width, parent_height, "Set positioner parent size"
);
}
xdg_positioner::Request::SetParentConfigure { serial } => {
debug!(?positioner, serial, "Set positioner parent size");
}
xdg_positioner::Request::Destroy => (),
_ => unreachable!(),
}
}
}
#[derive(Debug, Serialize, Clone, Copy)]
pub struct Geometry {
pub origin: Vector2<i32>,
pub size: Vector2<u32>,
}
impl Default for Geometry {
fn default() -> Self {
Self {
origin: Vector2::from([0; 2]),
size: Vector2::from([0; 2]),
}
}
}
pub struct XdgSurfaceData {
wl_surface: WlWeak<WlSurface>,
surface_id: SurfaceID,
panel_item: Weak<PanelItem>,
geometry: Option<Geometry>,
}
impl XdgSurfaceData {
pub fn new(wl_surface: &WlSurface) -> Self {
XdgSurfaceData {
wl_surface: wl_surface.downgrade(),
surface_id: SurfaceID::Toplevel,
panel_item: Weak::new(),
geometry: None,
}
}
pub fn get(xdg_surface: &XdgSurface) -> &Mutex<Self> {
xdg_surface.data::<Mutex<Self>>().unwrap()
}
pub fn wl_surface(&self) -> WlSurface {
self.wl_surface.upgrade().unwrap()
}
pub fn panel_item(&self) -> Option<Arc<PanelItem>> {
self.panel_item.upgrade()
}
}
// impl Clone for XdgSurfaceData {
// fn clone(&self) -> Self {
// Self {
// wl_surface: self.wl_surface.clone(),
// geometry: self.geometry.clone(),
// surface_type: Mutex::new(self.surface_type.lock().clone()),
// }
// }
// }
impl Dispatch<XdgSurface, Mutex<XdgSurfaceData>, WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
client: &Client,
xdg_surface: &XdgSurface,
request: xdg_surface::Request,
xdg_surface_data: &Mutex<XdgSurfaceData>,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_surface::Request::GetToplevel { id } => {
let toplevel_state = Mutex::new(ToplevelData::new(xdg_surface));
let toplevel = data_init.init(id, toplevel_state);
debug!(?toplevel, ?xdg_surface, "Create XDG toplevel");
if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE {
toplevel.wm_capabilities(vec![]);
}
toplevel.configure(0, 0, vec![]);
xdg_surface.configure(SERIAL_COUNTER.inc());
let client_credentials = client.get_credentials(&state.display_handle).ok();
let seat_data = state.seats.get(&client.id()).unwrap().clone();
let toplevel_weak = toplevel.downgrade();
CoreSurface::add_to(
&state.display,
state.display_handle.clone(),
&xdg_surface_data.lock().wl_surface(),
move |c| match c {
0 => {
let toplevel = toplevel_weak.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let xdg_surface = toplevel_data.lock().xdg_surface();
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface);
let wl_surface = xdg_surface_data.lock().wl_surface();
xdg_surface_data.lock().surface_id = SurfaceID::Toplevel;
let (node, panel_item) = PanelItem::create(
toplevel.clone(),
wl_surface.clone(),
client_credentials,
seat_data.clone(),
);
toplevel_data.lock().panel_item_node.replace(node);
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
}
_ => {
let toplevel = toplevel_weak.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let panel_item = toplevel_data.lock().panel_item().unwrap();
panel_item.commit_toplevel();
}
},
);
}
xdg_surface::Request::GetPopup {
id,
parent,
positioner,
} => {
let parent_clone = parent.clone().unwrap();
let parent_data = parent_clone.data::<Mutex<XdgSurfaceData>>().unwrap().lock();
// let positioner_data = positioner
// .data::<Mutex<PositionerData>>()
// .unwrap()
// .lock()
// .clone();
// let parent = match &*parent_data {
// XdgSurfaceType::Toplevel(_) => SurfaceID::Toplevel,
// XdgSurfaceType::Popup(p) => {
// SurfaceID::Popup(p.upgrade().unwrap().uid.clone())
// }
// XdgSurfaceType::Unknown => return,
// };
let uid = nanoid!();
let popup_data = Mutex::new(PopupData::new(
uid.clone(),
xdg_surface,
parent_data.surface_id.clone(),
positioner,
));
let xdg_popup = data_init.init(id, popup_data);
xdg_surface_data.lock().surface_id = SurfaceID::Popup(uid);
let panel_item = parent_data.panel_item().unwrap();
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
panel_item.seat_data.new_surface(
&xdg_surface_data.lock().wl_surface(),
Arc::downgrade(&panel_item),
);
debug!(?xdg_popup, ?xdg_surface, "Create XDG popup");
let xdg_surface = xdg_surface.downgrade();
let xdg_popup = xdg_popup.downgrade();
CoreSurface::add_to(
&state.display,
state.display_handle.clone(),
&xdg_surface_data.lock().wl_surface.upgrade().unwrap(),
move |commit_count| match commit_count {
0 => xdg_surface
.upgrade()
.unwrap()
.configure(SERIAL_COUNTER.inc()),
c => {
let xdg_popup = xdg_popup.upgrade().unwrap();
let popup_data = PopupData::get(&xdg_popup);
let popup_data = popup_data.lock();
// panel_item.commit_popup(popup_data);
if c == 1 {
panel_item.new_popup(&xdg_popup, &*popup_data);
}
}
},
);
}
xdg_surface::Request::SetWindowGeometry {
x,
y,
width,
height,
} => {
debug!(
?xdg_surface,
x, y, width, height, "Set XDG surface geometry"
);
let geometry = Geometry {
origin: [x, y].into(),
size: [width as u32, height as u32].into(),
};
xdg_surface_data.lock().geometry.replace(geometry);
}
xdg_surface::Request::AckConfigure { serial } => {
debug!(?xdg_surface, serial, "Acknowledge XDG surface configure");
}
xdg_surface::Request::Destroy => {
debug!(?xdg_surface, "Destroy XDG surface");
}
_ => unreachable!(),
}
}
}
fn serde_error<S: Serializer>(msg: &str) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom(msg))
}
#[derive(Debug, Clone)]
pub struct ToplevelData {
panel_item_node: Option<Arc<Node>>,
xdg_surface: WlWeak<XdgSurface>,
parent: Option<WlWeak<XdgToplevel>>,
title: Option<String>,
app_id: Option<String>,
max_size: Option<Vector2<u32>>,
min_size: Option<Vector2<u32>>,
states: Vec<u32>,
}
impl ToplevelData {
fn new(xdg_surface: &XdgSurface) -> Self {
ToplevelData {
panel_item_node: None,
xdg_surface: xdg_surface.downgrade(),
parent: None,
title: None,
app_id: None,
max_size: None,
min_size: None,
states: Vec::new(),
} }
} }
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {} pub fn get(toplevel: &XdgToplevel) -> &Mutex<ToplevelData> {
fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {} toplevel.data::<Mutex<ToplevelData>>().unwrap()
}
pub fn xdg_surface(&self) -> XdgSurface {
self.xdg_surface.upgrade().unwrap()
}
fn panel_item(&self) -> Option<Arc<PanelItem>> {
let xdg_surface = self.xdg_surface();
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
xdg_surface_data.panel_item()
}
}
impl Serialize for ToplevelData {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let xdg_surface = self.xdg_surface();
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface).lock();
let geometry = xdg_surface_data.geometry.clone();
let wl_surface = xdg_surface_data.wl_surface();
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return serde_error::<S>("Core surface not found")};
let Some(size) = core_surface.size() else {return serializer.serialize_none()};
let geometry = geometry.unwrap_or_else(|| Geometry {
origin: [0; 2].into(),
size,
});
let mut seq = serializer.serialize_seq(None)?;
// Parent UID
seq.serialize_element(&self.parent.as_ref().and_then(|p| {
Some(
ToplevelData::get(&p.upgrade().ok()?)
.lock()
.panel_item()?
.uid
.clone(),
)
}))?;
seq.serialize_element(&self.title)?;
seq.serialize_element(&self.app_id)?;
seq.serialize_element(&size)?;
seq.serialize_element(&self.min_size)?;
seq.serialize_element(&self.max_size)?;
seq.serialize_element(&geometry)?;
seq.serialize_element(&self.states)?;
seq.end()
}
}
impl Dispatch<XdgToplevel, Mutex<ToplevelData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_toplevel: &XdgToplevel,
request: xdg_toplevel::Request,
data: &Mutex<ToplevelData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_toplevel::Request::SetParent { parent } => {
debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent");
data.lock().parent = parent.map(|toplevel| toplevel.downgrade());
}
xdg_toplevel::Request::SetTitle { title } => {
debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title");
data.lock().title = (!title.is_empty()).then_some(title);
}
xdg_toplevel::Request::SetAppId { app_id } => {
debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID");
data.lock().app_id = (!app_id.is_empty()).then_some(app_id);
}
xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => {
debug!(
?xdg_toplevel,
?seat,
serial,
x,
y,
"Show XDG Toplevel window menu"
);
}
xdg_toplevel::Request::Move { seat, serial } => {
debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request");
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Move);
}
xdg_toplevel::Request::Resize {
seat,
serial,
edges,
} => {
let WEnum::Value(edges) = edges else { return };
debug!(
?xdg_toplevel,
?seat,
serial,
?edges,
"XDG Toplevel resize request"
);
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Resize(edges as u32));
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size");
data.lock().max_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMinSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size");
data.lock().min_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMaximized => {
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Maximize(true));
}
xdg_toplevel::Request::UnsetMaximized => {
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Maximize(false));
}
xdg_toplevel::Request::SetFullscreen { output: _ } => {
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
xdg_toplevel::Request::UnsetFullscreen => {
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
xdg_toplevel::Request::SetMinimized => {
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.recommend_toplevel_state(RecommendedState::Minimize);
}
xdg_toplevel::Request::Destroy => {
debug!(?xdg_toplevel, "Destroy XDG Toplevel");
let Some(panel_item) = data.lock().panel_item() else { return };
panel_item.on_drop();
}
_ => unreachable!(),
}
}
}
#[derive(Clone)]
pub struct PopupData {
pub uid: String,
grabbed: bool,
parent_id: SurfaceID,
positioner: XdgPositioner,
xdg_surface: WlWeak<XdgSurface>,
}
impl PopupData {
fn new(
uid: impl ToString,
xdg_surface: &XdgSurface,
parent_id: SurfaceID,
positioner: XdgPositioner,
) -> Self {
PopupData {
uid: uid.to_string(),
grabbed: false,
parent_id,
positioner,
xdg_surface: xdg_surface.downgrade(),
}
}
pub fn get(popup: &XdgPopup) -> &Mutex<Self> {
popup.data::<Mutex<Self>>().unwrap()
}
pub fn xdg_surface(&self) -> XdgSurface {
self.xdg_surface.upgrade().unwrap()
}
fn panel_item(&self) -> Option<Arc<PanelItem>> {
XdgSurfaceData::get(&self.xdg_surface()).lock().panel_item()
}
// fn get_parent(&self) -> Option<XdgSurface> {
// self.parent.as_ref()?.upgrade().ok()
// }
pub fn wl_surface(&self) -> WlSurface {
XdgSurfaceData::get(&self.xdg_surface()).lock().wl_surface()
}
pub fn positioner_data(&self) -> Option<PositionerData> {
Some(
self.positioner
.data::<Mutex<PositionerData>>()?
.lock()
.clone(),
)
}
}
impl Serialize for PopupData {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let Some(positioner_data) = self.positioner_data() else {return serde_error::<S>("Positioner not found")};
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&self.uid)?;
seq.serialize_element(&self.parent_id)?;
seq.serialize_element(&positioner_data)?;
seq.end()
}
}
impl Debug for PopupData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XdgPopupData")
.field("uid", &self.uid)
.field("positioner", &self.positioner)
.field("xdg_surface", &self.xdg_surface)
.finish()
}
}
impl Dispatch<XdgPopup, Mutex<PopupData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_popup: &XdgPopup,
request: xdg_popup::Request,
data: &Mutex<PopupData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_popup::Request::Grab { seat, serial } => {
let mut data = data.lock();
data.grabbed = true;
debug!(?xdg_popup, ?seat, serial, "XDG popup grab");
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(Some(SurfaceID::Popup(data.uid.clone())));
}
xdg_popup::Request::Reposition { positioner, token } => {
let mut data = data.lock();
debug!(?xdg_popup, ?positioner, token, "XDG popup reposition");
data.positioner = positioner;
let Some(panel_item) = data.panel_item() else {return};
panel_item.reposition_popup(&*data);
// xdg_popup.popup_done(); // temporary hack to avoid apps locking up before popups are implemented
}
xdg_popup::Request::Destroy => {
let data = data.lock();
debug!(?xdg_popup, "Destroy XDG popup");
if data.grabbed {
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(None);
}
}
_ => unreachable!(),
}
}
fn destroyed(
_state: &mut WaylandState,
_client: ClientId,
_resource: ObjectId,
data: &Mutex<PopupData>,
) {
let data = data.lock();
let Some(panel_item) = data.panel_item() else {return};
panel_item.drop_popup(&data.uid);
}
} }
delegate_xdg_shell!(WaylandState);

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