From 6164d3524c3628b9d7f00f8b57c68a7cc8eb698d Mon Sep 17 00:00:00 2001 From: Zicklag Date: Mon, 21 Aug 2023 22:42:54 +0000 Subject: [PATCH] feat!: major refactor to make bones independent of bevy with the new `bones_framework`. (#132) Co-authored-by: Jacob LeCoq --- .github/bors.toml | 10 +- .github/workflows/ci.yml | 16 + .github/workflows/docs.yml | 6 +- .vscode/launch.json | 535 ++++ .vscode/settings.json | 1 - Cargo.lock | 2289 ++++++++++++----- Cargo.toml | 42 +- book/book.toml | 15 - book/src/SUMMARY.md | 12 - book/src/api_documentation.md | 3 - book/src/code_of_conduct.md | 1 - book/src/ecs_tutorial/index.md | 257 -- book/src/introduction.md | 1 - crates/bones_asset/Cargo.toml | 21 - crates/bones_asset/src/lib.rs | 508 ---- crates/bones_bevy_asset/CHANGELOG.md | 179 -- crates/bones_bevy_asset/Cargo.toml | 29 - crates/bones_bevy_asset/assets/game.meta.yaml | 9 - .../assets/players/player1.player.yaml | 1 - .../assets/players/player2.player.json | 3 - crates/bones_bevy_asset/examples/derive.rs | 81 - crates/bones_bevy_asset/macros/CHANGELOG.md | 71 - crates/bones_bevy_asset/macros/Cargo.toml | 19 - crates/bones_bevy_asset/macros/src/lib.rs | 259 -- crates/bones_bevy_asset/src/lib.rs | 211 -- crates/bones_bevy_renderer/Cargo.toml | 30 - .../examples/debug_rendering.rs | 93 - crates/bones_bevy_renderer/src/asset.rs | 60 - crates/bones_bevy_renderer/src/lib.rs | 600 ----- crates/bones_bevy_utils/CHANGELOG.md | 112 - crates/bones_bevy_utils/Cargo.toml | 12 - crates/bones_bevy_utils/src/lib.rs | 54 - crates/bones_ecs/Cargo.toml | 39 - crates/bones_ecs/src/components.rs | 168 -- crates/bones_ecs/src/components/typed.rs | 343 --- crates/bones_ecs/src/components/typed/ops.rs | 163 -- crates/bones_ecs/src/components/untyped.rs | 392 --- crates/bones_ecs/src/lib.rs | 171 -- crates/bones_ecs/src/resources.rs | 336 --- crates/bones_ecs/src/ulid.rs | 13 - crates/bones_input/CHANGELOG.md | 132 - crates/bones_input/Cargo.toml | 16 - crates/bones_input/src/lib.rs | 24 - crates/bones_matchmaker/Cargo.toml | 36 - crates/bones_matchmaker_proto/Cargo.toml | 11 - crates/bones_render/CHANGELOG.md | 195 -- crates/bones_render/Cargo.toml | 27 - crates/bones_render/src/lib.rs | 59 - crates/quinn_runtime_bevy/Cargo.toml | 18 - crates/type_ulid/Cargo.toml | 17 - crates/type_ulid/macros/Cargo.toml | 17 - demos/assets_minimal/Cargo.toml | 10 + demos/assets_minimal/assets/game.yaml | 3 + demos/assets_minimal/assets/pack.yaml | 3 + demos/assets_minimal/src/main.rs | 52 + demos/features/Cargo.toml | 10 + demos/features/assets/atlas/atlas-demo.yaml | 17 + .../features/assets/atlas/seagull.atlas.yaml | 4 + demos/features/assets/atlas/seagull.png | Bin 0 -> 87707 bytes demos/features/assets/game.yaml | 4 + .../features/assets/locales/en-US/locale.yaml | 3 + demos/features/assets/locales/en-US/menu.ftl | 6 + demos/features/assets/localization.yaml | 2 + demos/features/assets/pack.yaml | 1 + demos/features/assets/sprite/fractal.png | Bin 0 -> 34581 bytes demos/features/assets/tilemap/tilemap.yaml | 27 + .../assets/tilemap/tileset.atlas.yaml | 4 + demos/features/assets/tilemap/tileset.png | Bin 0 -> 17653 bytes demos/features/src/main.rs | 355 +++ demos/hello_world/Cargo.toml | 10 + demos/hello_world/src/main.rs | 25 + deny.toml | 41 +- .../bones_asset/CHANGELOG.md | 0 framework_crates/bones_asset/Cargo.toml | 37 + .../bones_asset/examples/assets/pack.yaml | 2 + .../examples/assets/players/jane/avatar.png | Bin 0 -> 38787 bytes .../assets/players/jane/jane.atlas.yaml | 8 + .../assets/players/jane/jane.player.yaml | 20 + .../examples/assets/players/john/avatar.png | Bin 0 -> 34581 bytes .../assets/players/john/john.atlas.yaml | 8 + .../assets/players/john/john.player.yaml | 12 + .../examples/assets/root.game.yaml | 11 + .../examples/packs/pack1/pack.yaml | 24 + .../examples/packs/pack1/pack1.plugin.yaml | 3 + .../examples/packs/pack2/pack.yaml | 11 + .../examples/packs/pack2/pack2.plugin.yaml | 2 + .../bones_asset/examples/tutorial.rs | 209 ++ framework_crates/bones_asset/src/asset.rs | 343 +++ framework_crates/bones_asset/src/cid.rs | 31 + framework_crates/bones_asset/src/handle.rs | 178 ++ framework_crates/bones_asset/src/io.rs | 201 ++ framework_crates/bones_asset/src/lib.rs | 30 + framework_crates/bones_asset/src/parse.rs | 71 + framework_crates/bones_asset/src/path.rs | 46 + framework_crates/bones_asset/src/server.rs | 898 +++++++ .../bones_bevy_renderer/CHANGELOG.md | 0 .../bones_bevy_renderer/Cargo.toml | 27 + .../bones_bevy_renderer/src/convert.rs | 243 ++ .../bones_bevy_renderer/src/lib.rs | 708 +++++ .../bones_ecs/CHANGELOG.md | 0 framework_crates/bones_ecs/Cargo.toml | 34 + .../bones_ecs/LICENSE | 0 .../bones_ecs/README.md | 0 .../bones_ecs/examples/pos_vel.rs | 10 +- .../bones_ecs/src/bitset.rs | 2 +- framework_crates/bones_ecs/src/components.rs | 172 ++ .../bones_ecs/src/components/iterator.rs | 97 +- .../bones_ecs/src/components/typed.rs | 183 ++ .../bones_ecs/src/components/untyped.rs | 593 +++++ .../bones_ecs/src/entities.rs | 31 +- .../bones_ecs/src/error.rs | 7 - framework_crates/bones_ecs/src/lib.rs | 102 + framework_crates/bones_ecs/src/resources.rs | 301 +++ .../bones_ecs/src/stage.rs | 50 +- .../bones_ecs/src/system.rs | 158 +- .../bones_ecs/src/world.rs | 104 +- framework_crates/bones_framework/CHANGELOG.md | 0 framework_crates/bones_framework/Cargo.toml | 79 + .../bones_framework/src}/animation.rs | 66 +- .../bones_framework/src}/camera.rs | 20 +- framework_crates/bones_framework/src/input.rs | 22 + .../bones_framework/src/input/gamepad.rs | 2 + .../bones_framework/src/input/keyboard.rs | 381 +++ .../bones_framework/src/input/mouse.rs | 58 + .../bones_framework/src/input}/time.rs | 16 +- .../src/input}/time/stopwatch.rs | 20 +- .../bones_framework/src/input}/time/timer.rs | 44 +- .../bones_framework/src/input/window.rs | 11 + framework_crates/bones_framework/src/lib.rs | 66 + .../bones_framework/src/localization.rs | 265 ++ .../bones_framework/src/params.rs | 26 + .../bones_framework/src/render.rs | 47 + .../bones_framework/src/render}/audio.rs | 8 +- .../bones_framework/src/render}/camera.rs | 30 +- .../bones_framework/src/render}/color.rs | 9 +- .../bones_framework/src/render}/line.rs | 4 +- .../bones_framework/src/render}/sprite.rs | 55 +- .../bones_framework/src/render}/tilemap.rs | 55 +- .../bones_framework/src/render}/transform.rs | 3 +- .../bones_framework/src/render/ui.rs | 29 + framework_crates/bones_lib/Cargo.toml | 19 + framework_crates/bones_lib/src/lib.rs | 347 +++ framework_crates/bones_schema/Cargo.toml | 36 + .../bones_schema/examples/custom_type_data.rs | 42 + .../bones_schema/examples/schema_derive.rs | 46 + .../bones_schema/macros/Cargo.toml | 19 + .../bones_schema/macros/src/lib.rs | 311 +++ framework_crates/bones_schema/src/alloc.rs | 16 + .../bones_schema/src/alloc/layout.rs | 86 + .../bones_schema/src/alloc/map.rs | 672 +++++ .../bones_schema/src/alloc/resizable.rs | 344 +++ .../bones_schema/src/alloc/type_set.rs | 26 + .../bones_schema/src/alloc/vec.rs | 684 +++++ framework_crates/bones_schema/src/lib.rs | 43 + framework_crates/bones_schema/src/ptr.rs | 933 +++++++ framework_crates/bones_schema/src/raw_fns.rs | 102 + framework_crates/bones_schema/src/registry.rs | 160 ++ framework_crates/bones_schema/src/schema.rs | 406 +++ framework_crates/bones_schema/src/ser_de.rs | 62 + .../bones_schema/src/std_impls.rs | 289 +++ framework_crates/bones_schema/tests/tests.rs | 316 +++ .../bones_schema/tests/trybuild.rs | 6 + .../tests/trybuild_fail/schema_ptr.rs | 38 + .../tests/trybuild_fail/schema_ptr.stderr | 11 + framework_crates/bones_utils/Cargo.toml | 26 + .../bones_utils/macros/Cargo.toml | 18 + .../bones_utils/macros/src/lib.rs | 170 ++ .../bones_utils/src/collections.rs | 14 + framework_crates/bones_utils/src/default.rs | 30 + .../bones_utils/src/key_mod.rs | 2 +- .../bones_utils/src/labeled_id.rs | 192 ++ framework_crates/bones_utils/src/lib.rs | 49 + framework_crates/bones_utils/src/names.rs | 136 + framework_crates/bones_utils/src/ptr.rs | 29 + .../bones_matchmaker/CHANGELOG.md | 0 other_crates/bones_matchmaker/Cargo.toml | 32 + .../bones_matchmaker/README.md | 0 .../examples/matchmaker_client.rs | 0 .../bones_matchmaker/src/certs.rs | 0 .../bones_matchmaker/src/cli.rs | 0 .../bones_matchmaker/src/lib.rs | 0 .../bones_matchmaker/src/main.rs | 0 .../bones_matchmaker/src/matchmaker.rs | 0 .../bones_matchmaker/src/proxy.rs | 0 .../bones_matchmaker_proto/CHANGELOG.md | 0 .../bones_matchmaker_proto/Cargo.toml | 11 + .../bones_matchmaker_proto/README.md | 0 .../bones_matchmaker_proto/src/lib.rs | 0 .../quinn_runtime_bevy/CHANGELOG.md | 0 other_crates/quinn_runtime_bevy/Cargo.toml | 17 + .../quinn_runtime_bevy/README.md | 0 .../quinn_runtime_bevy/src/lib.rs | 0 .../type_ulid/CHANGELOG.md | 0 other_crates/type_ulid/Cargo.toml | 17 + .../type_ulid/macros/CHANGELOG.md | 0 other_crates/type_ulid/macros/Cargo.toml | 17 + .../type_ulid/macros/src/lib.rs | 0 {crates => other_crates}/type_ulid/src/lib.rs | 0 rust-toolchain | 2 +- src/lib.rs | 50 - taplo.toml | 3 + 201 files changed, 14490 insertions(+), 5916 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 book/book.toml delete mode 100644 book/src/SUMMARY.md delete mode 100644 book/src/api_documentation.md delete mode 100644 book/src/code_of_conduct.md delete mode 100644 book/src/ecs_tutorial/index.md delete mode 100644 book/src/introduction.md delete mode 100644 crates/bones_asset/Cargo.toml delete mode 100644 crates/bones_asset/src/lib.rs delete mode 100644 crates/bones_bevy_asset/CHANGELOG.md delete mode 100644 crates/bones_bevy_asset/Cargo.toml delete mode 100644 crates/bones_bevy_asset/assets/game.meta.yaml delete mode 100644 crates/bones_bevy_asset/assets/players/player1.player.yaml delete mode 100644 crates/bones_bevy_asset/assets/players/player2.player.json delete mode 100644 crates/bones_bevy_asset/examples/derive.rs delete mode 100644 crates/bones_bevy_asset/macros/CHANGELOG.md delete mode 100644 crates/bones_bevy_asset/macros/Cargo.toml delete mode 100644 crates/bones_bevy_asset/macros/src/lib.rs delete mode 100644 crates/bones_bevy_asset/src/lib.rs delete mode 100644 crates/bones_bevy_renderer/Cargo.toml delete mode 100644 crates/bones_bevy_renderer/examples/debug_rendering.rs delete mode 100644 crates/bones_bevy_renderer/src/asset.rs delete mode 100644 crates/bones_bevy_renderer/src/lib.rs delete mode 100644 crates/bones_bevy_utils/CHANGELOG.md delete mode 100644 crates/bones_bevy_utils/Cargo.toml delete mode 100644 crates/bones_bevy_utils/src/lib.rs delete mode 100644 crates/bones_ecs/Cargo.toml delete mode 100644 crates/bones_ecs/src/components.rs delete mode 100644 crates/bones_ecs/src/components/typed.rs delete mode 100644 crates/bones_ecs/src/components/typed/ops.rs delete mode 100644 crates/bones_ecs/src/components/untyped.rs delete mode 100644 crates/bones_ecs/src/lib.rs delete mode 100644 crates/bones_ecs/src/resources.rs delete mode 100644 crates/bones_ecs/src/ulid.rs delete mode 100644 crates/bones_input/CHANGELOG.md delete mode 100644 crates/bones_input/Cargo.toml delete mode 100644 crates/bones_input/src/lib.rs delete mode 100644 crates/bones_matchmaker/Cargo.toml delete mode 100644 crates/bones_matchmaker_proto/Cargo.toml delete mode 100644 crates/bones_render/CHANGELOG.md delete mode 100644 crates/bones_render/Cargo.toml delete mode 100644 crates/bones_render/src/lib.rs delete mode 100644 crates/quinn_runtime_bevy/Cargo.toml delete mode 100644 crates/type_ulid/Cargo.toml delete mode 100644 crates/type_ulid/macros/Cargo.toml create mode 100644 demos/assets_minimal/Cargo.toml create mode 100644 demos/assets_minimal/assets/game.yaml create mode 100644 demos/assets_minimal/assets/pack.yaml create mode 100644 demos/assets_minimal/src/main.rs create mode 100644 demos/features/Cargo.toml create mode 100644 demos/features/assets/atlas/atlas-demo.yaml create mode 100644 demos/features/assets/atlas/seagull.atlas.yaml create mode 100644 demos/features/assets/atlas/seagull.png create mode 100644 demos/features/assets/game.yaml create mode 100644 demos/features/assets/locales/en-US/locale.yaml create mode 100644 demos/features/assets/locales/en-US/menu.ftl create mode 100644 demos/features/assets/localization.yaml create mode 100644 demos/features/assets/pack.yaml create mode 100644 demos/features/assets/sprite/fractal.png create mode 100644 demos/features/assets/tilemap/tilemap.yaml create mode 100644 demos/features/assets/tilemap/tileset.atlas.yaml create mode 100644 demos/features/assets/tilemap/tileset.png create mode 100644 demos/features/src/main.rs create mode 100644 demos/hello_world/Cargo.toml create mode 100644 demos/hello_world/src/main.rs rename {crates => framework_crates}/bones_asset/CHANGELOG.md (100%) create mode 100644 framework_crates/bones_asset/Cargo.toml create mode 100644 framework_crates/bones_asset/examples/assets/pack.yaml create mode 100644 framework_crates/bones_asset/examples/assets/players/jane/avatar.png create mode 100644 framework_crates/bones_asset/examples/assets/players/jane/jane.atlas.yaml create mode 100644 framework_crates/bones_asset/examples/assets/players/jane/jane.player.yaml create mode 100644 framework_crates/bones_asset/examples/assets/players/john/avatar.png create mode 100644 framework_crates/bones_asset/examples/assets/players/john/john.atlas.yaml create mode 100644 framework_crates/bones_asset/examples/assets/players/john/john.player.yaml create mode 100644 framework_crates/bones_asset/examples/assets/root.game.yaml create mode 100644 framework_crates/bones_asset/examples/packs/pack1/pack.yaml create mode 100644 framework_crates/bones_asset/examples/packs/pack1/pack1.plugin.yaml create mode 100644 framework_crates/bones_asset/examples/packs/pack2/pack.yaml create mode 100644 framework_crates/bones_asset/examples/packs/pack2/pack2.plugin.yaml create mode 100644 framework_crates/bones_asset/examples/tutorial.rs create mode 100644 framework_crates/bones_asset/src/asset.rs create mode 100644 framework_crates/bones_asset/src/cid.rs create mode 100644 framework_crates/bones_asset/src/handle.rs create mode 100644 framework_crates/bones_asset/src/io.rs create mode 100644 framework_crates/bones_asset/src/lib.rs create mode 100644 framework_crates/bones_asset/src/parse.rs create mode 100644 framework_crates/bones_asset/src/path.rs create mode 100644 framework_crates/bones_asset/src/server.rs rename {crates => framework_crates}/bones_bevy_renderer/CHANGELOG.md (100%) create mode 100644 framework_crates/bones_bevy_renderer/Cargo.toml create mode 100644 framework_crates/bones_bevy_renderer/src/convert.rs create mode 100644 framework_crates/bones_bevy_renderer/src/lib.rs rename {crates => framework_crates}/bones_ecs/CHANGELOG.md (100%) create mode 100644 framework_crates/bones_ecs/Cargo.toml rename {crates => framework_crates}/bones_ecs/LICENSE (100%) rename {crates => framework_crates}/bones_ecs/README.md (100%) rename {crates => framework_crates}/bones_ecs/examples/pos_vel.rs (90%) rename {crates => framework_crates}/bones_ecs/src/bitset.rs (96%) create mode 100644 framework_crates/bones_ecs/src/components.rs rename {crates => framework_crates}/bones_ecs/src/components/iterator.rs (52%) create mode 100644 framework_crates/bones_ecs/src/components/typed.rs create mode 100644 framework_crates/bones_ecs/src/components/untyped.rs rename {crates => framework_crates}/bones_ecs/src/entities.rs (94%) rename {crates => framework_crates}/bones_ecs/src/error.rs (80%) create mode 100644 framework_crates/bones_ecs/src/lib.rs create mode 100644 framework_crates/bones_ecs/src/resources.rs rename {crates => framework_crates}/bones_ecs/src/stage.rs (84%) rename {crates => framework_crates}/bones_ecs/src/system.rs (72%) rename {crates => framework_crates}/bones_ecs/src/world.rs (79%) create mode 100644 framework_crates/bones_framework/CHANGELOG.md create mode 100644 framework_crates/bones_framework/Cargo.toml rename {src => framework_crates/bones_framework/src}/animation.rs (65%) rename {src => framework_crates/bones_framework/src}/camera.rs (94%) create mode 100644 framework_crates/bones_framework/src/input.rs create mode 100644 framework_crates/bones_framework/src/input/gamepad.rs create mode 100644 framework_crates/bones_framework/src/input/keyboard.rs create mode 100644 framework_crates/bones_framework/src/input/mouse.rs rename {crates/bones_input/src => framework_crates/bones_framework/src/input}/time.rs (97%) rename {crates/bones_input/src => framework_crates/bones_framework/src/input}/time/stopwatch.rs (93%) rename {crates/bones_input/src => framework_crates/bones_framework/src/input}/time/timer.rs (95%) create mode 100644 framework_crates/bones_framework/src/input/window.rs create mode 100644 framework_crates/bones_framework/src/lib.rs create mode 100644 framework_crates/bones_framework/src/localization.rs create mode 100644 framework_crates/bones_framework/src/params.rs create mode 100644 framework_crates/bones_framework/src/render.rs rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/audio.rs (88%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/camera.rs (60%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/color.rs (98%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/line.rs (92%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/sprite.rs (51%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/tilemap.rs (54%) rename {crates/bones_render/src => framework_crates/bones_framework/src/render}/transform.rs (93%) create mode 100644 framework_crates/bones_framework/src/render/ui.rs create mode 100644 framework_crates/bones_lib/Cargo.toml create mode 100644 framework_crates/bones_lib/src/lib.rs create mode 100644 framework_crates/bones_schema/Cargo.toml create mode 100644 framework_crates/bones_schema/examples/custom_type_data.rs create mode 100644 framework_crates/bones_schema/examples/schema_derive.rs create mode 100644 framework_crates/bones_schema/macros/Cargo.toml create mode 100644 framework_crates/bones_schema/macros/src/lib.rs create mode 100644 framework_crates/bones_schema/src/alloc.rs create mode 100644 framework_crates/bones_schema/src/alloc/layout.rs create mode 100644 framework_crates/bones_schema/src/alloc/map.rs create mode 100644 framework_crates/bones_schema/src/alloc/resizable.rs create mode 100644 framework_crates/bones_schema/src/alloc/type_set.rs create mode 100644 framework_crates/bones_schema/src/alloc/vec.rs create mode 100644 framework_crates/bones_schema/src/lib.rs create mode 100644 framework_crates/bones_schema/src/ptr.rs create mode 100644 framework_crates/bones_schema/src/raw_fns.rs create mode 100644 framework_crates/bones_schema/src/registry.rs create mode 100644 framework_crates/bones_schema/src/schema.rs create mode 100644 framework_crates/bones_schema/src/ser_de.rs create mode 100644 framework_crates/bones_schema/src/std_impls.rs create mode 100644 framework_crates/bones_schema/tests/tests.rs create mode 100644 framework_crates/bones_schema/tests/trybuild.rs create mode 100644 framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.rs create mode 100644 framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.stderr create mode 100644 framework_crates/bones_utils/Cargo.toml create mode 100644 framework_crates/bones_utils/macros/Cargo.toml create mode 100644 framework_crates/bones_utils/macros/src/lib.rs create mode 100644 framework_crates/bones_utils/src/collections.rs create mode 100644 framework_crates/bones_utils/src/default.rs rename crates/bones_render/src/datatypes.rs => framework_crates/bones_utils/src/key_mod.rs (98%) create mode 100644 framework_crates/bones_utils/src/labeled_id.rs create mode 100644 framework_crates/bones_utils/src/lib.rs create mode 100644 framework_crates/bones_utils/src/names.rs create mode 100644 framework_crates/bones_utils/src/ptr.rs rename {crates => other_crates}/bones_matchmaker/CHANGELOG.md (100%) create mode 100644 other_crates/bones_matchmaker/Cargo.toml rename {crates => other_crates}/bones_matchmaker/README.md (100%) rename {crates => other_crates}/bones_matchmaker/examples/matchmaker_client.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/certs.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/cli.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/lib.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/main.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/matchmaker.rs (100%) rename {crates => other_crates}/bones_matchmaker/src/proxy.rs (100%) rename {crates => other_crates}/bones_matchmaker_proto/CHANGELOG.md (100%) create mode 100644 other_crates/bones_matchmaker_proto/Cargo.toml rename {crates => other_crates}/bones_matchmaker_proto/README.md (100%) rename {crates => other_crates}/bones_matchmaker_proto/src/lib.rs (100%) rename {crates => other_crates}/quinn_runtime_bevy/CHANGELOG.md (100%) create mode 100644 other_crates/quinn_runtime_bevy/Cargo.toml rename {crates => other_crates}/quinn_runtime_bevy/README.md (100%) rename {crates => other_crates}/quinn_runtime_bevy/src/lib.rs (100%) rename {crates => other_crates}/type_ulid/CHANGELOG.md (100%) create mode 100644 other_crates/type_ulid/Cargo.toml rename {crates => other_crates}/type_ulid/macros/CHANGELOG.md (100%) create mode 100644 other_crates/type_ulid/macros/Cargo.toml rename {crates => other_crates}/type_ulid/macros/src/lib.rs (100%) rename {crates => other_crates}/type_ulid/src/lib.rs (100%) delete mode 100644 src/lib.rs create mode 100644 taplo.toml diff --git a/.github/bors.toml b/.github/bors.toml index 394b162f1c..d658d56e8a 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -1,9 +1,3 @@ use_squash_merge = true -cut_body_after = "_ _ _" -status = [ - "Lint commit message", - "Check Rust formatting", - "Clippy correctness checks (%)", - "Build Docs", - "Miri tests" -] +cut_body_after = "_ _ _" +status = ["Lint commit message", "Check Rust formatting", "Clippy correctness checks (%)", "Build Docs", "Miri tests"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e739b2656..edb5cca7b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,15 @@ jobs: command: clippy args: --target ${{ matrix.config.target }} -- -W clippy::correctness -D warnings + - name: ⚙️ Test + if: matrix.config.target != 'wasm32-unknown-unknown' + uses: actions-rs/cargo@v1 + env: + CARGO_TARGET_DIR: ${{ matrix.config.target_dir }} + with: + command: test + args: --workspace + minimal_deps_check: runs-on: ubuntu-latest name: 🔧 Check Minimal Dependency Versions @@ -87,6 +96,13 @@ jobs: - name: ⬇️ Checkout Source uses: actions/checkout@v3 + - name: 🧰 Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y -q \ + libasound2-dev \ + libudev-dev + - name: 🧰 Install Rust Nightly uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index efbb861ee8..e95c034adf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -46,12 +46,12 @@ jobs: - name: 🔨 Build Rustdoc run: | cargo doc --workspace --no-deps - mv target/doc book/src/rustdoc + mkdir -p target/doc-dist + mv target/doc target/doc-dist/rustdoc - name: 🚀 Deploy uses: JamesIves/github-pages-deploy-action@4.1.3 if: ${{ github.ref == 'refs/heads/main' }} with: branch: gh-pages - folder: ./book/dist - target-folder: book + folder: target/doc-dist diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..c96573fa8c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,535 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_asset'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_asset" + ], + "filter": { + "name": "bones_asset", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tutorial'", + "cargo": { + "args": [ + "build", + "--example=tutorial", + "--package=bones_asset" + ], + "filter": { + "name": "tutorial", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tutorial'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tutorial", + "--package=bones_asset" + ], + "filter": { + "name": "tutorial", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_schema'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--features", + "glam", + "--package=bones_schema" + ], + "filter": { + "name": "bones_schema", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'schema_derive'", + "cargo": { + "args": [ + "build", + "--example=schema_derive", + "--package=bones_schema" + ], + "filter": { + "name": "schema_derive", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'schema_derive'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=schema_derive", + "--package=bones_schema" + ], + "filter": { + "name": "schema_derive", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'custom_type_data'", + "cargo": { + "args": [ + "build", + "--example=custom_type_data", + "--package=bones_schema" + ], + "filter": { + "name": "custom_type_data", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'custom_type_data'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=custom_type_data", + "--package=bones_schema" + ], + "filter": { + "name": "custom_type_data", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'trybuild'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=trybuild", + "--package=bones_schema" + ], + "filter": { + "name": "trybuild", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'tests'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=tests", + "--package=bones_schema" + ], + "filter": { + "name": "tests", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_utils" + ], + "filter": { + "name": "bones_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_bevy_renderer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_bevy_renderer" + ], + "filter": { + "name": "bones_bevy_renderer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_framework'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_framework" + ], + "filter": { + "name": "bones_framework", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_lib'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_lib" + ], + "filter": { + "name": "bones_lib", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_ecs'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_ecs" + ], + "filter": { + "name": "bones_ecs", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'pos_vel'", + "cargo": { + "args": [ + "build", + "--example=pos_vel", + "--package=bones_ecs" + ], + "filter": { + "name": "pos_vel", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'pos_vel'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=pos_vel", + "--package=bones_ecs" + ], + "filter": { + "name": "pos_vel", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_bevy_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_bevy_utils" + ], + "filter": { + "name": "bones_bevy_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_matchmaker'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_matchmaker" + ], + "filter": { + "name": "bones_matchmaker", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'bones_matchmaker'", + "cargo": { + "args": [ + "build", + "--bin=bones_matchmaker", + "--package=bones_matchmaker" + ], + "filter": { + "name": "bones_matchmaker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'bones_matchmaker'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=bones_matchmaker", + "--package=bones_matchmaker" + ], + "filter": { + "name": "bones_matchmaker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'matchmaker_client'", + "cargo": { + "args": [ + "build", + "--example=matchmaker_client", + "--package=bones_matchmaker" + ], + "filter": { + "name": "matchmaker_client", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'matchmaker_client'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=matchmaker_client", + "--package=bones_matchmaker" + ], + "filter": { + "name": "matchmaker_client", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'bones_matchmaker_proto'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=bones_matchmaker_proto" + ], + "filter": { + "name": "bones_matchmaker_proto", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'quinn_runtime_bevy'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=quinn_runtime_bevy" + ], + "filter": { + "name": "quinn_runtime_bevy", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'type_ulid'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=type_ulid" + ], + "filter": { + "name": "type_ulid", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'simple_game'", + "cargo": { + "args": [ + "build", + "--bin=simple_game", + "--package=simple_game" + ], + "filter": { + "name": "simple_game", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}/demos/simple_game" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'simple_game'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=simple_game", + "--package=simple_game" + ], + "filter": { + "name": "simple_game", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index f852886a73..dc49b3714a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,6 @@ "bones_render", "bones_input", "bones_bevy_utils", - "bones_bevy_asset", "bones_bevy_renderer", "bones_has_load_progress", "bones_matchmaker", diff --git a/Cargo.lock b/Cargo.lock index d301ae3f8c..317748f57e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,26 +2,42 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "accesskit" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704d532b1cd3d912bb37499c55a81ac748cc1afa737eedd100ba441acdd47d38" +checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" [[package]] name = "accesskit_consumer" -version = "0.14.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba8b23cfca3944012ee2e5c71c02077a400e034c720eed6bd927cb6b4d1fd9" +checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" dependencies = [ "accesskit", ] [[package]] name = "accesskit_macos" -version = "0.6.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58d062544d6cc36f4213323b7cb3a0d74ddff4b0d2311ab5e7596f4278bb2cc9" +checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" dependencies = [ "accesskit", "accesskit_consumer", @@ -31,23 +47,23 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.13.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf5b3c3828397ee832ba4a72fb1a4ace10f781e31885f774cbd531014059115" +checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" dependencies = [ "accesskit", "accesskit_consumer", "arrayvec", "once_cell", "paste", - "windows", + "windows 0.48.0", ] [[package]] name = "accesskit_winit" -version = "0.12.4" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcb615217efc79c4bed3094c4ca76c4bc554751d1da16f3ed4ba0459b1e8f31" +checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" dependencies = [ "accesskit", "accesskit_macos", @@ -57,9 +73,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ "gimli", ] @@ -76,34 +92,46 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] -name = "aligned-vec" -version = "0.5.0" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-activity" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" +checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" dependencies = [ "android-properties", - "bitflags", + "bitflags 1.3.2", "cc", "jni-sys", "libc", @@ -111,7 +139,7 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "num_enum", + "num_enum 0.6.1", ] [[package]] @@ -122,9 +150,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android_log-sys" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" +checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" [[package]] name = "android_system_properties" @@ -152,15 +180,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -176,9 +204,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -186,9 +214,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "approx" @@ -199,26 +227,46 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "parking_lot", + "thiserror", + "winapi", + "x11rb", +] + [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ash" -version = "0.37.2+1.3.238" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ - "libloading", + "libloading 0.7.4", ] [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -253,7 +301,7 @@ dependencies = [ "log", "parking", "polling", - "rustix", + "rustix 0.37.23", "slab", "socket2 0.4.9", "waker-fn", @@ -261,9 +309,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -276,9 +324,9 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "atomic_refcell" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" +checksum = "112ef6b3f6cb3cb6fc5b6b494ef7a848492cff1ab0ef4de10b0f7d572861c905" [[package]] name = "autocfg" @@ -288,9 +336,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ "addr2line", "cc", @@ -303,30 +351,33 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] -name = "base64" -version = "0.21.2" +name = "basic-toml" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] [[package]] name = "bevy" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93f906133305915d63f04108e6873c1b93a6605fe374b8f3391f6bda093e396" +checksum = "18e71a9143ac21bed247c30129399af8be170309e7ff5983a1bd37e87d3da520" dependencies = [ "bevy_internal", ] [[package]] name = "bevy_a11y" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037c4063f7dac1a5d596eb47f40782a04ca5838dc4274dbbadc90eb81efe5169" +checksum = "3d87d5753cefaeb5f5c5d5e937844f5386eabaf9ab95f3013e86d8fb438d424e" dependencies = [ "accesskit", "bevy_app", @@ -336,13 +387,14 @@ dependencies = [ [[package]] name = "bevy_app" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01db46963eb9486f7884121527ec69751d0e448f9e1d5329e80ea3424118a31a" +checksum = "3cb660188d5d4ceaead6d5861ce22ecedc08b68c385cc8edf0a3c0c0285560bf" dependencies = [ "bevy_derive", "bevy_ecs", "bevy_reflect", + "bevy_tasks", "bevy_utils", "downcast-rs", "wasm-bindgen", @@ -351,11 +403,12 @@ dependencies = [ [[package]] name = "bevy_asset" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98609b4b0694a23bde0628aed626644967991f167aad9db2afb68dacb0017540" +checksum = "6733d20a22bb10da928785fdbf6fad6cfb1c7da4a8c170ab3adbba5862c375d5" dependencies = [ "anyhow", + "async-channel", "bevy_app", "bevy_diagnostic", "bevy_ecs", @@ -378,9 +431,9 @@ dependencies = [ [[package]] name = "bevy_core" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee53d7b4691b57207d72e996992c995a53f3e8d21ca7151ca3956d9ce7d232e" +checksum = "e1d47b435bdebeefedda95de98a419c4d3b4c160ed8ce3470ea358a16aad6038" dependencies = [ "bevy_app", "bevy_ecs", @@ -393,12 +446,13 @@ dependencies = [ [[package]] name = "bevy_core_pipeline" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093ae5ced77251602ad6e43521e2acc1a5570bf85b80f232f1a7fdd43b50f8d8" +checksum = "a01c2652f5a6d24e0ab465e6feca8a034dfb33dfefbcdc19e436fec879a362a8" dependencies = [ "bevy_app", "bevy_asset", + "bevy_core", "bevy_derive", "bevy_ecs", "bevy_math", @@ -406,27 +460,27 @@ dependencies = [ "bevy_render", "bevy_transform", "bevy_utils", - "bitflags", + "bitflags 2.4.0", "radsort", "serde", ] [[package]] name = "bevy_derive" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff0add5ab4a6b2b7e86e18f9043bb48b6386faa3b56abaa0ed97a3d669a1992" +checksum = "c5cc78985f4d0ad1fd7b8ead06dcfaa192685775a7b1be158191c788c7d52298" dependencies = [ "bevy_macro_utils", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "bevy_diagnostic" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c778422643b0adee9e82abbd07e1e906eb9947c274a9b18e0f7fbf137d4c34" +checksum = "ee4e366724d233fdc7e282e1415f4cd570e97fbb8443e5a8ff3f8cc5ae253ffd" dependencies = [ "bevy_app", "bevy_core", @@ -439,9 +493,9 @@ dependencies = [ [[package]] name = "bevy_ecs" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed2f74687ccf13046c0f8e3b00dc61d7e656877b4a1380cf04635bb74d8e586" +checksum = "fb6fd0ec64cd32b8fcf16157173431ba0e675b29c4643a8d6c9a9eeef6f93c32" dependencies = [ "async-channel", "bevy_ecs_macros", @@ -454,36 +508,69 @@ dependencies = [ "fixedbitset", "rustc-hash", "serde", + "thiserror", "thread_local", ] [[package]] name = "bevy_ecs_macros" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97fd126a0db7b30fb1833614b3a657b44ac88485741c33b2780e25de0f96d78" +checksum = "e13b0fd864433db6ff825efd0eb86b2690e208151905b184cc9bfd2c3ac66c3b" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", +] + +[[package]] +name = "bevy_egui" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a73a93a8cf6b8c744281d1b88f5b0fa278d608e909af9bbf4eb491a7cb1ad2c" +dependencies = [ + "arboard", + "bevy", + "egui", + "thread_local", + "webbrowser", ] [[package]] name = "bevy_encase_derive" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c086ebdc1f5522787d63772943277cc74a279445fb65db4d58c2c5330654648e" +checksum = "da8ffb3d214ee4d8b7e851bc8409768a0f18c872c3a25065c248611ff832180e" dependencies = [ "bevy_macro_utils", "encase_derive_impl", ] +[[package]] +name = "bevy_gizmos" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c08196fcb36b7175577443cbe2c1193d596a15eb0fa210bae378e6e1478876" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_transform", + "bevy_utils", +] + [[package]] name = "bevy_hierarchy" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d04099865a13d1fd8bf3c044a80148cb3d23bfe8c3d5f082dda2ce091d85532" +checksum = "402789ee53acf345243cf2c86a895d6cf8631e533780ed261c6ecf891eb050b8" dependencies = [ "bevy_app", "bevy_core", @@ -496,9 +583,9 @@ dependencies = [ [[package]] name = "bevy_input" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15d40aa636bb656967ac16ca36066ab7a7bb9179e1b0390c5705e54208e8fd7" +checksum = "b9dfd9c768cf153f3fc807661996b2db44b824299860ba198fb3b92dd731bdd8" dependencies = [ "bevy_app", "bevy_ecs", @@ -510,9 +597,9 @@ dependencies = [ [[package]] name = "bevy_internal" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862b11931c5874cb00778ffb715fc526ee49e52a493d3bcf50e8010f301858b3" +checksum = "1006f2c501bceb1aef5cc18ed07eb822f295763227b83ba6887e6743698af9f8" dependencies = [ "bevy_a11y", "bevy_app", @@ -522,6 +609,7 @@ dependencies = [ "bevy_derive", "bevy_diagnostic", "bevy_ecs", + "bevy_gizmos", "bevy_hierarchy", "bevy_input", "bevy_log", @@ -530,6 +618,7 @@ dependencies = [ "bevy_ptr", "bevy_reflect", "bevy_render", + "bevy_scene", "bevy_sprite", "bevy_tasks", "bevy_time", @@ -541,9 +630,9 @@ dependencies = [ [[package]] name = "bevy_log" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25980c90ceaad34d09a53291e72ca56fcc754a974cd4654fffcf5b68b283b7a7" +checksum = "f0b1e5ca5b217dd384a3bf9186df0d0da757035f9f06d8eec0ebe62cffc05c34" dependencies = [ "android_log-sys", "bevy_app", @@ -557,20 +646,21 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2fee53b2497cdc3bffff2ddf52afa751242424a5fd0d51d227d4dab081d0d9" +checksum = "d1cd460205fe05634d58b32d9bb752b1b4eaf32b2d29cbd4161ba35eb44a2f8c" dependencies = [ "quote", - "syn 1.0.109", + "rustc-hash", + "syn 2.0.29", "toml_edit", ] [[package]] name = "bevy_math" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6a1109d06fe947990db032e719e162414cf9bf7a478dcc52742f1c7136c42a" +checksum = "267f2ec44aa948051768b1320c2dbff0871799e0a3b746f5fe5b6ee7258fbaf5" dependencies = [ "glam", "serde", @@ -578,18 +668,18 @@ dependencies = [ [[package]] name = "bevy_mikktspace" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39106bc2ee21fce9496d2e15e0ba7925dff63e3eae10f7c1fc0094b56ad9f2bb" +checksum = "0d98eaa7f40b97bb9b2d681ecf9fd439830a7eb88ad2846680d4f4acd285cf84" dependencies = [ "glam", ] [[package]] name = "bevy_pbr" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f507cef55812aa70c2ec2b30fb996eb285fa7497d974cf03f76ec49c77fbe27" +checksum = "9386944248ac8fcaaabec2302b0e7cd8196ef5d7b7a2e63b10ace3eeb813d3de" dependencies = [ "bevy_app", "bevy_asset", @@ -602,16 +692,17 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags", + "bitflags 2.4.0", "bytemuck", + "naga_oil", "radsort", ] [[package]] name = "bevy_prototype_lyon" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799f9bc78cdef67d8dbecd06d2147d30732cdc74eb5e15edacf76ff2069b3d9f" +checksum = "9e347c16caede05dc5f774ba388cefeef0ab558a5601fc6b5ffd6606bef77308" dependencies = [ "bevy", "lyon_algorithms", @@ -621,15 +712,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b88451d4c5a353bff67dbaa937b6886efd26ae114769c17f2b35099c7a4de" +checksum = "15702dff420fac72c2ab92428a8600e079ae89c5845401c4e39b843665a3d2d0" [[package]] name = "bevy_reflect" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc3979471890e336f3ba87961ef3ecd45c331cf2cb2f582c885e541af228b48" +checksum = "3ac66cccbf1457c5cfc004a0e83569bd4ddc5d6310701f4af6aa81001fe2964a" dependencies = [ "bevy_math", "bevy_ptr", @@ -642,28 +733,29 @@ dependencies = [ "parking_lot", "serde", "smallvec", + "smol_str", "thiserror", ] [[package]] name = "bevy_reflect_derive" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc7ea7c9bc2c531eb29ba5619976613d6680453ff5dd4a7fcd08848e8bec5ad" +checksum = "e5a2a1fa784e9a22560b9de350246a159cd59239eb61c7b66824031b3b28abb0" dependencies = [ "bevy_macro_utils", "bit-set", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", "uuid", ] [[package]] name = "bevy_render" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee1e126226f0a4d439bf82fe07c1104f894a6a365888e3eba7356f9647e77a83" +checksum = "b2e125de06ffaed8100623843b447fb524dc16f2a2120adce81143d7307278be" dependencies = [ "anyhow", "async-channel", @@ -684,52 +776,68 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags", + "bitflags 2.4.0", + "bytemuck", "codespan-reporting", "downcast-rs", "encase", "futures-lite", "hexasphere", "image", + "js-sys", "naga", - "once_cell", + "naga_oil", "parking_lot", "regex", "serde", "smallvec", "thiserror", "thread_local", + "wasm-bindgen", + "web-sys", "wgpu", "wgpu-hal", ] [[package]] name = "bevy_render_macros" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "652f8c4d9577c6e6a8b3dfd8a4ce331e8b6ecdbb99636a4b2701dec50104d6bc" +checksum = "e283eb4156285d0d4b85ce9b959d080b652165848f0b3f1a8770af6cfad41c34" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] -name = "bevy_simple_tilemap" -version = "0.11.0" -source = "git+https://github.com/MaxCWhitehead/bevy_simple_tilemap.git?branch=v0.11.0#25756eb2aa2c3e5697a2209a4020a470ca59bb47" +name = "bevy_scene" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77172c572239e741283e585433e986dd7f1bfdd7f7489ff10933f59e93cbb04" dependencies = [ - "bevy", - "bitflags", - "bytemuck", + "anyhow", + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_hierarchy", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "ron", + "serde", + "thiserror", + "uuid", ] [[package]] name = "bevy_sprite" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c110358fe3651a5796fd1c07989635680738f5b5c7e9b8a463dd50d12bb78410" +checksum = "d7811ade4df81ffa6bae0e293c42d784ad88ce84d2b10fa05801e3c368714581" dependencies = [ "bevy_app", "bevy_asset", @@ -742,7 +850,7 @@ dependencies = [ "bevy_render", "bevy_transform", "bevy_utils", - "bitflags", + "bitflags 2.4.0", "bytemuck", "fixedbitset", "guillotiere", @@ -752,24 +860,23 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de86364316e151aeb0897eaaa917c3ad5ee5ef1471a939023cf7f2d5ab76955" +checksum = "9200e7b42d49c787d9a08675c425d8bd6393ba3beed2eac64be6027a44a01870" dependencies = [ "async-channel", "async-executor", "async-task", "concurrent-queue", "futures-lite", - "once_cell", "wasm-bindgen-futures", ] [[package]] name = "bevy_time" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3edbd605df1bced312eb9888d6be3d5a5fcac3d4140038bbe3233d218399eef" +checksum = "2ba50bf25c4dc40296b744f77de10d39c8981b710d8dce609da9de5e54ef164b" dependencies = [ "bevy_app", "bevy_ecs", @@ -781,9 +888,9 @@ dependencies = [ [[package]] name = "bevy_transform" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24383dfb97d8a14b17721ecfdf58556eff5ea9a4b2a3d91accf2b472783880b0" +checksum = "86043ec5cc3cf406d33c0e4d6920171601e186b1288e9b4f5ae54682a0564032" dependencies = [ "bevy_app", "bevy_ecs", @@ -794,14 +901,14 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a88ebbca55d360d72e9fe78df0d22e25cd419933c9559e79dae2757f7c4d066" +checksum = "829eb8d0d06a0baeabc2e8bad74136ed3329b055aa1e11c5d9df09ebb9be3d85" dependencies = [ - "ahash", + "ahash 0.8.3", "bevy_utils_proc_macros", - "getrandom 0.2.9", - "hashbrown", + "getrandom", + "hashbrown 0.14.0", "instant", "petgraph", "thiserror", @@ -811,20 +918,20 @@ dependencies = [ [[package]] name = "bevy_utils_proc_macros" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630b92e32fa5cd7917c7d4fdbf63a90af958b01e096239f71bc4f8f3cf40c0d2" +checksum = "0d104f29e231123c703e8b394e2341d2425c33c5a2e9ab8cc8d0a554bdb62a41" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "bevy_window" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad31234754268fbe12050290b0496e2296252a16995a38f94bfb9680a4f09fda" +checksum = "4a66f9963152093220fc5ffd2244cadcc7f79cf2c23dd02076f4d0cbb7f5162f" dependencies = [ "bevy_app", "bevy_ecs", @@ -837,9 +944,9 @@ dependencies = [ [[package]] name = "bevy_winit" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf17bd6330f7e633b7c56754c776511a8f52cde4bf54c0278f34d7527548f253" +checksum = "003e20cff652a364f1f98b0d5ba24f53140dc77643241afe4a9b848bdde66184" dependencies = [ "accesskit_winit", "approx", @@ -850,10 +957,10 @@ dependencies = [ "bevy_hierarchy", "bevy_input", "bevy_math", + "bevy_tasks", "bevy_utils", "bevy_window", "crossbeam-channel", - "once_cell", "raw-window-handle", "wasm-bindgen", "web-sys", @@ -881,6 +988,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] + [[package]] name = "bitset-core" version = "0.1.1" @@ -893,6 +1009,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-sys" version = "0.1.0-beta.1" @@ -914,113 +1039,79 @@ dependencies = [ [[package]] name = "bones_asset" -version = "0.2.0" -dependencies = [ - "bevy_asset", - "bones_bevy_utils", - "bones_ecs", - "serde", - "type_ulid", - "ulid", -] - -[[package]] -name = "bones_bevy_asset" -version = "0.2.0" +version = "0.3.0" dependencies = [ - "bevy", - "bevy_app", - "bevy_asset", - "bevy_reflect", - "bevy_utils", - "bones_bevy_asset_macros", - "bones_bevy_utils", - "bones_lib", + "anyhow", + "async-channel", + "bones_schema", + "bones_utils", + "bs58", "glam", + "notify", + "once_cell", + "paste", + "semver", "serde", "serde_json", "serde_yaml", - "type_ulid", - "uuid", -] - -[[package]] -name = "bones_bevy_asset_macros" -version = "0.2.0" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", + "sha2", "ulid", ] [[package]] name = "bones_bevy_renderer" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bevy", + "bevy_egui", "bevy_prototype_lyon", - "bevy_simple_tilemap", - "bones_bevy_asset", - "bones_lib", + "bones_framework", "glam", - "serde", - "serde_json", - "serde_yaml", - "type_ulid", -] - -[[package]] -name = "bones_bevy_utils" -version = "0.2.0" -dependencies = [ - "bevy_ecs", - "type_ulid", ] [[package]] name = "bones_ecs" -version = "0.2.0" +version = "0.3.0" dependencies = [ - "aligned-vec", "anyhow", "atomic_refcell", - "bevy_derive", "bitset-core", - "bytemuck", - "either", - "fxhash", + "bones_schema", + "bones_utils", "glam", - "itertools", - "serde", + "paste", "thiserror", - "type_ulid", ] [[package]] -name = "bones_input" -version = "0.2.0" +name = "bones_framework" +version = "0.3.0" dependencies = [ - "bevy", - "bones_bevy_utils", + "bones_lib", + "document-features", + "egui", + "fluent-bundle", + "fluent-langneg", + "gilrs", "glam", + "hex", + "image", "instant", - "type_ulid", + "intl-memoizer", + "serde", + "serde_yaml", + "sys-locale", + "thiserror", + "tracing", + "unic-langid", ] [[package]] name = "bones_lib" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bones_asset", - "bones_bevy_utils", "bones_ecs", - "bones_input", - "bones_render", - "noise", - "serde", - "type_ulid", ] [[package]] @@ -1040,7 +1131,7 @@ dependencies = [ "postcard", "quinn", "quinn_runtime_bevy", - "rand 0.8.5", + "rand", "rcgen", "rustls", "scc", @@ -1057,34 +1148,81 @@ dependencies = [ ] [[package]] -name = "bones_render" -version = "0.2.0" +name = "bones_schema" +version = "0.3.0" dependencies = [ - "bevy_render", - "bevy_transform", - "bones_asset", - "bones_bevy_utils", - "bones_ecs", + "bones_schema_macros", + "bones_utils", "glam", - "hex", + "paste", "serde", - "thiserror", - "type_ulid", + "serde_yaml", + "sptr", + "trybuild", + "ulid", ] [[package]] -name = "bumpalo" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +name = "bones_schema_macros" +version = "0.3.0" dependencies = [ - "bytemuck_derive", + "proc-macro2", + "quote", + "venial", +] + +[[package]] +name = "bones_utils" +version = "0.3.0" +dependencies = [ + "bevy_ptr", + "bones_utils_macros", + "branches", + "fxhash", + "hashbrown 0.14.0", + "maybe-owned", + "parking_lot", + "serde", + "smallvec", + "ulid", +] + +[[package]] +name = "bones_utils_macros" +version = "0.3.0" +dependencies = [ + "quote", + "venial", +] + +[[package]] +name = "branches" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7958fb9748a08a6f46ef773e87c43997a844709bc293b4c3de48135debaf9d2a" + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +dependencies = [ + "bytemuck_derive", ] [[package]] @@ -1095,7 +1233,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] @@ -1112,13 +1250,20 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -1133,9 +1278,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "clap" -version = "4.3.0" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" dependencies = [ "clap_builder", "clap_derive", @@ -1144,27 +1289,26 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.3.0" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] @@ -1173,6 +1317,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + [[package]] name = "cobs" version = "0.2.3" @@ -1207,6 +1362,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -1232,6 +1397,21 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" +[[package]] +name = "const_soft_float" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" + +[[package]] +name = "constgebra" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd23e864550e6dafc1e41ac78ce4f1ccddc8672b40c403524a04ff3f0518420" +dependencies = [ + "const_soft_float", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1254,7 +1434,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -1263,16 +1443,33 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1285,47 +1482,152 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "d3d12" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" dependencies = [ - "bitflags", - "libloading", + "bitflags 1.3.2", + "libloading 0.7.4", "winapi", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "demo_assets_minimal" +version = "0.3.0" +dependencies = [ + "bones_bevy_renderer", + "bones_framework", +] + +[[package]] +name = "demo_features" +version = "0.3.0" +dependencies = [ + "bones_bevy_renderer", + "bones_framework", +] + +[[package]] +name = "demo_hello_world" +version = "0.3.0" +dependencies = [ + "bones_bevy_renderer", + "bones_framework", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "document-features" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "ecolor" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e479a7fa3f23d4e794f8b2f8b3568dd4e47886ad1b12c9c095e141cb591eb63" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "egui" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3aef8ec3ae1b772f340170c65bf27d5b8c28f543a0116c844d2ac08d01123e7" +dependencies = [ + "ahash 0.8.3", + "epaint", + "nohash-hasher", +] + [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "emath" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3857d743a6e0741cdd60b622a74c7a36ea75f5f8f11b793b41d905d2c9721a4b" +dependencies = [ + "bytemuck", +] [[package]] name = "encase" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6591f13a63571c4821802eb5b10fd1155b1290bce87086440003841c8c3909b" +checksum = "8fce2eeef77fd4a293a54b62aa00ac9daebfbcda4bf8998c5a815635b004aa1c" dependencies = [ "const_panic", "encase_derive", @@ -1335,38 +1637,60 @@ dependencies = [ [[package]] name = "encase_derive" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1da6deed1f8b6f5909616ffa695f63a5de54d6a0f084fa715c70c8ed3abac9" +checksum = "0e520cde08cbf4f7cc097f61573ec06ce467019803de8ae82fb2823fa1554a0e" dependencies = [ "encase_derive_impl", ] [[package]] name = "encase_derive_impl" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae489d58959f3c4cdd1250866a05acfb341469affe4fced71aff3ba228be1693" +checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", +] + +[[package]] +name = "epaint" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09333964d4d57f40a85338ba3ca5ed4716070ab184dcfed966b35491c5c64f3b" +dependencies = [ + "ab_glyph", + "ahash 0.8.3", + "atomic_refcell", + "bytemuck", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "erased-serde" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" dependencies = [ "serde", ] [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1383,6 +1707,16 @@ dependencies = [ "libc", ] +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + [[package]] name = "euclid" version = "0.22.9" @@ -1407,12 +1741,43 @@ dependencies = [ "instant", ] +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.48.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float_next_after" version = "0.1.5" @@ -1422,6 +1787,46 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1437,6 +1842,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -1496,7 +1919,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] @@ -1539,50 +1962,108 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" dependencies = [ - "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "winapi", ] [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gilrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fd19844d0eb919aca41d3e4ea0e0b6bf60e1e827558b101c269015b8f5f27a" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85b0f27572f0560cfc4a067a2978a4a490f9fa5cf1326d30b142a288312a965" +dependencies = [ + "core-foundation", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix 0.26.2", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.48.0", +] + [[package]] name = "gimli" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "glam" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" +checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" dependencies = [ "bytemuck", "serde", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "glow" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" dependencies = [ "js-sys", "slotmap", @@ -1596,7 +2077,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gpu-alloc-types", ] @@ -1606,7 +2087,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1619,7 +2100,7 @@ dependencies = [ "log", "thiserror", "winapi", - "windows", + "windows 0.44.0", ] [[package]] @@ -1628,9 +2109,9 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1639,7 +2120,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1658,20 +2139,30 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", "serde", ] [[package]] name = "hassle-rs" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90601c6189668c7345fc53842cb3f3a3d872203d523be1b3cb44a36a3e62fb85" +checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "com-rs", "libc", - "libloading", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -1685,9 +2176,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1697,12 +2188,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hexasphere" -version = "8.1.0" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd41d443f978bfa380a6dad58b62a08c43bcb960631f13e9d015b911eaf73588" +checksum = "7cb3df16a7bcb1b5bc092abd55e14f77ca70aea14445026e264586fc62889a10" dependencies = [ + "constgebra", "glam", - "once_cell", ] [[package]] @@ -1712,38 +2203,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "image" -version = "0.24.6" +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-rational", - "num-traits", + "type-map", + "unic-langid", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "intl_pluralrules" +version = "7.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" dependencies = [ - "autocfg", - "hashbrown", + "unic-langid", ] [[package]] -name = "instant" -version = "0.1.12" +name = "io-kit-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "9b2d4429acc1deff0fbdece0325b4997bdb02b2c245ab7023fd5deca0f6348de" dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", + "core-foundation-sys", + "mach2", ] [[package]] @@ -1759,30 +2332,36 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "io-lifetimes", - "rustix", + "rustix 0.38.8", "windows-sys 0.48.0", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "itoa" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] -name = "itoa" -version = "1.0.6" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] [[package]] name = "jni-sys" @@ -1799,11 +2378,17 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1815,10 +2400,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", - "libloading", + "libloading 0.7.4", "pkg-config", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1827,9 +2432,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -1841,23 +2446,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "libm" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "litrs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" + [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1865,12 +2502,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lyon_algorithms" @@ -1895,9 +2529,9 @@ dependencies = [ [[package]] name = "lyon_path" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8358c012e5651e4619cfd0b5b75c0f77866181a01b0909aab4bae14adf660" +checksum = "ca507745ba7ccbc76e5c44e7b63b1a29d2b0d6126f375806a5bbaf657c7d6c45" dependencies = [ "lyon_geom", "num-traits", @@ -1914,6 +2548,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1929,22 +2572,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-graphics-types", "foreign-types", @@ -1954,39 +2612,39 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "wasi", + "windows-sys 0.48.0", ] [[package]] name = "naga" -version = "0.11.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c3d4269bcb7d50121097702fde1afb75f4ea8083aeb7a55688dcf289a853271" +checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" dependencies = [ "bit-set", - "bitflags", + "bitflags 1.3.2", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 1.9.3", "log", "num-traits", - "petgraph", "pp-rs", "rustc-hash", "spirv", @@ -1995,16 +2653,36 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "naga_oil" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9c27fc9c84580434af75123d13ad98d9a56e16d033b16dcfa6940728c8c225" +dependencies = [ + "bit-set", + "codespan-reporting", + "data-encoding", + "indexmap 1.9.3", + "naga", + "once_cell", + "regex", + "regex-syntax 0.6.29", + "rustc-hash", + "thiserror", + "tracing", + "unicode-ident", +] + [[package]] name = "ndk" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", - "num_enum", + "num_enum 0.5.11", "raw-window-handle", "thiserror", ] @@ -2025,14 +2703,52 @@ dependencies = [ ] [[package]] -name = "noise" -version = "0.8.2" +name = "nix" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba869e17168793186c10ca82c7079a4ffdeac4f1a7d9e755b9491c028180e40" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "num-traits", - "rand 0.7.3", - "rand_xorshift", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "static_assertions", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", ] [[package]] @@ -2077,9 +2793,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -2091,7 +2807,16 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", ] [[package]] @@ -2106,6 +2831,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "objc" version = "0.2.7" @@ -2116,6 +2853,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc-sys" version = "0.2.0-beta.2" @@ -2151,20 +2899,29 @@ dependencies = [ "cc", ] +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" -version = "0.30.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl-probe" @@ -2174,11 +2931,11 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "orbclient" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" +checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" dependencies = [ - "redox_syscall 0.3.5", + "redox_syscall", ] [[package]] @@ -2187,6 +2944,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owned_ttf_parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking" version = "2.1.0" @@ -2205,73 +2971,74 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.5", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem" -version = "1.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" dependencies = [ - "base64 0.13.1", + "base64", + "serde", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.0.0", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -2285,6 +3052,19 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -2292,7 +3072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", @@ -2303,9 +3083,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa512cd0d087cc9f99ad30a1bf64795b67871edbead083ffc3a4dfafa59aa00" +checksum = "c9ee729232311d3cd113749948b689627618133b1c5012b77342c1950b25eaeb" dependencies = [ "cobs", "serde", @@ -2338,24 +3118,24 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" +checksum = "46b2164ebdb1dfeec5e337be164292351e11daf63a05174c6776b2f47460f0c9" [[package]] name = "quinn" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21252f1c0fc131f1b69182db8f34837e8a69737b8251dff75636a9be0518c324" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" dependencies = [ "bytes", "futures-io", @@ -2371,12 +3151,12 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85af4ed6ee5a89f26a26086e9089a6643650544c025158449a3626ebf72884b3" +checksum = "b83c2a964b8b68e6c9c616f09b735b436a78843704fa6979a076073e622f69dc" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring", "rustc-hash", "rustls", @@ -2389,9 +3169,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", @@ -2402,7 +3182,7 @@ dependencies = [ [[package]] name = "quinn_runtime_bevy" -version = "0.2.0" +version = "0.3.0" dependencies = [ "async-executor", "async-io", @@ -2410,15 +3190,14 @@ dependencies = [ "futures-lite", "pin-project", "quinn", - "quinn-proto", "quinn-udp", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -2429,19 +3208,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2449,18 +3215,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2470,16 +3226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2488,25 +3235,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_xorshift" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -2523,9 +3252,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rcgen" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +checksum = "4954fbc00dcd4d8282c987710e50ba513d351400dbdd00e803a05172a90d8976" dependencies = [ "pem", "ring", @@ -2534,19 +3263,10 @@ dependencies = [ ] [[package]] -name = "rectangle-pack" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" - -[[package]] -name = "redox_syscall" -version = "0.2.16" +name = "rectangle-pack" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] +checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" [[package]] name = "redox_syscall" @@ -2554,18 +3274,19 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.8.3" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", ] [[package]] @@ -2577,6 +3298,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -2585,15 +3317,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "renderdoc-sys" -version = "0.7.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "ring" @@ -2610,6 +3342,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.0", + "serde", + "serde_derive", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2624,23 +3368,36 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring", @@ -2650,9 +3407,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -2662,18 +3419,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" dependencies = [ "ring", "untrusted", @@ -2681,30 +3438,39 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "scc" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d9bf5e8953149d84e5bbcdbc48841b8a2fcf9790b6b5da09fe45a166e3bcc17" +checksum = "be6effabd7563ad3037a693171700ea8917e34ac023aea7de86dd7bfbb703df6" [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -2718,11 +3484,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2731,39 +3497,54 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] + [[package]] name = "serde" -version = "1.0.163" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -2772,17 +3553,28 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.21" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap", + "indexmap 2.0.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2792,6 +3584,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.10" @@ -2818,9 +3616,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +dependencies = [ + "serde", +] + +[[package]] +name = "smol_str" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" dependencies = [ "serde", ] @@ -2857,16 +3664,28 @@ version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ - "bitflags", + "bitflags 1.3.2", "num-traits", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + [[package]] name = "strsim" version = "0.10.0" @@ -2901,20 +3720,30 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sys-locale" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b9eefabb91675082b41eb94c3ecd91af7656caee3fb4961a07c0ec8c7ca6f" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "sysinfo" -version = "0.28.4" +version = "0.29.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" +checksum = "d10ed79c22663a35a255d289a7fdcb43559fc77ff15df5ce6c341809e7867528" dependencies = [ "cfg-if", "core-foundation-sys", @@ -2935,22 +3764,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] @@ -2963,12 +3792,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "a79d09ac6b08c1ab3906a2f7cc2e81a0e27c7ae89c63812df75e52bef0751e07" dependencies = [ + "deranged", "serde", "time-core", ] @@ -2979,6 +3820,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +[[package]] +name = "tinystr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2996,28 +3846,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", + "backtrace", "pin-project-lite", - "windows-sys 0.48.0", ] [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "toml_datetime", "winnow", ] @@ -3036,13 +3885,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", ] [[package]] @@ -3095,6 +3944,36 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "trybuild" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df60d81823ed9c520ee897489573da4b1d79ffbe006b8134f46de1a1aa03555" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + +[[package]] +name = "ttf-parser" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" + +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + [[package]] name = "type_ulid" version = "0.2.0" @@ -3113,20 +3992,61 @@ dependencies = [ "ulid", ] +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "ulid" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd" dependencies = [ - "rand 0.8.5", + "rand", + "serde", +] + +[[package]] +name = "unic-langid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398f9ad7239db44fd0f80fe068d12ff22d78354080332a5077dc6f52f14dcf2f" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35bfd2f2b8796545b55d7d3fd3e89a0613f68a0d1c8bc28cb7ff96b411a35ff" +dependencies = [ + "serde", + "tinystr", ] +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-width" @@ -3142,9 +4062,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "untrusted" @@ -3152,6 +4072,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -3160,11 +4091,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ - "getrandom 0.2.9", + "getrandom", "serde", ] @@ -3174,6 +4105,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "venial" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "version_check" version = "0.9.4" @@ -3187,10 +4134,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "walkdir" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "wasi" @@ -3200,9 +4151,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3210,24 +4161,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -3237,9 +4188,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3247,22 +4198,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-scanner" @@ -3277,19 +4228,42 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "wgpu" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d745a1b6d91d85c33defbb29f0eee0450e1d2614d987e14bf6baf26009d132d7" +checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" dependencies = [ "arrayvec", "cfg-if", @@ -3311,20 +4285,20 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" +checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" dependencies = [ "arrayvec", "bit-vec", - "bitflags", + "bitflags 2.4.0", "codespan-reporting", - "fxhash", "log", "naga", "parking_lot", "profiling", "raw-window-handle", + "rustc-hash", "smallvec", "thiserror", "web-sys", @@ -3334,20 +4308,19 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.15.4" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcf61a283adc744bb5453dd88ea91f3f86d5ca6b027661c6c73c7734ae0288b" +checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags", + "bitflags 2.4.0", "block", "core-graphics-types", "d3d12", "foreign-types", - "fxhash", "glow", "gpu-alloc", "gpu-allocator", @@ -3356,7 +4329,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading", + "libloading 0.8.0", "log", "metal", "naga", @@ -3366,6 +4339,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", + "rustc-hash", "smallvec", "thiserror", "wasm-bindgen", @@ -3376,20 +4350,20 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32444e121b0bd00cb02c0de32fde457a9491bd44e03e7a5db6df9b1da2f6f110" +checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" dependencies = [ - "bitflags", + "bitflags 2.4.0", "js-sys", "web-sys", ] [[package]] name = "widestring" -version = "0.5.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -3416,6 +4390,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3427,17 +4410,26 @@ name = "windows" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-implement", "windows-interface", - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] name = "windows-implement" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" dependencies = [ "proc-macro2", "quote", @@ -3446,30 +4438,15 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -3485,7 +4462,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] @@ -3505,17 +4482,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3526,9 +4503,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -3538,9 +4515,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -3550,9 +4527,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -3562,9 +4539,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -3574,9 +4551,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -3586,9 +4563,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -3598,9 +4575,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winit" @@ -3609,7 +4586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196" dependencies = [ "android-activity", - "bitflags", + "bitflags 1.3.2", "cfg_aliases", "core-foundation", "core-graphics", @@ -3624,7 +4601,7 @@ dependencies = [ "orbclient", "percent-encoding", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall", "wasm-bindgen", "wayland-scanner", "web-sys", @@ -3634,9 +4611,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" dependencies = [ "memchr", ] @@ -3652,11 +4629,33 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +dependencies = [ + "gethostname", + "nix 0.24.3", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +dependencies = [ + "nix 0.24.3", +] + [[package]] name = "xml-rs" -version = "0.8.13" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f380ae16a37b30e6a2cf67040608071384b1450c189e61bea3ff57cde922d" +checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" [[package]] name = "yasna" diff --git a/Cargo.toml b/Cargo.toml index 00d96b5f4e..f70a9f982c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,21 @@ -[package] -authors = ["The Fish Folks & Spicy Lobster Developers"] -description = "Opinionated game meta-engine built on Bevy" -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_lib" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - [workspace] -members = [".", "crates/*"] +resolver = "2" +members = ["framework_crates/*", "other_crates/*", "demos/*"] +default-members = ["framework_crates/*"] + +[workspace.package] +edition = "2021" +version = "0.3.0" +rust-version = "1.71" +readme = "README.md" +homepage = "https://fishfolk.org/development/bones/introduction/" +repository = "https://github.com/fishfolk/bones" +documentation = "https://fishfolk.github.io/bones/rustdoc/bones_framework/index.html" +description = "The bones game development framework." +license = "MIT OR Apache-2.0" +authors = ["The Fish Folk & Spicy Lobster Developers"] +categories = ["game-development", "game-engines", "wasm", "data-structures"] +keywords = ["bones", "bevy", "scripting", "ecs", "framework"] [profile.release] lto = true - -[features] -bevy = ["bones_asset/bevy", "bones_render/bevy", "dep:bones_bevy_utils"] -serde = ["dep:serde", "bones_render/serde", "bones_ecs/serde"] - -[dependencies] -bones_asset = { version = "^0.2.0", path = "./crates/bones_asset" } -bones_bevy_utils = { version = "^0.2.0", path = "./crates/bones_bevy_utils", optional = true } -bones_ecs = { version = "^0.2.0", path = "./crates/bones_ecs" } -bones_input = { version = "^0.2.0", path = "./crates/bones_input" } -bones_render = { version = "^0.2.0", path = "./crates/bones_render" } -type_ulid = { version = "^0.2.0", path = "./crates/type_ulid" } - -noise = "0.8" -serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/book/book.toml b/book/book.toml deleted file mode 100644 index 4ccd41340e..0000000000 --- a/book/book.toml +++ /dev/null @@ -1,15 +0,0 @@ -[book] -authors = ["Fish Folk & Spicy Lobster Contributors"] -language = "en" -multilingual = false -src = "src" -title = "Bones Book" - -[build] -build-dir = "dist" -create-missing = false - -[output.html] -default-theme = "navy" -git-repository-url = "https://github.com/fishfolk/bones" -git-repository-icon = "fa-github" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md deleted file mode 100644 index 32c75fd5c9..0000000000 --- a/book/src/SUMMARY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Overview - -[Introduction](./introduction.md) -[Code of Conduct](./code_of_conduct.md) - ---- - -- [ECS Tutorial](./ecs_tutorial/index.md) - ---- - -[API Documentation](./api_documentation.md) diff --git a/book/src/api_documentation.md b/book/src/api_documentation.md deleted file mode 100644 index cb05cdbf9f..0000000000 --- a/book/src/api_documentation.md +++ /dev/null @@ -1,3 +0,0 @@ -# API Documentation - -Here is the link to our Rustdoc [API documentation](./rustdoc/bones_lib/index.html). diff --git a/book/src/code_of_conduct.md b/book/src/code_of_conduct.md deleted file mode 100644 index acc1e6c3e7..0000000000 --- a/book/src/code_of_conduct.md +++ /dev/null @@ -1 +0,0 @@ -{{#include ../../CODE_OF_CONDUCT.md}} \ No newline at end of file diff --git a/book/src/ecs_tutorial/index.md b/book/src/ecs_tutorial/index.md deleted file mode 100644 index dcb14aa7d9..0000000000 --- a/book/src/ecs_tutorial/index.md +++ /dev/null @@ -1,257 +0,0 @@ -# ECS Tutorial - -`bones_ecs` is the core of the Bones framework, and all of the other crates depend on it, but it can also be used independently of the rest of Bones. - -Here we'll give an overview of Bones ECS and how to use it. - -Throughout this tutorial we assume that you have imported the `bones_ecs` prelude: - -```rust -# extern crate bones_ecs; -use bones_ecs::prelude::*; -``` - -Or if you're using `bones_lib`: - -```rust,ignore -use bones_lib::ecs::prelude::*; -``` - -You must also add the `type_ulid` crate to your `Cargo.toml` ( this won't be necessary in the future ). - -## Components - -Let's start with making some components. We'll need a `Pos` component and a `Vel` component, to represent positions and velocities: - -```rust -# extern crate bones_ecs; -# extern crate type_ulid; -# use bones_ecs::prelude::*; -/// Our position component. -#[derive(Clone, TypeUlid, Debug)] -#[ulid = "01GPYNA7R38W826BG79QG0X590"] -pub struct Pos { - pub x: f32, - pub y: f32, -} - -/// Our velocity component. -#[derive(Clone, TypeUlid, Debug)] -#[ulid = "01GPYN9961WQAJ19Q6NEQ6AFPH"] -pub struct Vel { - pub x: f32, - pub y: f32, -} -``` - -Components are just rust structs, but notice that we are required to derive at least two traits on all our components: `Clone` and `TypeUlid`. - -- `Clone` is required to allow snapshotting the world state. -- `TypeUlid` is required to provide unique component identifiers across mods. - -For every different component you create, it has to have a different ULID. You can generate a ULID by going to [webassembly.sh](https://webassembly.sh) and typing the command `ulid`. - -> **Note:** We are considering removing the `TypeUlid` requirement. See this [issue](https://github.com/fishfolk/bones/issues/49) for details. - -## Systems - -Now that we have components, we can create our systems. - -### Setup - -Let's start off with a `setup_system` that will spawn some entities into our world. - -Systems in Bones ECS are just functions or closures where all the arguments are `SystemParam`s. - -```rust -# extern crate bones_ecs; -# extern crate type_ulid; -# use bones_ecs::prelude::*; -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYNA7R38W826BG79QG0X590"] -# pub struct Pos { -# pub x: f32, -# pub y: f32, -# } -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYN9961WQAJ19Q6NEQ6AFPH"] -# pub struct Vel { -# pub x: f32, -# pub y: f32, -# } -// Spawn our initial entities. -fn setup_system( - mut entities: ResMut, - mut positions: CompMut, - mut velocities: CompMut, -) { - // Create our first entity - let ent1 = entities.create(); - // Add add Pos and a Vel component to it. - positions.insert(ent1, Pos { x: 0., y: 0.}); - velocities.insert(ent1, Vel { x: 3.0, y: 1.0 }); - - // And do the same with another entity - let ent2 = entities.create(); - positions.insert(ent2, Pos { x: 0., y: 100. }); - velocities.insert(ent2, Vel { x: 0.0, y: -1.0 }); -} -``` - -Notice here how we use two different kinds of system parameters: `ResMut` and `CompMut`. - -- `ResMut` gives us mutable access to a resource. In this case, we specifically access the `Entities` resource, which is always present in the `World`. The `Entities` resource allows us to create and kill entities, as well as iterate over entities. -- `CompMut` gives us mutable access to a component store. `CompMut` can almost be though of as a `HashMap`. Similarly, you can get the position of an entity with `positions.get(ent)`. - -For both resources and components there are non-mutable variants, `Res` and `Comp`, that may be used of you only need to read from it. - -### Position-Velocity System - -Now that we've got our `setup_system` made, let's add a system that will update all the entity's positions based on their velocities. - -```rust -# extern crate bones_ecs; -# extern crate type_ulid; -# use bones_ecs::prelude::*; -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYNA7R38W826BG79QG0X590"] -# pub struct Pos { -# pub x: f32, -# pub y: f32, -# } -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYN9961WQAJ19Q6NEQ6AFPH"] -# pub struct Vel { -# pub x: f32, -# pub y: f32, -# } -fn pos_vel_system( - entities: Res, - mut positions: CompMut, - velocities: Comp, -) { - for (entity, (pos, vel)) in entities.iter_with((&mut positions, &velocities)) { - pos.x += vel.x; - pos.y += vel.y; - } -} -``` - -Here we see a new feature of the `Entities` resource. It allows us to iterate over all of our entities that have specific components. In this case, want to iterate over all our entities that have a position and a velocity, with mutable access to the position and read access to the velocity. - -### Print System - -Finally, we'll want to be able to see what our system is doing, so let's add one more system to print out the velocities and positions of our entities: - -```rust -# extern crate bones_ecs; -# extern crate type_ulid; -# use bones_ecs::prelude::*; -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYNA7R38W826BG79QG0X590"] -# pub struct Pos { -# pub x: f32, -# pub y: f32, -# } -# #[derive(Clone, TypeUlid, Debug)] -# #[ulid = "01GPYN9961WQAJ19Q6NEQ6AFPH"] -# pub struct Vel { -# pub x: f32, -# pub y: f32, -# } -fn print_system( - entities: Res, - positions: Comp, - velocities: Comp, -) { - println!("========"); - - for (entity, (pos, vel)) in entities.iter_with((&positions, &velocities)) { - println!("{entity:?}: {pos:?} - {vel:?}"); - } -} -``` - -## `World` and `SystemStages` - -Now that we've defined our components and our systems, it's time to put them together. - -The first step is to create a `World` to store our components and resources in: - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -let mut world = World::new(); -``` - -And now we can run our setup system, to create our entities: - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -# fn setup_system() {} -# let mut world = World::new(); -world.run_system(setup_system).unwrap(); -``` - -The `run_system()` function will run a system one time. Though we haven't done so in any of our systems so far, systems are allowed to return a `SystemResult`, so we have to unwrap the possible error when running the system. - -While we only want to run our setup system once, we will need to run our other systems multiple times, and for that we use `SystemStages`. - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -let mut stages = SystemStages::with_core_stages(); -``` - -This creates a new `SystemStages` collection, pre-populated with the `CoreStage`s. There are five core stages, run in order: - -- `CoreStage::First` -- `CoreStage::PreUpdate` -- `CoreStage::Update` -- `CoreStage::PostUpdate` -- `CoreStage::Last` - -These groupings make it easier to control which order your systems run, when you have a lot of different modules all adding systems to your `SystemStages`. - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -# fn pos_vel_system() {} -# fn print_system() {} -# let mut stages = SystemStages::with_core_stages(); -stages - .add_system_to_stage(CoreStage::Update, pos_vel_system) - .add_system_to_stage(CoreStage::PostUpdate, print_system); -``` - -Once we have added all our systems we have to initialize them. This will make sure that any component store or resources that they access are created registered with the world. - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -# let mut world = World::new(); -# let mut stages = SystemStages::with_core_stages(); -stages.initialize_systems(&mut world); -``` - -Finally, we can run our stages against the world, and it will be sure to execute all of our systems in all of their stages every time we call `run()`. Let's run it ten times, so we can see our positions changing according to their velocity over a few frames: - -```rust -# extern crate bones_ecs; -# use bones_ecs::prelude::*; -# let mut world = World::new(); -# let mut stages = SystemStages::with_core_stages(); -# stages.initialize_systems(&mut world); -for _ in 0..10 { - stages.run(&mut world).unwrap(); -} -``` - -## Full Example - -And that's it for the intro! Here's the full example: - -```rust,ignore -{{#include ../../../crates/bones_ecs/examples/pos_vel.rs}} -``` diff --git a/book/src/introduction.md b/book/src/introduction.md deleted file mode 100644 index 676d71c102..0000000000 --- a/book/src/introduction.md +++ /dev/null @@ -1 +0,0 @@ -{{#include ../../README.md}} diff --git a/crates/bones_asset/Cargo.toml b/crates/bones_asset/Cargo.toml deleted file mode 100644 index 1c6053666d..0000000000 --- a/crates/bones_asset/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Asset interface for bones_lib." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_asset" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bones_bevy_utils = { version = "^0.2.0", path = "../bones_bevy_utils", optional = true } -bones_ecs = { version = "^0.2.0", path = "../bones_ecs" } -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -bevy_asset = { version = "0.10", optional = true } -serde = "1.0" -ulid = "1.0" - -[features] -bevy = ["dep:bones_bevy_utils", "dep:bevy_asset"] -default = [] diff --git a/crates/bones_asset/src/lib.rs b/crates/bones_asset/src/lib.rs deleted file mode 100644 index 4208db5066..0000000000 --- a/crates/bones_asset/src/lib.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! An asset interface for Bones. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -use std::{ - any::TypeId, - collections::hash_map::Entry, - marker::PhantomData, - path::{Path, PathBuf}, - sync::Arc, -}; - -use bones_ecs::{ - prelude::{AtomicRefCell, Deref, DerefMut}, - ulid::{TypeUlid, UlidMap}, -}; - -/// The prelude. -pub mod prelude { - pub use crate::*; -} - -/// A resource that may be used to access [`AssetProvider`]s for all the different registered asset -/// types. -/// -/// > ⚠️ **Warning:** This API is work-in-progress and has not been used in an actual project yet. -/// > In the official Bevy integration we are currently "cheating" by just borrowing asset directly -/// > from the Bevy world. -/// > -/// > Cheating like this means that we can't acess assets through the C API that we want to provide -/// > later, and it isn't very ergonomic, so we will want to make something like this -/// > [`AssetProviders`] resource work properly later. -#[derive(Default)] -pub struct AssetProviders { - providers: UlidMap>, - type_ids: UlidMap, -} - -/// Type alias for getting the [`AssetProviders`] resource. -pub type ResAssetProviders<'a> = bones_ecs::system::Res<'a, AssetProvidersResource>; - -/// The type of the [`AssetProviders`] resource. -// TODO: Create custom system parameter to prevent needing to manualy `.borrow()` `AssetProvidersResource`. -#[derive(Deref, DerefMut, Clone, TypeUlid)] -#[ulid = "01GNWY5HKV5JZQRKG20ANJXHCK"] - -pub struct AssetProvidersResource(pub Arc>); - -impl Default for AssetProvidersResource { - fn default() -> Self { - Self(Arc::new(AtomicRefCell::new(AssetProviders::default()))) - } -} - -impl AssetProviders { - /// Add an asset provider for a specific asset type. - pub fn add(&mut self, provider: A) - where - T: TypeUlid + 'static, - A: AssetProvider + UntypedAssetProvider + 'static, - { - let type_id = TypeId::of::(); - let type_ulid = T::ULID; - - match self.type_ids.entry(type_ulid) { - Entry::Occupied(entry) => { - if entry.get() != &type_id { - panic!("Multiple Rust types with the same Type ULID"); - } - } - Entry::Vacant(entry) => { - entry.insert(type_id); - } - } - - self.providers.insert(type_ulid, Box::new(provider)); - } - - /// Get the asset provider for the given type - pub fn get(&self) -> AssetProviderRef { - self.try_get::().unwrap() - } - - /// Get the asset provider for the given asset type, if it exists. - pub fn try_get(&self) -> Option> { - self.providers.get(&T::ULID).map(|x| { - let untyped = x.as_ref(); - - AssetProviderRef { - untyped, - _phantom: PhantomData, - } - }) - } - - /// Get the asset provider for the given type - pub fn get_mut(&mut self) -> AssetProviderMut { - self.try_get_mut::().unwrap() - } - - /// Get the asset provider for the given asset type, if it exists. - pub fn try_get_mut(&mut self) -> Option> { - self.providers.get_mut(&T::ULID).map(|x| { - let untyped = x.as_mut(); - - AssetProviderMut { - untyped, - _phantom: PhantomData, - } - }) - } - - /// Remove an asset provider. - pub fn remove(&mut self) -> Box { - self.try_remove::().unwrap() - } - - /// Remove an asset provider. - pub fn try_remove(&mut self) -> Option> { - self.providers.remove(&T::ULID) - } -} - -/// Trait implemented for asset providers that can return untyped pointers to their assets. -pub trait UntypedAssetProvider: Sync + Send { - /// Returns a read-only pointer to the asset for the given handle, or a null pointer if it - /// doesn't exist. - fn get(&self, handle: UntypedHandle) -> *const u8; - /// Returns a mutable-only pointer to the asset for the given handle, or a null pointer if it - /// doesn't exist. - fn get_mut(&mut self, handle: UntypedHandle) -> *mut u8; -} - -/// Trait for asset providers. -/// -/// Asset providers are reponsible for returning references to assets out of their backing asset -/// store, when giving handles to the asset to laod -pub trait AssetProvider: Sync + Send { - /// Get a reference to an asset, if it exists in the store. - fn get(&self, handle: Handle) -> Option<&T>; - /// Get a mutable reference to an asset, if it exists in the store. - fn get_mut(&mut self, handle: Handle) -> Option<&mut T>; -} - -impl UntypedAssetProvider for dyn AssetProvider { - fn get(&self, handle: UntypedHandle) -> *const u8 { - let asset = >::get(self, handle.typed()); - asset - .map(|x| x as *const T as *const u8) - .unwrap_or(std::ptr::null()) - } - - fn get_mut(&mut self, handle: UntypedHandle) -> *mut u8 { - let asset = >::get_mut(self, handle.typed()); - asset - .map(|x| x as *mut T as *mut u8) - .unwrap_or(std::ptr::null_mut()) - } -} - -/// A borrow of an [`AssetProvider`]. -pub struct AssetProviderRef<'a, T: TypeUlid> { - untyped: &'a dyn UntypedAssetProvider, - _phantom: PhantomData, -} - -impl<'a, T: TypeUlid> AssetProviderRef<'a, T> { - /// Get an asset, given it's handle - pub fn get(&self, handle: Handle) -> Option<&T> { - let ptr = self.untyped.get(handle.untyped()) as *const T; - - if ptr.is_null() { - None - } else { - // SAFE: AssetProviderRef may only be constructed by us, and we only construct it ( see - // AssetProviders ) when we know the untyped provider matches T. - unsafe { Some(&*ptr) } - } - } -} - -/// A mutable borrow of an [`AssetProvider`]. -pub struct AssetProviderMut<'a, T: TypeUlid> { - untyped: &'a mut dyn UntypedAssetProvider, - _phantom: PhantomData, -} - -impl<'a, T: TypeUlid> AssetProviderMut<'a, T> { - /// Get an asset, given it's handle - pub fn get(&self, handle: Handle) -> Option<&T> { - let ptr = self.untyped.get(handle.untyped()) as *const T; - - if ptr.is_null() { - None - } else { - // SAFE: AssetProviderRef may only be constructed by us, and we only construct it ( see - // AssetProviders ) when we know the untyped provider matches T. - unsafe { Some(&*ptr) } - } - } - - /// Get an asset, given it's handle - pub fn get_mut(&mut self, handle: Handle) -> Option<&mut T> { - let ptr = self.untyped.get_mut(handle.untyped()) as *mut T; - - if ptr.is_null() { - None - } else { - // SAFE: AssetProviderRef may only be constructed by us, and we only construct it ( see - // AssetProviders ) when we know the untyped provider matches T. - unsafe { Some(&mut *ptr) } - } - } -} - -/// A path to an asset. -/// -/// This is a virtual filesystem path, and may not actually refer to physical files. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AssetPath { - /// The virtual filesystem path - pub path: Arc, - /// The optional sub-asset label - pub label: Option>, -} - -#[cfg(feature = "bevy")] -impl AssetPath { - /// Get the equivalent [`bevy_asset::AssetPath`] from this Bones [`AssetPath`]. - pub fn get_bevy_asset_path(&self) -> bevy_asset::AssetPath<'static> { - bevy_asset::AssetPath::new( - // Bones convention is that `/` is relative to root, but Bevy doesn't like the - // leading /. - if let Ok(path) = self.path.strip_prefix("/") { - path.to_path_buf() - } else { - self.path.to_path_buf() - }, - self.label.as_ref().map(|x| x.to_string()), - ) - } -} - -impl AssetPath { - /// Create a new asset path. - pub fn new>(path: P, label: Option) -> Self { - AssetPath { - path: Arc::from(path.into()), - label: label.map(Arc::from), - } - } - - /// Take this path, treat it as a path relative to `base_path`, normalize it, and update `self` - /// with the result. - pub fn normalize_relative_to(&mut self, base_path: &Path) { - fn normalize_path(path: &std::path::Path) -> std::path::PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ std::path::Component::Prefix(..)) = components.peek() { - let buf = std::path::PathBuf::from(c.as_os_str()); - components.next(); - buf - } else { - std::path::PathBuf::new() - }; - - for component in components { - match component { - std::path::Component::Prefix(..) => unreachable!(), - std::path::Component::RootDir => { - ret.push(component.as_os_str()); - } - std::path::Component::CurDir => {} - std::path::Component::ParentDir => { - ret.pop(); - } - std::path::Component::Normal(c) => { - ret.push(c); - } - } - } - - ret - } - - let is_relative = !self.path.starts_with(Path::new("/")); - - let path = if is_relative { - let base = base_path.parent().unwrap_or_else(|| Path::new("")); - base.join(&self.path) - } else { - self.path.to_path_buf() - }; - - self.path = Arc::from(normalize_path(&path)); - } -} - -impl Default for AssetPath { - fn default() -> Self { - Self { - path: Arc::from(PathBuf::default()), - label: Default::default(), - } - } -} - -/// A typed handle to an asset. -/// -/// The type of the handle is used to help reduce runtime errors arising from mis-matching handle -/// types, but internally, the handle's only stored data is it's [`AssetPath`]. -/// -/// You can change the type of a handle by converting it to an untyped handle with -/// [`untyped()`][Self::untyped] and converting it back to a typed handle with -/// [`typed()`][UntypedHandle::typed]. -#[derive(PartialEq, Eq, Hash)] -pub struct Handle { - /// The [`AssetPath`] for the asset. - pub path: AssetPath, - phantom: PhantomData, -} - -impl Clone for Handle { - fn clone(&self) -> Self { - Self { - path: self.path.clone(), - phantom: self.phantom, - } - } -} - -impl std::fmt::Debug for Handle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Handle").field("path", &self.path).finish() - } -} - -impl Handle { - /// Create a new asset handle, from it's path and label. - pub fn new>(path: P, label: Option) -> Self { - Handle { - path: AssetPath::new(path, label), - phantom: PhantomData, - } - } -} - -impl Default for Handle { - fn default() -> Self { - Self { - path: AssetPath::default(), - phantom: Default::default(), - } - } -} - -impl Handle { - /// Convert the handle to an [`UntypedHandle`]. - pub fn untyped(self) -> UntypedHandle { - UntypedHandle { path: self.path } - } -} - -/// An untyped handle to an asset. -/// -/// This simply contains the [`AssetPath`] of the asset. -/// -/// Can be converted to a typed handle with the [`typed()`][Self::typed] method. -#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)] -pub struct UntypedHandle { - /// The unique identifier of the asset this handle represents. - pub path: AssetPath, -} - -impl UntypedHandle { - /// Create a new handle from it's path and label. - pub fn new>(path: P, label: Option) -> Self { - UntypedHandle { - path: AssetPath::new(path, label), - } - } - - /// Create a typed [`Handle`] from this [`UntypedHandle`]. - pub fn typed(self) -> Handle { - Handle { - path: self.path, - phantom: PhantomData, - } - } -} - -impl<'de> serde::Deserialize<'de> for UntypedHandle { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(UntypedHandleVisitor) - } -} - -impl<'de, T: TypeUlid> serde::Deserialize<'de> for Handle { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer - .deserialize_str(UntypedHandleVisitor) - .map(UntypedHandle::typed) - } -} - -impl serde::Serialize for UntypedHandle { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&format!( - "{}{}", - self.path.path.to_str().expect("Non-unicode path"), - self.path - .label - .as_ref() - .map(|x| format!("#{}", x)) - .unwrap_or_default() - )) - } -} - -impl serde::Serialize for Handle { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&format!( - "{}{}", - self.path.path.to_str().expect("Non-unicode path"), - self.path - .label - .as_ref() - .map(|x| format!("#{}", x)) - .unwrap_or_default() - )) - } -} - -struct UntypedHandleVisitor; -impl<'de> serde::de::Visitor<'de> for UntypedHandleVisitor { - type Value = UntypedHandle; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - formatter, - "A string path to an asset with an optional label." - ) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - let (path, label) = match v.rsplit_once('#') { - Some((path, label)) => (path, Some(label)), - None => (v, None), - }; - - Ok(UntypedHandle { - path: AssetPath::new(path, label.map(String::from)), - }) - } -} - -/// Implement bevy conversions when bevy feature is enabled -#[cfg(feature = "bevy")] -mod bevy { - use bevy_asset::{prelude::*, Asset, AssetPath}; - use bones_bevy_utils::*; - use bones_ecs::ulid::TypeUlid; - - impl IntoBevy> for super::AssetPath { - fn into_bevy(self) -> AssetPath<'static> { - AssetPath::new(self.path.to_path_buf(), self.label.map(|x| x.to_string())) - } - } - impl super::Handle { - /// Get a Bevy weak [`Handle`] from from this bones asset handle. - pub fn get_bevy_handle(&self) -> Handle { - let asset_path = self.path.get_bevy_asset_path(); - Handle::weak(asset_path.into()) - } - } - - impl super::Handle { - /// Get a Bevy weak [`HandleUntyped`] from this bones asset handle. - pub fn get_bevy_handle_untyped(&self) -> HandleUntyped { - let asset_path = self.path.get_bevy_asset_path(); - HandleUntyped::weak(asset_path.into()) - } - } - impl super::UntypedHandle { - /// Get a Bevy weak [`HandleUntyped`] from this bones asset handle. - pub fn get_bevy_handle(&self) -> HandleUntyped { - let asset_path = self.path.get_bevy_asset_path(); - HandleUntyped::weak(asset_path.into()) - } - } -} diff --git a/crates/bones_bevy_asset/CHANGELOG.md b/crates/bones_bevy_asset/CHANGELOG.md deleted file mode 100644 index a5ae0cdcae..0000000000 --- a/crates/bones_bevy_asset/CHANGELOG.md +++ /dev/null @@ -1,179 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2023-06-01) - - - -### Chore - - - add serde to bones color. - Add serde Serialize / Deserailize to bones color. - -### Documentation - - - update changelogs. - -### New Features - - - upgrade to Bevy 0.10. - - implement `BonesBevyAssetLoad` for `Duration`. - - add time resource + sync system - - implement BonesBevyAssetLoad for `Key`. - This makes it easier to deserialize `Key`s in bevy assets. - -### Bug Fixes - - - makes bones asset path representation more consistent. - Previously the normalize method on a bones path would remove the leading - `/` to make it support Bevy paths, which can't start with a `/`, but - this was not consistent with the way that the handle was serialized. - - Now, the bones path representations always maintain the leading `/` to - indicate a root path, and the leading `/` is removed when converting to - a Bevy handle. - - This fixes issues run into when trying to read serialized bones handles - during map saving in Jumpy. - -### New Features (BREAKING) - - - add `from_world` implementation similar to Bevy. - Allows resources to be added with either a `Default` implementation, - or a custom `FromWorld` implementation that allows them to derive their, - value from any other data currently in the world. - -### Commit Statistics - - - - - 8 commits contributed to the release over the course of 119 calendar days. - - 133 days passed between releases. - - 8 commits were understood as [conventional](https://www.conventionalcommits.org). - - 8 unique issues were worked on: [#102](https://github.com/fishfolk/bones/issues/102), [#106](https://github.com/fishfolk/bones/issues/106), [#112](https://github.com/fishfolk/bones/issues/112), [#122](https://github.com/fishfolk/bones/issues/122), [#124](https://github.com/fishfolk/bones/issues/124), [#83](https://github.com/fishfolk/bones/issues/83), [#92](https://github.com/fishfolk/bones/issues/92), [#95](https://github.com/fishfolk/bones/issues/95) - -### Commit Details - - - -
view details - - * **[#102](https://github.com/fishfolk/bones/issues/102)** - - implement `BonesBevyAssetLoad` for `Duration`. ([`e7330d9`](https://github.com/fishfolk/bones/commit/e7330d9cdb590564c3c01255401d8530425e18f0)) - * **[#106](https://github.com/fishfolk/bones/issues/106)** - - makes bones asset path representation more consistent. ([`632ef4e`](https://github.com/fishfolk/bones/commit/632ef4e2d7647f6cb704a1b5eaeb2fbba9562314)) - * **[#112](https://github.com/fishfolk/bones/issues/112)** - - add serde to bones color. ([`c57a208`](https://github.com/fishfolk/bones/commit/c57a2089f4dcf6bd63e8f0e0609cf6ff3506084f)) - * **[#122](https://github.com/fishfolk/bones/issues/122)** - - upgrade to Bevy 0.10. ([`3f2e348`](https://github.com/fishfolk/bones/commit/3f2e3485f9556cc68eb4c04df34d3aa2c6087330)) - * **[#124](https://github.com/fishfolk/bones/issues/124)** - - update changelogs. ([`3f18051`](https://github.com/fishfolk/bones/commit/3f18051e023a4deb676a5f895f1478beda513f04)) - * **[#83](https://github.com/fishfolk/bones/issues/83)** - - implement BonesBevyAssetLoad for `Key`. ([`a699f5d`](https://github.com/fishfolk/bones/commit/a699f5d9254037d6127becae77f09527759fd408)) - * **[#92](https://github.com/fishfolk/bones/issues/92)** - - add `from_world` implementation similar to Bevy. ([`00110c2`](https://github.com/fishfolk/bones/commit/00110c27b0aa76ed597c7e4d62bec70cfd1b2a23)) - * **[#95](https://github.com/fishfolk/bones/issues/95)** - - add time resource + sync system ([`605345b`](https://github.com/fishfolk/bones/commit/605345bd3d4fa2f8f540ae106b114d52c45b904a)) -
- -## 0.1.0 (2023-01-18) - - - - - - - -### Chore - - - add missing crate descriptions. - -### Chore - - - generate changelogs for all crates. - -### Documentation - - - document source repository in cargo manifest. - The `repository` key under `bones_ecs` previously pointed to https://github.com/fishfolk/jumpy. - - This updates this to point to the bones repo, and also adds the `repository` key to the other - crates in the repository. - -### New Features - - - - - implement `BonesBevyAssetLoad` for more types. - Added implementations for `Option`, `HashMap`, - and `bevy_utils::HashMap` when the values implement - `BonesBevyAssetLoad`. - - add extra derive support & type implementations. - - The derive macro for `BonesBevyAssetLoad` can now be used on enums. - -### Style - - - use `if let` statement instead of `Option::map()` - -### New Features (BREAKING) - - - add asset integration with bevy. - This is a big overall change that adds ways to integrate Bones with bevy assets. - -### Refactor (BREAKING) - - - prepare for release. - - Remove `bones_has_load_progress`: for now we don't use it, and if we - want something similar we will work it into `bones_bevy_asset`. - - Remove `bones_camera_shake`: it was moved into `bones_lib::camera`. - - Add version numbers for all local dependencies. - - make world in `BevyWorld` resource optional. - Since the bevy world can't be cloned, we previously had it in - an Arc, but that didn't play nicely with world snapshots. - - Now the bevy world inside the `BevyWorld` resource is an - option, with the `BevyAssets` system param panicking if it - doesn't find the world when it needs it. - -### Commit Statistics - - - - - 12 commits contributed to the release over the course of 14 calendar days. - - 10 commits were understood as [conventional](https://www.conventionalcommits.org). - - 9 unique issues were worked on: [#29](https://github.com/fishfolk/bones/issues/29), [#33](https://github.com/fishfolk/bones/issues/33), [#37](https://github.com/fishfolk/bones/issues/37), [#39](https://github.com/fishfolk/bones/issues/39), [#41](https://github.com/fishfolk/bones/issues/41), [#52](https://github.com/fishfolk/bones/issues/52), [#63](https://github.com/fishfolk/bones/issues/63), [#65](https://github.com/fishfolk/bones/issues/65), [#67](https://github.com/fishfolk/bones/issues/67) - -### Commit Details - - - -
view details - - * **[#29](https://github.com/fishfolk/bones/issues/29)** - - add asset integration with bevy. ([`89b44d7`](https://github.com/fishfolk/bones/commit/89b44d7b4f64ec266eb0ea674c220e07376a03b7)) - * **[#33](https://github.com/fishfolk/bones/issues/33)** - - add derive macro for `BonesBevyAssetLoad`. ([`3206a4d`](https://github.com/fishfolk/bones/commit/3206a4d9559df5e9aafdc22e7c464308e3a9eac7)) - * **[#37](https://github.com/fishfolk/bones/issues/37)** - - document source repository in cargo manifest. ([`a693894`](https://github.com/fishfolk/bones/commit/a69389412d22b8cb48bab0ed96d739b0fee35348)) - * **[#39](https://github.com/fishfolk/bones/issues/39)** - - add extra derive support & type implementations. ([`7fd1c59`](https://github.com/fishfolk/bones/commit/7fd1c592c61e3032d803b8f70364b826b4a9ebaf)) - * **[#41](https://github.com/fishfolk/bones/issues/41)** - - make world in `BevyWorld` resource optional. ([`ef12c3f`](https://github.com/fishfolk/bones/commit/ef12c3fb681cc826199b1564e1a033a56a5ce2d4)) - * **[#52](https://github.com/fishfolk/bones/issues/52)** - - use `if let` statement instead of `Option::map()` ([`de43e3c`](https://github.com/fishfolk/bones/commit/de43e3cf45b9108bebecd4196aa7524c87758e35)) - * **[#63](https://github.com/fishfolk/bones/issues/63)** - - prepare for release. ([`ae0a761`](https://github.com/fishfolk/bones/commit/ae0a761fc9b82ba2fc639c2b6f7af09fb650cd31)) - * **[#65](https://github.com/fishfolk/bones/issues/65)** - - add missing crate descriptions. ([`2725246`](https://github.com/fishfolk/bones/commit/27252465ad0506ff2f8c377531fa079ec64d1750)) - * **[#67](https://github.com/fishfolk/bones/issues/67)** - - generate changelogs for all crates. ([`a68cb79`](https://github.com/fishfolk/bones/commit/a68cb79e6b7d3774c53c0236edf3a12175f297b5)) - * **Uncategorized** - - Release bones_bevy_asset_macros v0.2.0, bones_bevy_asset v0.1.0, bones_bevy_renderer v0.1.0, safety bump 2 crates ([`7f7bb38`](https://github.com/fishfolk/bones/commit/7f7bb38fca7b54fd1ad408bd63f63515d07ef2ab)) - - Release type_ulid_macros v0.1.0, type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`db0333d`](https://github.com/fishfolk/bones/commit/db0333ddacb6f29aed8664db67973e72ea586dce)) - - implement `BonesBevyAssetLoad` for more types. ([`c0a14c5`](https://github.com/fishfolk/bones/commit/c0a14c5681a82d8e2db725a678b3dbccfa8a80b4)) -
- diff --git a/crates/bones_bevy_asset/Cargo.toml b/crates/bones_bevy_asset/Cargo.toml deleted file mode 100644 index 02a8cff2f6..0000000000 --- a/crates/bones_bevy_asset/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Asset integration between bones_lib and Bevy." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_bevy_asset" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bones_bevy_asset_macros = { version = "0.2", path = "./macros" } -bones_bevy_utils = { version = "^0.2.0", path = "../bones_bevy_utils" } -bones_lib = { version = "^0.2.0", path = "../../", features = ["bevy"] } -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -bevy_app = "0.10" -bevy_asset = "0.10" -bevy_reflect = "0.10" -bevy_utils = "0.10" -glam = "0.23" -serde = { version = "1", features = ["derive"] } -serde_json = "1.0" -serde_yaml = "0.9" -uuid = "1.0" - -[dev-dependencies.bevy] -default-features = false -features = ["x11", "bevy_winit", "bevy_asset"] -version = "0.10" diff --git a/crates/bones_bevy_asset/assets/game.meta.yaml b/crates/bones_bevy_asset/assets/game.meta.yaml deleted file mode 100644 index dc1623ef61..0000000000 --- a/crates/bones_bevy_asset/assets/game.meta.yaml +++ /dev/null @@ -1,9 +0,0 @@ -title: My Game -info: - description: Some awesome game. - authors: - - John - - Jane -players: - - players/player1.player.yaml - - players/player2.player.json diff --git a/crates/bones_bevy_asset/assets/players/player1.player.yaml b/crates/bones_bevy_asset/assets/players/player1.player.yaml deleted file mode 100644 index f698655a41..0000000000 --- a/crates/bones_bevy_asset/assets/players/player1.player.yaml +++ /dev/null @@ -1 +0,0 @@ -name: Player 1 diff --git a/crates/bones_bevy_asset/assets/players/player2.player.json b/crates/bones_bevy_asset/assets/players/player2.player.json deleted file mode 100644 index fd602bdbb6..0000000000 --- a/crates/bones_bevy_asset/assets/players/player2.player.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Player 2" -} diff --git a/crates/bones_bevy_asset/examples/derive.rs b/crates/bones_bevy_asset/examples/derive.rs deleted file mode 100644 index 330e786c97..0000000000 --- a/crates/bones_bevy_asset/examples/derive.rs +++ /dev/null @@ -1,81 +0,0 @@ -use bevy::prelude::*; -use bones_bevy_asset::prelude::*; -use bones_lib::prelude as bones; -use serde::Deserialize; - -/// Example of an airplane asset. -#[derive(BonesBevyAsset, TypeUlid, Deserialize, Debug)] -#[ulid = "01GNT26ATV1QWAAYP2PA3M5EFT"] -// This allows us to load the asset from files with `.meta.json` and `.meta.yaml` extensions. -#[asset_id = "meta"] -pub struct GameMeta { - pub title: String, - #[asset(deserialize_only)] - pub info: GameInfo, - pub players: Vec>, -} - -#[derive(serde::Deserialize, Debug)] -pub struct GameInfo { - pub description: String, - pub authors: Vec, -} - -#[derive(BonesBevyAsset, TypeUlid, Deserialize, Debug)] -#[ulid = "01GNT6APZEBGYFJ8SAKX2Q7TX2"] -#[asset_id = "player"] -pub struct PlayerMeta { - pub name: String, -} - -#[derive(Resource)] -pub struct GameMetaHandle(pub Handle); - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_bones_asset::() - .add_bones_asset::() - .add_startup_system(|mut commands: Commands, asset_server: Res| { - let handle = asset_server.load("game.meta.yaml"); - commands.insert_resource(GameMetaHandle(handle)); - }) - .add_system( - |mut done: Local, - game_meta_assets: Res>, - player_assets: Res>, - game_meta_handle: Option>| { - if *done { - return; - } - let Some(game_meta_handle) = game_meta_handle else { - return; - }; - let Some(game_meta) = game_meta_assets.get(&game_meta_handle.0) else { - return; - }; - - let player_bevy_handles = game_meta - .players - .iter() - .map(|x| x.get_bevy_handle()) - .collect::>(); - - if player_bevy_handles - .iter() - .all(|x| player_assets.get(x).is_some()) - { - *done = true; - dbg!(&game_meta); - for player_handle in &game_meta.players { - let handle = player_handle.get_bevy_handle(); - - let player_meta = player_assets.get(&handle); - - dbg!(&player_meta); - } - } - }, - ) - .run(); -} diff --git a/crates/bones_bevy_asset/macros/CHANGELOG.md b/crates/bones_bevy_asset/macros/CHANGELOG.md deleted file mode 100644 index a9e00339ba..0000000000 --- a/crates/bones_bevy_asset/macros/CHANGELOG.md +++ /dev/null @@ -1,71 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2023-01-18) - - - -### Chore - - - add missing crate descriptions. - -### Chore - - - generate changelogs for all crates. - -### Documentation - - - document source repository in cargo manifest. - The `repository` key under `bones_ecs` previously pointed to https://github.com/fishfolk/jumpy. - - This updates this to point to the bones repo, and also adds the `repository` key to the other - crates in the repository. - -### New Features - - - - - add extra derive support & type implementations. - - The derive macro for `BonesBevyAssetLoad` can now be used on enums. -- Added more implementations of `BonesBevyAssetLoad` for primitive types. - -### New Features (BREAKING) - - - add asset integration with bevy. - This is a big overall change that adds ways to integrate Bones with bevy assets. - -### Commit Statistics - - - - - 6 commits contributed to the release over the course of 14 calendar days. - - 6 commits were understood as [conventional](https://www.conventionalcommits.org). - - 6 unique issues were worked on: [#29](https://github.com/fishfolk/bones/issues/29), [#33](https://github.com/fishfolk/bones/issues/33), [#37](https://github.com/fishfolk/bones/issues/37), [#39](https://github.com/fishfolk/bones/issues/39), [#65](https://github.com/fishfolk/bones/issues/65), [#67](https://github.com/fishfolk/bones/issues/67) - -### Commit Details - - - -
view details - - * **[#29](https://github.com/fishfolk/bones/issues/29)** - - add asset integration with bevy. ([`89b44d7`](https://github.com/fishfolk/bones/commit/89b44d7b4f64ec266eb0ea674c220e07376a03b7)) - * **[#33](https://github.com/fishfolk/bones/issues/33)** - - add derive macro for `BonesBevyAssetLoad`. ([`3206a4d`](https://github.com/fishfolk/bones/commit/3206a4d9559df5e9aafdc22e7c464308e3a9eac7)) - * **[#37](https://github.com/fishfolk/bones/issues/37)** - - document source repository in cargo manifest. ([`a693894`](https://github.com/fishfolk/bones/commit/a69389412d22b8cb48bab0ed96d739b0fee35348)) - * **[#39](https://github.com/fishfolk/bones/issues/39)** - - add extra derive support & type implementations. ([`7fd1c59`](https://github.com/fishfolk/bones/commit/7fd1c592c61e3032d803b8f70364b826b4a9ebaf)) - * **[#65](https://github.com/fishfolk/bones/issues/65)** - - add missing crate descriptions. ([`2725246`](https://github.com/fishfolk/bones/commit/27252465ad0506ff2f8c377531fa079ec64d1750)) - * **[#67](https://github.com/fishfolk/bones/issues/67)** - - generate changelogs for all crates. ([`a68cb79`](https://github.com/fishfolk/bones/commit/a68cb79e6b7d3774c53c0236edf3a12175f297b5)) -
- - - add derive macro for BonesBevyAssetLoad.This makes it easier to nest asset structs that have handles that need loading. - diff --git a/crates/bones_bevy_asset/macros/Cargo.toml b/crates/bones_bevy_asset/macros/Cargo.toml deleted file mode 100644 index a365c229d3..0000000000 --- a/crates/bones_bevy_asset/macros/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "bones_bevy_asset_macros" -description = "Macros for the bones_bevy_asset crate." -version = "0.2.0" -edition = "2021" -authors = ["The Fish Folk & Spicy Lobster Developers"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/fishfolk/bones" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.43" -quote = "1.0.21" -syn = { version = "1.0.100", features = ["extra-traits"] } -ulid = { version = "1.0.0", default-features = false } -regex = "1.0.0" - diff --git a/crates/bones_bevy_asset/macros/src/lib.rs b/crates/bones_bevy_asset/macros/src/lib.rs deleted file mode 100644 index 2c469f00c1..0000000000 --- a/crates/bones_bevy_asset/macros/src/lib.rs +++ /dev/null @@ -1,259 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote, quote_spanned}; -use syn::{parse_quote, spanned::Spanned}; - -/// Derive macro for the `BonesBevyAsset` trait. -#[proc_macro_derive(BonesBevyAsset, attributes(asset_id, asset))] -pub fn bones_bevy_asset(input: TokenStream) -> TokenStream { - let input = syn::parse(input).unwrap(); - - impl_bones_bevy_asset(&input).into() -} - -fn impl_bones_bevy_asset(input: &syn::DeriveInput) -> TokenStream2 { - let deserialize_only: syn::Attribute = parse_quote! { - #[asset(deserialize_only)] - }; - let item_ident = &input.ident; - - let mut asset_id = None; - for attr in &input.attrs { - let Ok(syn::Meta::NameValue(name_value)) = attr.parse_meta() else { - continue; - }; - - if name_value - .path - .get_ident() - .map(|i| i != "asset_id") - .unwrap_or(true) - { - continue; - } - - let syn::Lit::Str(lit_str) = name_value.lit else { - continue; - }; - - asset_id = Some(lit_str.value()); - } - - let Some(asset_id) = asset_id else { - return quote! { - compile_error!("You must specify a `asset_id` attribute"); - }; - }; - - let module_ident = format_ident!( - "{}_derive_bevy_asset", - item_ident.to_string().to_lowercase() - ); - - // Parse the struct - let in_struct = match &input.data { - syn::Data::Struct(s) => s, - syn::Data::Enum(_) | syn::Data::Union(_) => { - return quote_spanned! { input.ident.span() => - compile_error!("You may only derive HasLoadProgress on structs"); - }; - } - }; - - let mut field_loads = Vec::new(); - 'field: for field in &in_struct.fields { - // Skip this field if it has `#[has_load_progress(none)]` - for attr in &field.attrs { - if attr.path == parse_quote!(asset) { - if attr != &deserialize_only { - field_loads.push(quote_spanned! { attr.span() => - compile_error!("Attribute must be `#[asset(deserialize_only)]` if specified"); - }); - } - continue 'field; - } - } - let field_ident = field.ident.as_ref().expect("Field identifier missing"); - field_loads.push(quote_spanned! { field_ident.span() => - ::bones_bevy_asset::BonesBevyAssetLoad::load( - &mut meta.#field_ident, - load_context, - &mut dependencies - ); - }); - } - - quote! { - mod #module_ident { - use ::type_ulid::TypeUlid; - use ::bevy::asset::AddAsset; - use super::#item_ident; - - // Make sure `TypeUlid` is implemented - trait RequiredBounds: type_ulid::TypeUlid + for<'de> ::serde::Deserialize<'de> {} - impl RequiredBounds for #item_ident {} - - impl ::bevy::reflect::TypeUuid for #item_ident { - const TYPE_UUID: bevy::reflect::Uuid = bevy::reflect::Uuid::from_u128(Self::ULID.0); - } - - struct AssetLoader; - impl ::bevy::asset::AssetLoader for AssetLoader { - fn load<'a>( - &'a self, - bytes: &'a [u8], - load_context: &'a mut bevy::asset::LoadContext, - ) -> bevy::utils::BoxedFuture<'a, Result<(), bevy::asset::Error>> { - Box::pin(async move { - let mut dependencies = Vec::new(); - let mut meta: #item_ident = - if load_context.path().extension() == Some(std::ffi::OsStr::new("json")) { - ::bones_bevy_asset::_private::serde_json::from_slice(bytes)? - } else { - ::bones_bevy_asset::_private::serde_yaml::from_slice(bytes)? - }; - - #(#field_loads)* - - load_context.set_default_asset( - bevy::asset::LoadedAsset::new(meta) - .with_dependencies(dependencies) - ); - - Ok(()) - }) - } - - fn extensions(&self) -> &[&str] { - &[ - concat!(#asset_id, ".json"), - concat!(#asset_id, ".yaml"), - concat!(#asset_id, ".yml"), - ] - } - } - - impl ::bones_bevy_asset::BonesBevyAsset for #item_ident { - fn install_asset(app: &mut ::bevy::app::App) { - app - .add_asset::() - .add_asset_loader(AssetLoader); - } - } - } - } -} - -/// Derive macro for the `BonesBevyAssetLoad` trait. -#[proc_macro_derive(BonesBevyAssetLoad, attributes(asset))] -pub fn bones_bevy_asset_load(input: TokenStream) -> TokenStream { - let input = syn::parse(input).unwrap(); - - impl_bones_bevy_asset_load(&input).into() -} - -fn impl_bones_bevy_asset_load(input: &syn::DeriveInput) -> TokenStream2 { - let deserialize_only: syn::Attribute = parse_quote! { - #[asset(deserialize_only)] - }; - let item_ident = &input.ident; - - // Parse the struct - let mut field_loads = Vec::new(); - match &input.data { - syn::Data::Struct(s) => { - 'field: for field in &s.fields { - // Skip this field if it has `#[has_load_progress(none)]` - for attr in &field.attrs { - if attr.path == parse_quote!(asset) { - if attr != &deserialize_only { - field_loads.push(quote_spanned! { attr.span() => - compile_error!("Attribute must be `#[asset(deserialize_only)]` if specified"); - }); - } - continue 'field; - } - } - let field_ident = field.ident.as_ref().expect("Field identifier missing"); - field_loads.push(quote_spanned! { field_ident.span() => - ::bones_bevy_asset::BonesBevyAssetLoad::load( - &mut self.#field_ident, - load_context, - dependencies - ); - }); - } - } - syn::Data::Enum(e) => { - let mut patterns = Vec::new(); - for variant in &e.variants { - let variant_ident = &variant.ident; - match &variant.fields { - syn::Fields::Named(fields) => { - let ids = fields - .named - .iter() - .map(|x| x.ident.as_ref().expect("Field without ident")) - .collect::>(); - let loads = ids.iter().map(|id| { - quote! { - ::bones_bevy_asset::BonesBevyAssetLoad::load(#id, load_context, dependencies); - } - }); - - patterns.push(quote! { - Self::#variant_ident { #(#ids,)* } => { - #(#loads)* - } - }); - } - syn::Fields::Unnamed(fields) => { - let ids = fields - .unnamed - .iter() - .enumerate() - .map(|(i, _)| format_ident!("field_{}", i)) - .collect::>(); - let loads = ids.iter().map(|id| { - quote! { - ::bones_bevy_asset::BonesBevyAssetLoad::load(#id, load_context, dependencies); - } - }); - - patterns.push(quote! { - Self::#variant_ident(#(#ids)*) => { - #(#loads)* - } - }); - } - syn::Fields::Unit => patterns.push(quote! { - Self::#variant_ident => (), - }), - } - } - - field_loads.push(quote! { - match self { - #(#patterns)* - } - }); - } - syn::Data::Union(_) => { - return quote_spanned! { input.ident.span() => - compile_error!("Deriving not supported on unions"); - }; - } - }; - - quote! { - impl ::bones_bevy_asset::BonesBevyAssetLoad for #item_ident { - fn load( - &mut self, - load_context: &mut bevy::asset::LoadContext, - dependencies: &mut Vec>, - ) { - #(#field_loads)* - } - } - } -} diff --git a/crates/bones_bevy_asset/src/lib.rs b/crates/bones_bevy_asset/src/lib.rs deleted file mode 100644 index 126a2243fd..0000000000 --- a/crates/bones_bevy_asset/src/lib.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! An asset integration between Bevy and bones. -//! -//! Provides an easy way to load metadata for bones games using Bevy assets. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -use std::marker::PhantomData; -use std::time::Duration; - -use bevy_app::App; -use bevy_asset::{prelude::*, Asset}; - -/// The prelude. -pub mod prelude { - pub use crate::*; - pub use bones_lib::prelude as bones; - pub use type_ulid::TypeUlid; -} - -use bones_bevy_utils::BevyWorld; -use bones_lib::prelude::Color; -use prelude::*; - -pub use bones_bevy_asset_macros::{BonesBevyAsset, BonesBevyAssetLoad}; - -#[doc(hidden)] -pub mod _private { - pub use serde_json; - pub use serde_yaml; -} - -/// Trait that may be derived to implement a Bevy asset type. -// TODO: Integrate or move `HasLoadProgress` to `BonesBevyAsset`. -pub trait BonesBevyAsset: TypeUlid + Asset { - /// Install the asset loader for this type. - fn install_asset(app: &mut App); -} - -/// Extension trait for [`App`] that makes it easy to register bones assets. -pub trait BonesBevyAssetAppExt { - /// Adds a [`BonesBevyAsset`] to the app, including it's asset loader. - fn add_bones_asset(&mut self) -> &mut Self; -} - -impl BonesBevyAssetAppExt for App { - fn add_bones_asset(&mut self) -> &mut Self { - T::install_asset(self); - - self - } -} - -/// Trait implemented for types that may appear in the fields of a [`BonesBevyAsset`] and may need -/// to perform aditional loading with the bevy load context. -pub trait BonesBevyAssetLoad { - /// Allows the field to do any extra loading that it might need to do from the Bevy load context - /// when the asset is loaded. - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - let _ = (load_context, dependencies); - } -} - -impl BonesBevyAssetLoad for Duration {} - -impl BonesBevyAssetLoad for Color {} - -impl BonesBevyAssetLoad for bones::Handle { - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - // Convert this path to a path relative to the parent asset - self.path.normalize_relative_to(load_context.path()); - - // Create a bevy asset path from this bones handle - let asset_path = self.path.get_bevy_asset_path(); - let path_id = asset_path.get_id(); - dependencies.push(asset_path); - - // Load the asset - let handle = load_context.get_handle::<_, DummyAsset>(path_id); - - // Leak the strong handle so that the asset doesn't get unloaded - std::mem::forget(handle); - } -} - -impl BonesBevyAssetLoad for Vec { - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - self.iter_mut() - .for_each(|x| x.load(load_context, dependencies)) - } -} - -impl BonesBevyAssetLoad for Option { - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - if let Some(x) = self.as_mut() { - x.load(load_context, dependencies) - } - } -} - -impl BonesBevyAssetLoad for std::collections::HashMap { - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - self.iter_mut() - .for_each(|(_k, v)| v.load(load_context, dependencies)) - } -} - -impl BonesBevyAssetLoad for bevy_utils::HashMap { - fn load( - &mut self, - load_context: &mut bevy_asset::LoadContext, - dependencies: &mut Vec>, - ) { - self.iter_mut() - .for_each(|(_k, v)| v.load(load_context, dependencies)) - } -} - -/// Helper make empty load implementations for a list of types. -macro_rules! impl_default_traits { - ( $($type:ty),* $(,)? ) => { - $( - impl BonesBevyAssetLoad for $type {} - )* - }; -} - -// Implement for types that don't need special loading behavior. -impl_default_traits!( - String, - f32, - f64, - usize, - u8, - u16, - u32, - u64, - u128, - i8, - i16, - i32, - i64, - i128, - glam::Vec2, - glam::Vec3, - glam::UVec2, - bool, - bones_lib::prelude::Key -); - -/// Bones [`SystemParam`][bones_lib::ecs::system::SystemParam] for borrowing bevy -/// [`Assets`][bevy_asset::Assets] from the [`BevyWorld`] resource. -pub struct BevyAssets<'a, T: bevy_asset::Asset> { - cell: bones::AtomicRef<'a, BevyWorld>, - _phantom: PhantomData, -} -impl<'a, T: bevy_asset::Asset> std::ops::Deref for BevyAssets<'a, T> { - type Target = bevy_asset::Assets; - fn deref(&self) -> &Self::Target { - self.cell - .as_ref() - .expect("Bevy world not present in `BevyWorld` resource.") - .resource::>() - } -} - -impl<'a, T: bevy_asset::Asset> bones_lib::ecs::system::SystemParam for BevyAssets<'a, T> { - type State = bones::AtomicResource; - type Param<'s> = BevyAssets<'s, T>; - - fn initialize(_world: &mut bones::World) {} - - fn get_state(world: &bones::World) -> Self::State { - world.resource::() - } - - fn borrow(state: &mut Self::State) -> Self::Param<'_> { - BevyAssets { - cell: state.borrow(), - _phantom: PhantomData, - } - } -} - -/// Dummy asset needed as a type parameter for the `load_context.get_handle` method that doesn't -/// have an untyped equivalent. -#[derive(bevy_reflect::TypeUuid)] -#[uuid = "ece514f7-4ffe-4251-9c25-d568acd696eb"] -struct DummyAsset; diff --git a/crates/bones_bevy_renderer/Cargo.toml b/crates/bones_bevy_renderer/Cargo.toml deleted file mode 100644 index 6719313f95..0000000000 --- a/crates/bones_bevy_renderer/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Bevy plugin for rendering bones_lib games." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_bevy_renderer" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bones_bevy_asset = { version = "^0.2.0", path = "../bones_bevy_asset" } -bones_lib = { version = "^0.2.0", path = "../../", features = ["bevy"] } -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -glam = { version = "0.23", features = ["serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_yaml = "0.9" -# TODO: Update `bevy_simple_tilemap` when our PR is merged. -# https://github.com/forbjok/bevy_simple_tilemap/pull/9 -bevy_prototype_lyon = "0.8" -# Disable default features to remove rayon par-iter, is not performant. Once https://github.com/forbjok/bevy_simple_tilemap/pull/17 is merged, -# switch off fork back to original crate. -bevy_simple_tilemap = {git = "https://github.com/MaxCWhitehead/bevy_simple_tilemap.git", default-features = false, branch = "v0.11.0"} - - -[dependencies.bevy] -default-features = false -features = ["bevy_render", "bevy_core_pipeline", "bevy_sprite"] -version = "0.10" diff --git a/crates/bones_bevy_renderer/examples/debug_rendering.rs b/crates/bones_bevy_renderer/examples/debug_rendering.rs deleted file mode 100644 index 5f32d416f2..0000000000 --- a/crates/bones_bevy_renderer/examples/debug_rendering.rs +++ /dev/null @@ -1,93 +0,0 @@ -use bevy::prelude::*; -use bones_bevy_renderer::*; -use bones_lib::prelude as bones; - -#[derive(Deref, DerefMut, Resource)] -struct Session(pub bones_lib::prelude::World); - -impl HasBonesWorld for Session { - fn world(&mut self) -> &mut bones::World { - &mut self.0 - } -} - -pub fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_plugin(BonesRendererPlugin::::new()) - .add_startup_system(setup) - .add_system(spin) - .run(); -} - -/// Setup the game, loading the metadata and starting the game session. -fn setup(mut commands: Commands) { - let mut world = bones::World::new(); - - commands.spawn(Camera2dBundle::default()); - - world - .run_system( - |mut entities: bones::ResMut, - mut path2ds: bones::CompMut, - mut transforms: bones::CompMut| { - const SIZE: f32 = 100.0; - - // Draw a red square - let ent = entities.create(); - path2ds.insert( - ent, - bones::Path2d { - color: bones::Color::RED, - points: vec![ - glam::vec2(-SIZE, -SIZE), - glam::vec2(SIZE, -SIZE), - glam::vec2(SIZE, SIZE), - glam::vec2(-SIZE, SIZE), - glam::vec2(-SIZE, -SIZE), - ], - thickness: 2.0, - ..default() - }, - ); - transforms.insert(ent, default()); - - const SIZE2: f32 = SIZE / 2.0; - - // Draw two blue lines - let ent = entities.create(); - path2ds.insert( - ent, - bones::Path2d { - color: bones::Color::BLUE, - points: vec![ - // The first line - glam::vec2(-SIZE2, -SIZE2), - glam::vec2(SIZE2, -SIZE2), - // The second line - glam::vec2(SIZE2, SIZE2), - glam::vec2(-SIZE2, SIZE2), - ], - thickness: 4.0, - // This means that it won't connect points with indexes 2 and 3, so that - // they will be separate lines. - line_breaks: vec![2], - }, - ); - transforms.insert(ent, default()); - }, - ) - .unwrap(); - - commands.insert_resource(Session(world)); -} - -fn spin(session: ResMut) { - session - .run_initialized_system(|mut transforms: bones::CompMut| { - transforms.iter_mut().for_each(|trans| { - trans.rotation *= Quat::from_axis_angle(Vec3::Z, f32::to_radians(0.1)) - }); - }) - .unwrap(); -} diff --git a/crates/bones_bevy_renderer/src/asset.rs b/crates/bones_bevy_renderer/src/asset.rs deleted file mode 100644 index 71b0c4d729..0000000000 --- a/crates/bones_bevy_renderer/src/asset.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::ffi::OsStr; - -use bevy::{asset::LoadedAsset, sprite::TextureAtlas}; -use bones_bevy_asset::BonesBevyAssetLoad; -use glam::Vec2; - -/// The YAML/JSON metadata format for texture atlases -#[derive(serde::Deserialize)] -pub struct AtlasMeta { - pub image: bones_lib::asset::Handle, - pub tile_size: Vec2, - pub columns: usize, - pub rows: usize, - #[serde(default)] - pub padding: Option, - #[serde(default)] - pub offset: Option, -} - -/// An asset loader for [`TextureAtlas`]s from JSON or YAML. -pub struct TextureAtlasLoader; - -impl bevy::asset::AssetLoader for TextureAtlasLoader { - fn load<'a>( - &'a self, - bytes: &'a [u8], - load_context: &'a mut bevy::asset::LoadContext, - ) -> bevy::utils::BoxedFuture<'a, Result<(), bevy::asset::Error>> { - Box::pin(async move { - let self_path = &load_context.path().to_owned(); - let mut dependencies = Vec::with_capacity(1); - - let mut meta: AtlasMeta = if self_path.extension() == Some(OsStr::new("json")) { - serde_json::from_slice(bytes)? - } else { - serde_yaml::from_slice(bytes)? - }; - - meta.image.load(load_context, &mut dependencies); - - load_context.set_default_asset( - LoadedAsset::new(TextureAtlas::from_grid( - meta.image.get_bevy_handle_untyped().typed(), - meta.tile_size, - meta.columns, - meta.rows, - meta.padding, - meta.offset, - )) - .with_dependencies(dependencies), - ); - - Ok(()) - }) - } - - fn extensions(&self) -> &[&str] { - &["atlas.json", "atlas.yml", "atlas.yaml"] - } -} diff --git a/crates/bones_bevy_renderer/src/lib.rs b/crates/bones_bevy_renderer/src/lib.rs deleted file mode 100644 index e13c0e02e7..0000000000 --- a/crates/bones_bevy_renderer/src/lib.rs +++ /dev/null @@ -1,600 +0,0 @@ -//! Bevy plugin for rendering Bones framework games. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -use std::marker::PhantomData; - -use bevy::{prelude::*, render::camera::ScalingMode}; -use bevy_prototype_lyon::prelude as lyon; -use bevy_simple_tilemap::{prelude::TileMapBundle, Tile, TileFlags, TileMap}; -use bones_lib::prelude::{self as bones, BitSet, IntoBevy}; - -/// The prelude -pub mod prelude { - pub use crate::*; -} - -mod asset; - -/// This is a trait that must be implemented for your Bevy resource containing the bones -/// [`World`][bones::World]. -/// -/// It gives the [`BonesRendererPlugin`] a way to know how to read the bones world from your world -/// resource. -pub trait HasBonesWorld: Resource { - /// Return a mutable reference to the bones world stored by the resource. - fn world(&mut self) -> &mut bones::World; -} - -/// The bones renderer plugin. -/// -/// This will render the bones world stored in the resource of type `W`. -pub struct BonesRendererPlugin { - /// Whether or not to synchronize the [`Time`] resource automatically with Bevy. - /// - /// This defaults to `true`, but may be set to `false` if you use a custom time step. - pub sync_time: bool, - #[doc(hidden)] - pub _phantom: PhantomData, -} - -impl BonesRendererPlugin { - /// Initialize the plugin with the `sync_time` option set to the provided value. - pub fn with_sync_time(sync_time: bool) -> Self { - Self { - sync_time, - _phantom: PhantomData, - } - } -} - -impl Default for BonesRendererPlugin { - fn default() -> Self { - Self { - sync_time: true, - _phantom: default(), - } - } -} - -impl BonesRendererPlugin { - /// Create a new [`BonesRendererPlugin`] instance. - pub fn new() -> Self { - default() - } -} - -/// Marker component for entities that are rendered in Bevy for bones. -#[derive(Component)] -pub struct BevyBonesEntity; - -/// [`SystemSet`] marker for sets added by bones to the Bevy world. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -#[system_set(base)] -pub enum BonesStage { - /// This stage is run after [`CoreSet::First`] to synchronize the bevy `Time` resource with - /// the bones one. - SyncTime, - /// This is the stage where the plugin reads the bones world adds bevy sprites, tiles, etc. to - /// be rendered. - SyncRender, -} - -impl Plugin for BonesRendererPlugin { - fn build(&self, app: &mut App) { - // Configure the bones stages - app.configure_set(BonesStage::SyncTime.after(CoreSet::First)) - .configure_set(BonesStage::SyncRender.before(CoreSet::Update)); - - app.add_plugin(bevy_simple_tilemap::plugin::SimpleTileMapPlugin) - .add_plugin(lyon::ShapePlugin) - // Install the asset loader for .atlas.yaml files. - .add_asset_loader(asset::TextureAtlasLoader) - // Add the world sync systems - .add_systems( - ( - sync_sprites::, - sync_cameras::, - sync_path2ds::, - sync_tilemaps::, - sync_clear_color::, - sync_atlas_sprites::, - ) - .in_base_set(BonesStage::SyncRender), - ); - - if self.sync_time { - app.add_system(sync_time::.in_base_set(BonesStage::SyncTime)); - } - } -} - -fn sync_clear_color( - mut clear_color: ResMut, - world_resource: Option>, -) { - let Some(mut world_resource) = world_resource else { - return; - }; - let world = world_resource.world(); - world.init_resource::(); - - let bones_clear_color = world.resource::(); - let bones_clear_color = bones_clear_color.borrow(); - - clear_color.0 = bones_clear_color.0.into_bevy() -} - -/// The system that renders the bones world. -fn sync_sprites( - mut commands: Commands, - world_resource: Option>, - mut bevy_bones_sprites: Query< - (Entity, &mut Handle, &mut Sprite, &mut Transform), - With, - >, -) { - let Some(mut world_resource) = world_resource else { - bevy_bones_sprites.for_each(|(e, ..)| commands.entity(e).despawn()); - return; - }; - - let world = world_resource.world(); - - // TODO: Evaluate cost of initializing bones render components every frame. - world.components.init::(); - world.components.init::(); - - let entities = world.resource::(); - let entities = entities.borrow(); - let sprites = world.components.get::(); - let sprites = sprites.borrow(); - let transforms = world.components.get::(); - let transforms = transforms.borrow(); - - // Sync sprites - let mut sprites_bitset = sprites.bitset().clone(); - sprites_bitset.bit_and(transforms.bitset()); - let mut bones_sprite_entity_iter = entities.iter_with_bitset(&sprites_bitset); - for (bevy_ent, mut image, mut sprite, mut transform) in &mut bevy_bones_sprites { - if let Some(bones_ent) = bones_sprite_entity_iter.next() { - let bones_sprite = sprites.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - sprite.flip_x = bones_sprite.flip_x; - sprite.flip_y = bones_sprite.flip_y; - sprite.color = bones_sprite.color.into_bevy(); - *image = bones_sprite.image.get_bevy_handle_untyped().typed(); - *transform = bones_transform.into_bevy(); - } else { - commands.entity(bevy_ent).despawn(); - } - } - for bones_ent in bones_sprite_entity_iter { - let bones_sprite = sprites.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - commands.spawn(( - SpriteBundle { - texture: bones_sprite.image.get_bevy_handle_untyped().typed(), - transform: bones_transform.into_bevy(), - ..default() - }, - BevyBonesEntity, - )); - } -} - -/// The system that renders the bones world. -fn sync_atlas_sprites( - mut commands: Commands, - world_resource: Option>, - mut bevy_bones_atlases: Query< - ( - Entity, - &mut Handle, - &mut TextureAtlasSprite, - &mut Transform, - ), - With, - >, -) { - let Some(mut world_resource) = world_resource else { - bevy_bones_atlases.for_each(|(e, ..)| commands.entity(e).despawn()); - return; - }; - - let world = world_resource.world(); - - world.components.init::(); - world.components.init::(); - - let entities = world.resource::(); - let entities = entities.borrow(); - let atlas_sprites = world.components.get::(); - let atlas_sprites = atlas_sprites.borrow(); - let transforms = world.components.get::(); - let transforms = transforms.borrow(); - - // Sync atlas sprites - let mut atlas_bitset = atlas_sprites.bitset().clone(); - atlas_bitset.bit_and(transforms.bitset()); - let mut bones_atlas_sprite_entity_iter = entities.iter_with_bitset(&atlas_bitset); - for (bevy_ent, mut image, mut atlas_sprite, mut transform) in &mut bevy_bones_atlases { - if let Some(bones_ent) = bones_atlas_sprite_entity_iter.next() { - let bones_atlas = atlas_sprites.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - *image = bones_atlas.atlas.get_bevy_handle_untyped().typed(); - *transform = bones_transform.into_bevy(); - - atlas_sprite.index = bones_atlas.index; - atlas_sprite.flip_x = bones_atlas.flip_x; - atlas_sprite.flip_y = bones_atlas.flip_y; - atlas_sprite.color = bones_atlas.color.into_bevy(); - } else { - commands.entity(bevy_ent).despawn(); - } - } - for bones_ent in bones_atlas_sprite_entity_iter { - let bones_atlas = atlas_sprites.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - commands.spawn(( - SpriteSheetBundle { - texture_atlas: bones_atlas.atlas.get_bevy_handle_untyped().typed(), - transform: bones_transform.into_bevy(), - ..default() - }, - BevyBonesEntity, - )); - } -} - -/// The system that renders the bones world. -fn sync_cameras( - mut commands: Commands, - world_resource: Option>, - mut bevy_bones_cameras: Query< - ( - Entity, - &mut Camera, - &mut OrthographicProjection, - &mut Transform, - ), - With, - >, -) { - let Some(mut world_resource) = world_resource else { - bevy_bones_cameras.for_each(|(e, ..)| commands.entity(e).despawn()); - return; - }; - - let world = world_resource.world(); - - world.components.init::(); - world.components.init::(); - - let entities = world.resource::(); - let entities = entities.borrow(); - let transforms = world.components.get::(); - let transforms = transforms.borrow(); - let cameras = world.components.get::(); - let cameras = cameras.borrow(); - - // Sync cameras - let mut cameras_bitset = cameras.bitset().clone(); - cameras_bitset.bit_and(transforms.bitset()); - let mut bones_camera_entity_iter = entities.iter_with_bitset(&cameras_bitset); - for (bevy_ent, mut camera, mut projection, mut transform) in &mut bevy_bones_cameras { - if let Some(bones_ent) = bones_camera_entity_iter.next() { - let bones_camera = cameras.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - camera.is_active = bones_camera.active; - match projection.scaling_mode { - ScalingMode::FixedVertical(height) if height != bones_camera.height => { - projection.scaling_mode = ScalingMode::FixedVertical(bones_camera.height) - } - _ => (), - } - camera.viewport = bones_camera - .viewport - .map(|x| bevy::render::camera::Viewport { - physical_position: x.position, - physical_size: x.size, - depth: x.depth_min..x.depth_max, - }); - - *transform = bones_transform.into_bevy(); - } else { - commands.entity(bevy_ent).despawn(); - } - } - for bones_ent in bones_camera_entity_iter { - let bones_camera = cameras.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - commands.spawn(( - Camera2dBundle { - camera: Camera { - is_active: bones_camera.active, - ..default() - }, - projection: OrthographicProjection { - scaling_mode: ScalingMode::FixedVertical(bones_camera.height), - ..default() - }, - transform: bones_transform.into_bevy(), - ..default() - }, - BevyBonesEntity, - )); - } -} - -fn sync_tilemaps( - mut commands: Commands, - world_resource: Option>, - mut bevy_bones_tile_layers: Query< - ( - Entity, - &mut TileMap, - &mut Handle, - &mut Transform, - ), - With, - >, - atlas_assets: Res>, -) { - let Some(mut world_resource) = world_resource else { - bevy_bones_tile_layers.for_each(|(e, ..)| commands.entity(e).despawn()); - return; - }; - - let world = world_resource.world(); - - world.components.init::(); - world.components.init::(); - - let entities = world.resource::(); - let entities = entities.borrow(); - let tiles = world.components.get::(); - let tiles = tiles.borrow(); - let tile_layers = world.components.get::(); - let tile_layers = tile_layers.borrow(); - let transforms = world.components.get::(); - let transforms = transforms.borrow(); - - // Sync tile layers - let mut tile_layers_bitset = tile_layers.bitset().clone(); - tile_layers_bitset.bit_and(transforms.bitset()); - - let mut bones_tile_layer_entity_iter = entities.iter_with_bitset(&tile_layers_bitset); - for (bevy_ent, mut tile_map, mut atlas, mut transform) in &mut bevy_bones_tile_layers { - if let Some(bones_ent) = bones_tile_layer_entity_iter.next() { - let bones_tile_layer = tile_layers.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - *atlas = bones_tile_layer.atlas.get_bevy_handle_untyped().typed(); - *transform = bones_transform.into_bevy(); - transform.translation += bones_tile_layer.tile_size.extend(0.0) / 2.0; - - let Some(texture_atlas) = atlas_assets.get(&atlas) else { continue; }; - let atlas_grid_size = texture_atlas.size / texture_atlas.textures[0].size(); - let max_tile_idx = (atlas_grid_size.x * atlas_grid_size.y) as u32 - 1; - - let grid_size = bones_tile_layer.grid_size; - let tile_iter = bones_tile_layer - .tiles - .iter() - .enumerate() - .map(|(idx, entity)| { - let y = idx as u32 / grid_size.x; - let x = idx as u32 - (y * grid_size.x); - let tile = entity - .map(|e| { - let tile = tiles.get(e)?; - Some(Tile { - sprite_index: (tile.idx as u32).min(max_tile_idx), - color: default(), - flags: if tile.flip_x { - TileFlags::FLIP_X - } else { - TileFlags::empty() - } | if tile.flip_y { - TileFlags::FLIP_Y - } else { - TileFlags::empty() - }, - }) - }) - .flatten(); - (IVec3::new(x as i32, y as i32, 0), tile) - }); - - tile_map.clear(); - tile_map.set_tiles(tile_iter); - - // This is maybe a bug in bevy_simple_tilemap. If the tilemap atlas has been changed, - // and one of the tiles in the map had a tile index greater than the max tile count in - // the new atlas, the map renderer will panic. - // - // This shouldn't happen because we made sure to `clear()` the tiles and ensured that - // all the new tile indexes are clamped, but apparently the chunks are updated a frame - // late or otherwise just evaluated before our tile changes take effect, so we must - // clamp the tiles indexes directly on the chunks as well. - tile_map.chunks.iter_mut().for_each(|(_, chunk)| { - chunk - .tiles - .iter_mut() - .flatten() - .for_each(|x| x.sprite_index = x.sprite_index.min(max_tile_idx)) - }); - } else { - commands.entity(bevy_ent).despawn(); - } - } - for bones_ent in bones_tile_layer_entity_iter { - let bones_tile_layer = tile_layers.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - let mut tile_map = TileMap::default(); - - let grid_size = bones_tile_layer.grid_size; - let tile_iter = bones_tile_layer - .tiles - .iter() - .enumerate() - .map(|(idx, entity)| { - let y = idx as u32 / grid_size.x; - let x = idx as u32 - (y * grid_size.x); - let tile = entity - .map(|e| { - let tile = tiles.get(e)?; - Some(Tile { - sprite_index: tile.idx as _, - color: default(), - flags: if tile.flip_x { - TileFlags::FLIP_X - } else { - TileFlags::empty() - } | if tile.flip_y { - TileFlags::FLIP_Y - } else { - TileFlags::empty() - }, - }) - }) - .flatten(); - (IVec3::new(x as i32, y as i32, 0), tile) - }); - - tile_map.set_tiles(tile_iter); - - let mut transform = bones_transform.into_bevy(); - transform.translation += bones_tile_layer.tile_size.extend(0.0) / 2.0; - commands.spawn(( - TileMapBundle { - tilemap: tile_map, - transform, - ..default() - }, - BevyBonesEntity, - )); - } -} - -/// The system that renders the bones world. -fn sync_path2ds( - mut commands: Commands, - world_resource: Option>, - mut bevy_bones_path2ds: Query< - (Entity, &mut lyon::Path, &mut lyon::Stroke, &mut Transform), - With, - >, -) { - let Some(mut world_resource) = world_resource else { - bevy_bones_path2ds.for_each(|(e, ..)| commands.entity(e).despawn()); - return; - }; - - let world = world_resource.world(); - - world.components.init::(); - world.components.init::(); - - let entities = world.resource::(); - let entities = entities.borrow(); - let path2ds = world.components.get::(); - let path2ds = path2ds.borrow(); - let transforms = world.components.get::(); - let transforms = transforms.borrow(); - - fn get_bevy_components( - bones_path2d: &bones::Path2d, - bones_transform: &bones::Transform, - ) -> (lyon::Stroke, lyon::Path, Transform) { - let stroke = lyon::Stroke::new(bones_path2d.color.into_bevy(), bones_path2d.thickness); - let new_path = bones_path2d - .points - .iter() - .copied() - .enumerate() - .fold(lyon::PathBuilder::new(), |mut builder, (i, point)| { - if i > 0 && !bones_path2d.line_breaks.contains(&i) { - builder.line_to(point); - } - builder.move_to(point); - - builder - }) - .build(); - - let mut transform = bones_transform.into_bevy(); - // Offset the path towards the camera slightly to make sure it renders on top of a - // sprite/etc. if it is applied to an entity with both a sprite and a path. - transform.translation.z += 0.0001; - (stroke, new_path, transform) - } - - // Sync paths - let mut path2ds_bitset = path2ds.bitset().clone(); - path2ds_bitset.bit_and(transforms.bitset()); - let mut bones_sprite_entity_iter = entities.iter_with_bitset(&path2ds_bitset); - for (bevy_ent, mut path, mut draw_mode, mut transform) in &mut bevy_bones_path2ds { - if let Some(bones_ent) = bones_sprite_entity_iter.next() { - let bones_path2d = path2ds.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - (*draw_mode, *path, *transform) = get_bevy_components(bones_path2d, bones_transform); - } else { - commands.entity(bevy_ent).despawn(); - } - } - for bones_ent in bones_sprite_entity_iter { - let bones_path2d = path2ds.get(bones_ent).unwrap(); - let bones_transform = transforms.get(bones_ent).unwrap(); - - let (stroke, path, transform) = get_bevy_components(bones_path2d, bones_transform); - - commands.spawn(( - lyon::ShapeBundle { - path, - transform, - ..default() - }, - stroke, - BevyBonesEntity, - )); - } -} - -/// The system that renders the bones world. -fn sync_time( - world_resource: Option>, - bevy_time: Res, -) { - let Some(mut world_resource) = world_resource else { - return; - }; - let world = world_resource.world(); - - // Initialize the time resource if it doesn't exist. - if world.get_resource::().is_none() { - world.init_resource::(); - } - - let time = world.resource::(); - let mut time = time.borrow_mut(); - - // Use the Bevy time if it's available, otherwise use the default time. - if let Some(instant) = bevy_time.last_update() { - time.update_with_instant(instant); - } else { - time.update(); - } -} diff --git a/crates/bones_bevy_utils/CHANGELOG.md b/crates/bones_bevy_utils/CHANGELOG.md deleted file mode 100644 index d991aefef6..0000000000 --- a/crates/bones_bevy_utils/CHANGELOG.md +++ /dev/null @@ -1,112 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2023-06-01) - -### Documentation - - - update changelogs. - -### New Features - - - upgrade to Bevy 0.10. - -### Commit Statistics - - - - - 2 commits contributed to the release. - - 133 days passed between releases. - - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - - 2 unique issues were worked on: [#122](https://github.com/fishfolk/bones/issues/122), [#124](https://github.com/fishfolk/bones/issues/124) - -### Commit Details - - - -
view details - - * **[#122](https://github.com/fishfolk/bones/issues/122)** - - upgrade to Bevy 0.10. ([`3f2e348`](https://github.com/fishfolk/bones/commit/3f2e3485f9556cc68eb4c04df34d3aa2c6087330)) - * **[#124](https://github.com/fishfolk/bones/issues/124)** - - update changelogs. ([`3f18051`](https://github.com/fishfolk/bones/commit/3f18051e023a4deb676a5f895f1478beda513f04)) -
- -## 0.1.0 (2023-01-18) - - - - - - -### Chore - - - add missing crate descriptions. - -### Chore - - - generate changelogs for all crates. - -### Documentation - - - document source repository in cargo manifest. - The `repository` key under `bones_ecs` previously pointed to https://github.com/fishfolk/jumpy. - - This updates this to point to the bones repo, and also adds the `repository` key to the other - crates in the repository. - -### New Features (BREAKING) - - - add asset integration with bevy. - This is a big overall change that adds ways to integrate Bones with bevy assets. - -### Refactor (BREAKING) - - - prepare for release. - - Remove `bones_has_load_progress`: for now we don't use it, and if we - want something similar we will work it into `bones_bevy_asset`. - - Remove `bones_camera_shake`: it was moved into `bones_lib::camera`. - - Add version numbers for all local dependencies. - - make world in `BevyWorld` resource optional. - Since the bevy world can't be cloned, we previously had it in - an Arc, but that didn't play nicely with world snapshots. - - Now the bevy world inside the `BevyWorld` resource is an - option, with the `BevyAssets` system param panicking if it - doesn't find the world when it needs it. - -### Commit Statistics - - - - - 8 commits contributed to the release over the course of 14 calendar days. - - 6 commits were understood as [conventional](https://www.conventionalcommits.org). - - 6 unique issues were worked on: [#29](https://github.com/fishfolk/bones/issues/29), [#37](https://github.com/fishfolk/bones/issues/37), [#41](https://github.com/fishfolk/bones/issues/41), [#63](https://github.com/fishfolk/bones/issues/63), [#65](https://github.com/fishfolk/bones/issues/65), [#67](https://github.com/fishfolk/bones/issues/67) - -### Commit Details - - - -
view details - - * **[#29](https://github.com/fishfolk/bones/issues/29)** - - add asset integration with bevy. ([`89b44d7`](https://github.com/fishfolk/bones/commit/89b44d7b4f64ec266eb0ea674c220e07376a03b7)) - * **[#37](https://github.com/fishfolk/bones/issues/37)** - - document source repository in cargo manifest. ([`a693894`](https://github.com/fishfolk/bones/commit/a69389412d22b8cb48bab0ed96d739b0fee35348)) - * **[#41](https://github.com/fishfolk/bones/issues/41)** - - make world in `BevyWorld` resource optional. ([`ef12c3f`](https://github.com/fishfolk/bones/commit/ef12c3fb681cc826199b1564e1a033a56a5ce2d4)) - * **[#63](https://github.com/fishfolk/bones/issues/63)** - - prepare for release. ([`ae0a761`](https://github.com/fishfolk/bones/commit/ae0a761fc9b82ba2fc639c2b6f7af09fb650cd31)) - * **[#65](https://github.com/fishfolk/bones/issues/65)** - - add missing crate descriptions. ([`2725246`](https://github.com/fishfolk/bones/commit/27252465ad0506ff2f8c377531fa079ec64d1750)) - * **[#67](https://github.com/fishfolk/bones/issues/67)** - - generate changelogs for all crates. ([`a68cb79`](https://github.com/fishfolk/bones/commit/a68cb79e6b7d3774c53c0236edf3a12175f297b5)) - * **Uncategorized** - - Release type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`69713d7`](https://github.com/fishfolk/bones/commit/69713d7da8024ee4b3017b563f031880009c90ee)) - - Release type_ulid_macros v0.1.0, type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`db0333d`](https://github.com/fishfolk/bones/commit/db0333ddacb6f29aed8664db67973e72ea586dce)) -
- diff --git a/crates/bones_bevy_utils/Cargo.toml b/crates/bones_bevy_utils/Cargo.toml deleted file mode 100644 index 721cffdc84..0000000000 --- a/crates/bones_bevy_utils/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Utilities for using Bones with Bevy." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_bevy_utils" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bevy_ecs = "0.10" -type_ulid = { version = "^0.2.0", path = "../type_ulid" } diff --git a/crates/bones_bevy_utils/src/lib.rs b/crates/bones_bevy_utils/src/lib.rs deleted file mode 100644 index 867cdf3d52..0000000000 --- a/crates/bones_bevy_utils/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Bevy plugin for rendering Bones framework games. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -use type_ulid::TypeUlid; - -/// The prelude. -pub mod prelude { - pub use crate::*; -} - -/// Helper trait for converting bones types to Bevy types. -pub trait IntoBevy { - /// Convert the type to a Bevy type. - fn into_bevy(self) -> To; -} - -/// Resource that contains a bevy world. -/// -/// This may be used to give the bones ECS direct access to the bevy world. -/// -/// One way to do this is to [`std::mem::swap`] an empty world in the [`BevyWorld`]` resource, with -/// the actual Bevy world, immediatley before running the bones ECS systems. Then you can swap it -/// back once the bones systems finish. -#[derive(TypeUlid, Default)] -#[ulid = "01GNX5CJAAHS31DA9HXZ2CF74B"] -pub struct BevyWorld(pub Option); - -impl Clone for BevyWorld { - fn clone(&self) -> Self { - if self.0.is_some() { - panic!("BevyWorld may not be cloned."); - } else { - Self(None) - } - } -} - -impl std::ops::Deref for BevyWorld { - type Target = Option; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for BevyWorld { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/crates/bones_ecs/Cargo.toml b/crates/bones_ecs/Cargo.toml deleted file mode 100644 index 275f8b0757..0000000000 --- a/crates/bones_ecs/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -authors = [ - "Joël Lupien (Jojolepro) ", - "Fish Folk & Spicy Lobster Developers", -] -categories = ["game-engines"] -description = "A tiny but very powerful ECS framework." -edition = "2021" -keywords = ["game", "ecs"] -license = "Apache-2.0" -name = "bones_ecs" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[features] -default = ["keysize16"] - -keysize16 = [] -keysize20 = [] -keysize24 = [] -keysize32 = [] - -[dependencies] -aligned-vec = "0.5" -anyhow = "1.0" -atomic_refcell = "0.1" -# TODO: Replace Bevy's `Deref` and `DerefMut` derives with our own macros. -bevy_derive = "0.10" -bitset-core = "0.1" -bytemuck = "1.12" -either = "1.8" -fxhash = "0.2" -itertools = "0.10" -serde = { version = "1.0", features = ["derive"], optional = true } -thiserror = "1.0" -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -[dev-dependencies] -glam = "0.23" diff --git a/crates/bones_ecs/src/components.rs b/crates/bones_ecs/src/components.rs deleted file mode 100644 index ac92a33262..0000000000 --- a/crates/bones_ecs/src/components.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! ECS component storage. - -use std::{any::TypeId, sync::Arc}; - -use crate::prelude::*; - -mod iterator; -mod typed; -mod untyped; - -pub use iterator::*; -pub use typed::*; -pub use untyped::*; - -/// Makes sure that the component type `T` matches the component type previously registered with -/// the same UUID. -fn validate_type_uuid_match( - type_ids: &UlidMap, -) -> Result<(), EcsError> { - if type_ids.get(&T::ULID).ok_or(EcsError::NotInitialized)? != &TypeId::of::() { - Err(EcsError::TypeUlidCollision) - } else { - Ok(()) - } -} - -/// A collection of [`ComponentStore`]. -/// -/// [`ComponentStores`] is used to in [`World`] to store all component types that have been -/// initialized for that world. -#[derive(Default)] -pub struct ComponentStores { - pub(crate) components: UlidMap>>, - type_ids: UlidMap, -} - -impl Clone for ComponentStores { - fn clone(&self) -> Self { - Self { - components: self - .components - .iter() - // Be sure to clone the inner data of the components, so we don't just end up with - // new `Arc`s pointing to the same data. - .map(|(&k, v)| (k, Arc::new((**v).clone()))) - .collect(), - type_ids: self.type_ids.clone(), - } - } -} - -impl ComponentStores { - /// Initialize component storage for type `T`. - pub fn init(&mut self) { - self.try_init::().unwrap(); - } - - /// Initialize component storage for type `T`. - pub fn try_init( - &mut self, - ) -> Result<(), EcsError> { - match self.components.entry(T::ULID) { - std::collections::hash_map::Entry::Occupied(_) => { - validate_type_uuid_match::(&self.type_ids) - } - std::collections::hash_map::Entry::Vacant(entry) => { - entry.insert(Arc::new(AtomicRefCell::new( - UntypedComponentStore::for_type::(), - ))); - self.type_ids.insert(T::ULID, TypeId::of::()); - - Ok(()) - } - } - } - - /// Get the components of a certain type - /// - /// # Panics - /// - /// Panics if the component type has not been initialized. - pub fn get(&self) -> AtomicComponentStore { - self.try_get::().unwrap() - } - - /// Get the components of a certain type - pub fn try_get( - &self, - ) -> Result, EcsError> { - validate_type_uuid_match::(&self.type_ids)?; - let untyped = self.try_get_by_uuid(T::ULID)?; - - // Safe: We've made sure that the data initialized in the untyped components matches T - unsafe { Ok(AtomicComponentStore::from_components_unsafe(untyped)) } - } - - /// Get the untyped component storage by the component's UUID - /// - /// # Panics - /// - /// Panics if the component type has not been initialized. - pub fn get_by_uuid(&self, uuid: Ulid) -> Arc> { - self.try_get_by_uuid(uuid).unwrap() - } - - /// Get the untyped component storage by the component's UUID - pub fn try_get_by_uuid( - &self, - uuid: Ulid, - ) -> Result>, EcsError> { - self.components - .get(&uuid) - .cloned() - .ok_or(EcsError::NotInitialized) - } -} - -#[cfg(test)] -mod test { - use crate::prelude::*; - - #[derive(Clone, Copy, TypeUlid)] - #[ulid = "01GQNWZKBT0SN37QKJQNKPF5RR"] - struct MyData(pub i32); - - #[test] - fn borrow_many_mut() { - World::new() - .run_system( - |mut entities: ResMut, mut my_datas: CompMut| { - let ent1 = entities.create(); - let ent2 = entities.create(); - - my_datas.insert(ent1, MyData(7)); - my_datas.insert(ent2, MyData(8)); - - { - let [data2, data1] = my_datas.get_many_mut([ent2, ent1]).unwrap_many(); - - data1.0 = 0; - data2.0 = 1; - } - - assert_eq!(my_datas.get(ent1).unwrap().0, 0); - assert_eq!(my_datas.get(ent2).unwrap().0, 1); - }, - ) - .unwrap(); - } - - #[test] - #[should_panic = "must be unique"] - fn borrow_many_overlapping_mut() { - World::new() - .run_system( - |mut entities: ResMut, mut my_datas: CompMut| { - let ent1 = entities.create(); - let ent2 = entities.create(); - - my_datas.insert(ent1, MyData(1)); - my_datas.insert(ent2, MyData(2)); - - my_datas.get_many_mut([ent1, ent2, ent1]).unwrap_many(); - }, - ) - .unwrap(); - } -} diff --git a/crates/bones_ecs/src/components/typed.rs b/crates/bones_ecs/src/components/typed.rs deleted file mode 100644 index daeba7b0cf..0000000000 --- a/crates/bones_ecs/src/components/typed.rs +++ /dev/null @@ -1,343 +0,0 @@ -use std::{alloc::Layout, marker::PhantomData, rc::Rc, sync::Arc}; - -use bytemuck::Pod; - -use crate::prelude::*; - -mod ops; -pub use ops::TypedComponentOps; - -use super::{ - iterator::{ComponentBitsetIterator, ComponentBitsetIteratorMut}, - untyped::UntypedComponentStore, -}; - -/// A typed wrapper around [`UntypedComponentStore`]. -pub struct ComponentStore { - components: UntypedComponentStore, - ops: TypedComponentOps, -} - -impl Default for ComponentStore { - fn default() -> Self { - Self { - components: UntypedComponentStore::for_type::(), - // Safe: We will only use `TypedComponentOps` for the untyped components we created - // above, which was initialized for the same type T. - ops: unsafe { TypedComponentOps::::new() }, - } - } -} - -impl ComponentStore { - /// Create a new [`ComponentStore`] by wrapping an [`UntypedComponentStore`]. - /// - /// This method is safe because `T` is required to implement [`Pod`], which means `T` is valid - /// for _any_ bit pattern. - /// - /// # Panics - /// - /// This will panic if the layout of `T` does not match the layout of `components`. - pub fn from_components(components: UntypedComponentStore) -> Self { - assert_eq!( - components.layout(), - Layout::new::(), - "Layout mismatch creating `TypedComponents`" - ); - - Self { - components, - // Safe: - // - We will only use `TypedComponentOps` for the untyped components above - // - `T` is `Pod` so it is valid for any bit pattern - // - We validated the layout matches with the assertion above - ops: unsafe { TypedComponentOps::::new() }, - } - } -} - -impl ComponentStore { - /// Create a new [`ComponentStore`] by wrapping an [`UntypedComponentStore`]. - /// - /// # Safety - /// - /// The data stored in `components` data must be a valid bit pattern for the given type `T`. - /// - /// > **Note:** If `T` implements [`Pod`] you can safely create an instance of - /// > [`ComponentStore`] with [`from_components`][Self::from_components]. - pub unsafe fn from_components_unsafe(components: UntypedComponentStore) -> Self { - assert_eq!( - components.layout(), - Layout::new::(), - "Layout mismatch creating `TypedComponents`" - ); - - Self { - components, - ops: TypedComponentOps::::new(), - } - } - - /// Converts to the internal, untyped [`ComponentStore`]. - pub fn into_untyped(self) -> UntypedComponentStore { - self.components - } - - /// Inserts a component for the given `Entity` index. - /// Returns the previous component, if any. - pub fn insert(&mut self, entity: Entity, component: T) -> Option { - self.ops.insert(&mut self.components, entity, component) - } - - /// Gets an immutable reference to the component of `Entity`. - pub fn get(&self, entity: Entity) -> Option<&T> { - self.ops.get(&self.components, entity) - } - - /// Gets a mutable reference to the component of `Entity`. - pub fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { - self.ops.get_mut(&mut self.components, entity) - } - - /// Removes the component of `Entity`. - /// Returns `Some(T)` if the entity did have the component. - /// Returns `None` if the entity did not have the component. - pub fn remove(&mut self, entity: Entity) -> Option { - self.ops.remove(&mut self.components, entity) - } - - /// Iterates immutably over all components of this type. - /// Very fast but doesn't allow joining with other component types. - pub fn iter(&self) -> impl Iterator { - self.ops.iter(&self.components) - } - - /// Iterates mutably over all components of this type. - /// Very fast but doesn't allow joining with other component types. - pub fn iter_mut(&mut self) -> impl Iterator { - self.ops.iter_mut(&mut self.components) - } - - /// Iterates immutably over the components of this type where `bitset` - /// indicates the indices of entities. - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_with_bitset(&self, bitset: Rc) -> ComponentBitsetIterator { - self.ops.iter_with_bitset(&self.components, bitset) - } - - /// Iterates mutable over the components of this type where `bitset` - /// indicates the indices of entities. - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_mut_with_bitset(&mut self, bitset: Rc) -> ComponentBitsetIteratorMut { - self.ops.iter_mut_with_bitset(&mut self.components, bitset) - } - - /// Read the bitset containing the list of entites with this component type on it. - pub fn bitset(&self) -> &BitSetVec { - self.components.bitset() - } - - /// Check whether or not this component store has data for the given entity. - #[inline] - pub fn contains(&self, entity: Entity) -> bool { - self.bitset().contains(entity) - } -} - -/// A typed, wrapper handle around [`UntypedComponentStore`] that is runtime borrow checked and can -/// be cheaply cloned. Think can think of it like an `Arc>`. -#[derive(Clone)] -pub struct AtomicComponentStore { - components: Arc>, - _phantom: PhantomData, -} - -impl Default for AtomicComponentStore { - fn default() -> Self { - Self { - components: Arc::new(AtomicRefCell::new(UntypedComponentStore::for_type::())), - _phantom: Default::default(), - } - } -} - -impl AtomicComponentStore { - /// # Safety - /// - /// The [`UntypedComponentStore`] underlying data must be valid for type `T`. - pub unsafe fn from_components_unsafe( - components: Arc>, - ) -> Self { - Self { - components, - _phantom: PhantomData, - } - } - - /// Borrow the component store. - pub fn borrow(&self) -> AtomicComponentStoreRef { - AtomicComponentStoreRef { - components: self.components.borrow(), - // Safe: The component type T is the same as the one we already have - ops: unsafe { TypedComponentOps::::new() }, - } - } - - /// Mutably borrow the component store. - pub fn borrow_mut(&self) -> AtomicComponentStoreRefMut { - AtomicComponentStoreRefMut { - components: self.components.borrow_mut(), - // Safe: The construction of an [`AtomicComponents`] is unsafe, and this has the same - // invariants. - ops: unsafe { TypedComponentOps::::new() }, - } - } -} - -/// A read-only borrow of [`AtomicComponentStore`]. -pub struct AtomicComponentStoreRef<'a, T: TypedEcsData> { - components: AtomicRef<'a, UntypedComponentStore>, - ops: TypedComponentOps, -} - -impl<'a, T: TypedEcsData> AtomicComponentStoreRef<'a, T> { - /// Gets an immutable reference to the component of `Entity`. - pub fn get(&self, entity: Entity) -> Option<&T> { - self.ops.get(&self.components, entity) - } - - /// Iterates immutably over all components of this type. - /// Very fast but doesn't allow joining with other component types. - pub fn iter(&self) -> impl Iterator { - self.ops.iter(&self.components) - } - - /// Iterates immutably over the components of this type where `bitset` - /// indicates the indices of entities. - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_with_bitset(&self, bitset: Rc) -> ComponentBitsetIterator { - self.ops.iter_with_bitset(&self.components, bitset) - } - - /// Read the bitset containing the list of entites with this component type on it. - pub fn bitset(&self) -> &BitSetVec { - self.components.bitset() - } - - /// Check whether or not this component store has data for the given entity. - #[inline] - pub fn contains(&self, entity: Entity) -> bool { - self.bitset().contains(entity) - } -} - -/// A mutable borrow of [`AtomicComponentStore`]. -pub struct AtomicComponentStoreRefMut<'a, T: TypedEcsData> { - components: AtomicRefMut<'a, UntypedComponentStore>, - ops: TypedComponentOps, -} - -impl<'a, T: TypedEcsData> AtomicComponentStoreRefMut<'a, T> { - /// Inserts a component for the given [`Entity`] index. - /// - /// Returns the previous component, if any. - pub fn insert(&mut self, entity: Entity, component: T) -> Option { - self.ops.insert(&mut self.components, entity, component) - } - - /// Gets an immutable reference to the component of [`Entity`]. - pub fn get(&self, entity: Entity) -> Option<&T> { - self.ops.get(&self.components, entity) - } - - /// Gets a mutable reference to the component of [`Entity`]. - pub fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { - self.ops.get_mut(&mut self.components, entity) - } - - /// Get mutable pointers to the component data for multiple entities at the same time. - /// - /// # Panics - /// - /// This will panic if the same entity is specified multiple times. This is invalid because it - /// would mean you would have two mutable references to the same component data at the same - /// time. - pub fn get_many_mut(&mut self, entities: [Entity; N]) -> [Option<&mut T>; N] { - self.ops.get_many_mut(&mut self.components, entities) - } - - /// Removes the component of [`Entity`]. - /// - /// Returns the component that was on the entity, if any. - pub fn remove(&mut self, entity: Entity) -> Option { - self.ops.remove(&mut self.components, entity) - } - - /// Iterates immutably over all components of this type. - /// - /// Very fast but doesn't allow joining with other component types. - pub fn iter(&self) -> impl Iterator { - self.ops.iter(&self.components) - } - - /// Iterates mutably over all components of this type. - /// - /// Very fast but doesn't allow joining with other component types. - pub fn iter_mut(&mut self) -> impl Iterator { - self.ops.iter_mut(&mut self.components) - } - - /// Iterates immutably over the components of this type where `bitset` indicates the indices of - /// entities. - /// - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_with_bitset(&self, bitset: Rc) -> ComponentBitsetIterator { - self.ops.iter_with_bitset(&self.components, bitset) - } - - /// Iterates mutable over the components of this type where `bitset` indicates the indices of - /// entities. - /// - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_mut_with_bitset(&mut self, bitset: Rc) -> ComponentBitsetIteratorMut { - self.ops.iter_mut_with_bitset(&mut self.components, bitset) - } - - /// Get the bitset representing which entities have this component on it. - pub fn bitset(&self) -> &BitSetVec { - self.components.bitset() - } - - /// Check whether or not this component store has data for the given entity. - #[inline] - pub fn contains(&self, entity: Entity) -> bool { - self.bitset().contains(entity) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - - #[test] - fn create_remove_components() { - #[derive(Debug, Clone, PartialEq, Eq, TypeUlid)] - #[ulid = "01GNDP2GNMKQZF9V4JTC88CQ7X"] - struct A(String); - - let mut entities = Entities::default(); - let e1 = entities.create(); - let e2 = entities.create(); - - let mut storage = ComponentStore::::default(); - storage.insert(e1, A("hello".into())); - storage.insert(e2, A("world".into())); - assert!(storage.get(e1).is_some()); - storage.remove(e1); - assert!(storage.get(e1).is_none()); - assert_eq!( - storage.iter().cloned().collect::>(), - vec![A("world".into())] - ) - } -} diff --git a/crates/bones_ecs/src/components/typed/ops.rs b/crates/bones_ecs/src/components/typed/ops.rs deleted file mode 100644 index 2fbb3a4a58..0000000000 --- a/crates/bones_ecs/src/components/typed/ops.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::{ - marker::PhantomData, - mem::{ManuallyDrop, MaybeUninit}, - ops::DerefMut, - rc::Rc, -}; - -use crate::prelude::*; - -/// Implements typed operations on top of a [`UntypedComponentStore`]. -/// -/// This is a utility used to help represent the unsafty of interpreting the [`UntypedComponentStore`] -/// as a particular type. -/// -/// It is unsafe to construct a [`TypedComponentOps`] to indicate that you are taking responsibility -/// for only calling it's functions on an [`UntypedComponentStore`] that actually corresponds to the -/// type `T` that the [`TypedComponentOps`] was created for. -/// -/// > **Note:** The alternative to this approach would be to make every method of this type -/// > `unsafe`, which may be a better option. It really seems like a matter of preference, but if -/// > you have an opinion, @zicklag would be happy to discuss on GitHub! -pub struct TypedComponentOps(PhantomData); - -impl TypedComponentOps { - /// # Safety - /// Creating `TypedComponentOps` must only be used on an [`UntypedComponentStore`] where the - /// underlying, untyped component data is valid for `T`. - pub unsafe fn new() -> Self { - Self(PhantomData) - } - - /// Insert a component into the store. - pub fn insert( - &self, - components: &mut UntypedComponentStore, - entity: Entity, - component: T, - ) -> Option { - let mut component = ManuallyDrop::new(component); - let ptr = component.deref_mut() as *mut T as *mut u8; - - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component storage - // is valid for type T. - unsafe { - let already_existed = components.insert(entity, ptr); - - if already_existed { - let previous_component = ManuallyDrop::take(&mut component); - Some(previous_component) - } else { - None - } - } - } - - /// Borrow a component in the store, if it exists for the given entity. - pub fn get(&self, components: &UntypedComponentStore, entity: Entity) -> Option<&T> { - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component storage - // is valid for type T. - components.get(entity).map(|x| unsafe { &*(x as *const T) }) - } - - /// Mutably borrow a component in the store, if it exists for the given entity. - pub fn get_mut( - &self, - components: &mut UntypedComponentStore, - entity: Entity, - ) -> Option<&mut T> { - components - .get_mut(entity) - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component storage - // is valid for type T. - .map(|x| unsafe { &mut *(x as *mut T) }) - } - - /// Get mutable pointers to the component data for multiple entities at the same time. - /// - /// # Panics - /// - /// This will panic if the same entity is specified multiple times. This is invalid because it - /// would mean you would have two mutable references to the same component data at the same - /// time. - pub fn get_many_mut( - &self, - components: &mut UntypedComponentStore, - entities: [Entity; N], - ) -> [Option<&mut T>; N] { - let pointers = components.get_many_mut(entities); - std::array::from_fn(|i| { - pointers[i] - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component - // storage is valid for type T. Additionally, `components.get_many_mut()` verifies - // that the pointers don't overlap. - .map(|x| unsafe { &mut *(x as *mut T) }) - }) - } - - /// Remove a component from an entity, returning the previous component if one existed. - pub fn remove(&self, components: &mut UntypedComponentStore, entity: Entity) -> Option { - let mut r = MaybeUninit::::zeroed(); - let ptr = r.as_mut_ptr() as *mut u8; - - // SAFE: ptr doesn't overlap the component's internal storage - let had_previous = unsafe { components.remove(entity, Some(ptr)) }; - if had_previous { - // SAFE: According to `components.remove` the if it returns `true` then the previous - // component has been written to the pointer ( aka. initialized ). - unsafe { - let r = r.assume_init(); - Some(r) - } - } else { - None - } - } - - /// Iterate over all components in the store. - pub fn iter<'a>( - &'a self, - components: &'a UntypedComponentStore, - ) -> impl Iterator { - components - .iter() - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component - // storage is valid for type T. - .map(|x| unsafe { &*(x.as_ptr() as *const T) }) - } - - /// Mutably iterate over all components in the store. - pub fn iter_mut<'a>( - &'a self, - components: &'a mut UntypedComponentStore, - ) -> impl Iterator { - components - .iter_mut() - // SAFE: constructing TypedComponentOps is unsafe, and user asserts that component - // storage is valid for type T. - .map(|x| unsafe { &mut *(x.as_mut_ptr() as *mut T) }) - } - - /// Iterate over all the components in the store that match the entities in the given bitset. - pub fn iter_with_bitset<'a>( - &'a self, - components: &'a UntypedComponentStore, - bitset: Rc, - ) -> ComponentBitsetIterator<'a, T> { - // SAFE: Constructing `TypedComponentOps` is unsafe and user affirms the type T is valid for - // the underlying, untyped data. - unsafe { ComponentBitsetIterator::new(components.iter_with_bitset(bitset)) } - } - - /// Mutably iterate over all the components in the store that match the entities in the given - /// bitset. - pub fn iter_mut_with_bitset<'a>( - &'a self, - components: &'a mut UntypedComponentStore, - bitset: Rc, - ) -> ComponentBitsetIteratorMut { - // SAFE: Constructing `TypedComponentOps` is unsafe and user affirms the type T is valid for - // the underlying, untyped data. - unsafe { ComponentBitsetIteratorMut::new(components.iter_mut_with_bitset(bitset)) } - } -} diff --git a/crates/bones_ecs/src/components/untyped.rs b/crates/bones_ecs/src/components/untyped.rs deleted file mode 100644 index 21ff70ad50..0000000000 --- a/crates/bones_ecs/src/components/untyped.rs +++ /dev/null @@ -1,392 +0,0 @@ -use crate::prelude::*; - -use aligned_vec::AVec; -use std::{ - alloc::Layout, - ptr::{self}, - rc::Rc, -}; - -/// Holds components of a given type indexed by `Entity`. -/// -/// We do not check if the given entity is alive here, this should be done using `Entities`. -pub struct UntypedComponentStore { - pub(crate) bitset: BitSetVec, - pub(crate) storage: AVec, - pub(crate) layout: Layout, - pub(crate) max_id: usize, - pub(crate) drop_fn: Option, - pub(crate) clone_fn: unsafe extern "C" fn(*const u8, *mut u8), -} - -impl Clone for UntypedComponentStore { - fn clone(&self) -> Self { - let size = self.layout.size(); - let mut new_storage = self.storage.clone(); - - for i in 0..self.max_id { - if self.bitset.bit_test(i) { - // SAFE: constructing an UntypedComponent store is unsafe, and the user affirms that - // clone_fn will not do anything unsound. - // - // - And our previous pointer is a valid pointer to component data - // - And our new pointer is a writable pointer with the same layout - unsafe { - let prev_ptr = self.storage.as_ptr().add(i * size); - let new_ptr = new_storage.as_mut_ptr().add(i * size); - (self.clone_fn)(prev_ptr, new_ptr); - } - } - } - - Self { - bitset: self.bitset.clone(), - storage: new_storage, - layout: self.layout, - max_id: self.max_id, - drop_fn: self.drop_fn, - clone_fn: self.clone_fn, - } - } -} - -impl Drop for UntypedComponentStore { - fn drop(&mut self) { - if let Some(drop_fn) = self.drop_fn { - let size = self.layout().size(); - if size < 1 { - return; - } - for i in 0..(self.storage.len() / size) { - if self.bitset.bit_test(i) { - // SAFE: constructing an UntypedComponent store is unsafe, and the user affirms - // that clone_fn will not do anything unsound. - // - // And our pointer is valid. - unsafe { - let ptr = self.storage.as_mut_ptr().add(i * size); - drop_fn(ptr); - } - } - } - } - } -} - -impl UntypedComponentStore { - /// Create a arbitrary [`UntypedComponentStore`]. - /// - /// In Rust, you will usually not use [`UntypedComponentStore`] and will use the statically - /// typed [`ComponentStore`] instead. - /// - /// # Safety - /// - /// The `clone_fn` and `drop_fn`, if specified, must not do anything unsound, when given valid - /// pointers to clone or drop. - pub unsafe fn new( - layout: Layout, - clone_fn: unsafe extern "C" fn(*const u8, *mut u8), - drop_fn: Option, - ) -> Self { - Self { - bitset: create_bitset(), - // Approximation of a good default. - storage: AVec::with_capacity(layout.align(), (BITSET_SIZE >> 4) * layout.size()), - layout, - max_id: 0, - clone_fn, - drop_fn, - } - } - - /// Create an [`UntypedComponentStore`] that is valid for the given type `T`. - pub fn for_type() -> Self { - let layout = Layout::new::(); - Self { - bitset: create_bitset(), - // Approximation of a good default. - storage: AVec::with_capacity(layout.align(), (BITSET_SIZE >> 4) * layout.size()), - layout, - max_id: 0, - clone_fn: T::raw_clone, - drop_fn: Some(T::raw_drop), - } - } - - /// Get the layout of the components stored. - pub fn layout(&self) -> Layout { - self.layout - } - - /// Returns true if the entity already had a component of this type. - /// - /// If true is returned, the previous value of the pointer will be written to `data`. - /// - /// # Safety - /// - /// The data pointer must be valid for reading and writing objects with the layout that the - /// [`UntypedComponentStore`] was created with. - pub unsafe fn insert(&mut self, entity: Entity, data: *mut u8) -> bool { - let size = self.layout.size(); - - let index = entity.index() as usize; - self.allocate_enough(index * size); - let ptr = self.storage.as_mut_ptr().add(index * size); - - // If the component already exists on the entity - if self.bitset.bit_test(entity.index() as usize) { - // Swap the data with the data already there - ptr::swap_nonoverlapping(ptr, data, size); - - // There was already a component of this type - true - } else { - self.max_id = self.max_id.max(index + 1); - self.allocate_enough(index * size); - self.bitset.bit_set(index); - ptr::swap_nonoverlapping(ptr, data, size); - - // There was not already a component of this type - false - } - } - - /// Ensures that we have the vec filled at least until the `until` variable. - /// - /// Usually, set this to `entity.index`. - fn allocate_enough(&mut self, until: usize) { - if self.storage.len() <= until { - let qty = ((until - self.storage.len()) + 1) * self.layout.size(); - for _ in 0..qty { - self.storage.push(0); - } - } - } - - /// Get a read-only pointer to the component for the given [`Entity`] if the entity has this - /// component. - pub fn get(&self, entity: Entity) -> Option<*const u8> { - let index = entity.index() as usize; - - if self.bitset.bit_test(index) { - let size = self.layout.size(); - // SAFE: we've already validated that the contents of storage is valid for type T. - unsafe { - let ptr = self.storage.as_ptr().add(index * size); - Some(ptr) - } - } else { - None - } - } - - /// Get a mutable pointer to the component for the given [`Entity`] - pub fn get_mut(&mut self, entity: Entity) -> Option<*mut u8> { - let index = entity.index() as usize; - - if self.bitset.bit_test(index) { - let size = self.layout.size(); - // SAFE: we've already validated that the contents of storage is valid for type T. - unsafe { - let ptr = self.storage.as_mut_ptr().add(index * size); - Some(ptr) - } - } else { - None - } - } - - /// Get mutable pointers to the component data for multiple entities at the same time. - /// - /// # Panics - /// - /// This will panic if the same entity is specified multiple times. This is invalid because it - /// would mean you would have two mutable references to the same component data at the same - /// time. - pub fn get_many_mut(&mut self, entities: [Entity; N]) -> [Option<*mut u8>; N] { - // Sort a copy of the passed in entities list. - let mut sorted = entities; - sorted.sort_unstable(); - // Detect duplicates. - // - // Since we have sorted the slice, any duplicates will be adjacent to each-other, and we - // only have to make sure that for every item in the slice, the one after it is not the same - // as it. - for i in 0..(N - 1) { - if sorted[i] == sorted[i + 1] { - panic!("All entities passed to `get_multiple_mut()` must be unique."); - } - } - - std::array::from_fn(|i| { - let index = entities[i].index() as usize; - - if self.bitset.bit_test(index) { - let size = self.layout.size(); - // SAFE: we've already validated that the contents of storage is valid for type T. - // And we have verified that this entity is unique among the other entities we are - // borrowing at the same time. - unsafe { - let ptr = self.storage.as_mut_ptr().add(index * size); - Some(ptr) - } - } else { - None - } - }) - } - - /// If there is a previous value, `true` will be returned. - /// - /// If `out` is set, the previous value will be written to it. - /// - /// # Safety - /// - /// If set, the `out` pointer, must not overlap the internal component storage. - pub unsafe fn remove(&mut self, entity: Entity, out: Option<*mut u8>) -> bool { - let index = entity.index() as usize; - let size = self.layout.size(); - - if self.bitset.bit_test(index) { - self.bitset.bit_reset(index); - - let ptr = self.storage.as_mut_ptr().add(index * size); - - if let Some(out) = out { - // SAFE: user asserts `out` is non-overlapping - ptr::copy_nonoverlapping(ptr, out, size); - } else if let Some(drop_fn) = self.drop_fn { - // SAFE: construcing `UntypedComponentStore` asserts the soundess of the drop_fn - // - // And ptr is a valid pointer to the component type. - drop_fn(ptr); - } - - // Found previous component - true - } else { - // No previous component - false - } - } - - /// Iterates immutably over all components of this type. - /// - /// Very fast but doesn't allow joining with other component types. - pub fn iter(&self) -> impl Iterator { - let Self { - storage: components, - bitset, - layout, - .. - } = self; - - if layout.size() > 0 { - either::Left( - components - .chunks(layout.size()) - .enumerate() - .filter(move |(i, _)| bitset.bit_test(*i)) - .map(|(_i, x)| x), - ) - } else { - // TODO: Add more tests for the ZST component iterator. - let mut idx = 0usize; - let max_id = self.max_id; - let iterator = std::iter::from_fn(move || loop { - if idx >= max_id { - break None; - } - - if !bitset.bit_test(idx) { - idx += 1; - continue; - } - - idx += 1; - break Some(&[] as &[u8]); - }); - - either::Right(iterator) - } - } - - /// Iterates mutably over all components of this type. - /// - /// Very fast but doesn't allow joining with other component types. - pub fn iter_mut(&mut self) -> impl Iterator { - let Self { - storage, - bitset, - layout, - .. - } = self; - - if layout.size() > 0 { - either::Left( - storage - .chunks_mut(layout.size()) - .enumerate() - .filter(move |(i, _)| bitset.bit_test(*i)) - .map(|(_i, x)| x), - ) - } else { - let mut idx = 0usize; - let max_id = self.max_id; - let iterator = std::iter::from_fn(move || loop { - if idx >= max_id { - break None; - } - - if !bitset.bit_test(idx) { - idx += 1; - continue; - } - - idx += 1; - break Some(&mut [] as &mut [u8]); - }); - - either::Right(iterator) - } - } - - /// Iterates immutably over the components of this type where `bitset` indicates the indices of - /// entities. - /// - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_with_bitset(&self, bitset: Rc) -> UntypedComponentBitsetIterator { - UntypedComponentBitsetIterator { - current_id: 0, - components: self, - bitset, - } - } - - /// Iterates mutable over the components of this type where `bitset` indicates the indices of - /// entities. - /// - /// Slower than `iter()` but allows joining between multiple component types. - pub fn iter_mut_with_bitset( - &mut self, - bitset: Rc, - ) -> UntypedComponentBitsetIteratorMut { - UntypedComponentBitsetIteratorMut { - current_id: 0, - components: self, - bitset, - } - } - - /// Returns the bitset indicating which entity indices have a component associated to them. - /// - /// Useful to build conditions between multiple `Components`' bitsets. - /// - /// For example, take two bitsets from two different `Components` types. Then, - /// bitset1.clone().bit_and(bitset2); And finally, you can use bitset1 in `iter_with_bitset` and - /// `iter_mut_with_bitset`. This will iterate over the components of the entity only for - /// entities that have both components. - pub fn bitset(&self) -> &BitSetVec { - &self.bitset - } -} diff --git a/crates/bones_ecs/src/lib.rs b/crates/bones_ecs/src/lib.rs deleted file mode 100644 index 543f2f4bdb..0000000000 --- a/crates/bones_ecs/src/lib.rs +++ /dev/null @@ -1,171 +0,0 @@ -#![doc = include_str!("../README.md")] -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] -#![warn(missing_docs)] - -pub mod atomic { - //! Atomic Refcell implmentation. - //! - //! Atomic Refcells are from the [`atomic_refcell`] crate. - //! - //! [`atomic_refcell`]: https://docs.rs/atomic_refcell - pub use atomic_refcell::*; -} -pub mod bitset; -pub mod components; -pub mod entities; -pub mod resources; -pub mod stage; -pub mod system; -pub mod ulid; - -mod error; -pub use error::EcsError; - -mod world; -pub use world::{FromWorld, World}; - -/// The prelude. -pub mod prelude { - pub use { - atomic_refcell::*, - bevy_derive::{Deref, DerefMut}, - bitset_core::BitSet, - type_ulid::{TypeUlid, Ulid}, - }; - - pub use crate::{ - bitset::*, components::*, default, entities::*, error::*, resources::*, stage::*, - system::*, ulid::*, EcsData, FromWorld, RawFns, TypedEcsData, UnwrapMany, World, - }; -} - -/// Helper trait that is auto-implemented for anything that may be stored in the ECS's untyped -/// storage. -/// -/// Examples of untyped storage are [`UntypedResources`][crate::resources::UntypedResources] and -/// [`UntypedComponentStore`][crate::components::UntypedComponentStore]. -pub trait EcsData: Clone + Sync + Send + 'static {} -impl EcsData for T {} - -/// Helper trait that is auto-implemented for anything that may be stored in the ECS's typed -/// storage. -/// -/// Examples of typed storage are [`Resources`][crate::resources::Resources] and -/// [`ComponentStore`][crate::components::ComponentStore]. -pub trait TypedEcsData: type_ulid::TypeUlid + EcsData {} -impl TypedEcsData for T {} - -/// Helper trait for unwraping each item in an array. -/// -/// # Example -/// -/// ``` -/// # use bones_ecs::UnwrapMany; -/// let data = [Some(1), Some(2)]; -/// let [data1, data2] = data.unwrap_many(); -/// ``` -pub trait UnwrapMany { - /// Unwrap all the items in an array. - fn unwrap_many(self) -> [T; N]; -} - -impl UnwrapMany for [Option; N] { - fn unwrap_many(self) -> [T; N] { - let mut iter = self.into_iter(); - std::array::from_fn(|_| iter.next().unwrap().unwrap()) - } -} -impl UnwrapMany for [Result; N] { - fn unwrap_many(self) -> [T; N] { - let mut iter = self.into_iter(); - std::array::from_fn(|_| iter.next().unwrap().unwrap()) - } -} - -/// Helper trait that is auto-implemented for all `Clone`-able types. Provides easy access to drop -/// and clone funcitons for raw pointers. -/// -/// This simply serves as a convenient way to obtain a drop/clone function implementation for -/// [`UntypedResource`][crate::resources::UntypedResource] or -/// [`UntypedComponentStore`][crate::components::UntypedComponentStore]. -/// -/// > **Note:** This is an advanced feature that you don't need if you aren't working with some sort -/// > of scripting or otherwise untyped data access. -/// -/// # Example -/// -/// ``` -/// # use bones_ecs::prelude::*; -/// # use core::alloc::Layout; -/// let components = unsafe { -/// UntypedComponentStore::new(Layout::new::(), String::raw_clone, Some(String::raw_drop)); -/// }; -/// ``` -pub trait RawFns { - /// Drop the value at `ptr`. - /// - /// # Safety - /// - The pointer must point to a valid instance of the type that this implementation is - /// assocated with. - /// - The pointer must be writable. - unsafe extern "C" fn raw_drop(ptr: *mut u8); - - /// Clone the value at `src`, writing the new value to `dst`. - /// - /// # Safety - /// - The src pointer must point to a valid instance of the type that this implementation is - /// assocated with. - /// - The destination pointer must be properly aligned and writable. - unsafe extern "C" fn raw_clone(src: *const u8, dst: *mut u8); -} - -impl RawFns for T { - unsafe extern "C" fn raw_drop(ptr: *mut u8) { - use std::io::{self, Write}; - - let result = std::panic::catch_unwind(|| { - if std::mem::needs_drop::() { - (ptr as *mut T).drop_in_place() - } - }); - - if result.is_err() { - writeln!( - io::stderr(), - "Rust type {} panicked in destructor.\n\ - Unable to panic across C ABI: aborting.", - std::any::type_name::() - ) - .ok(); - std::process::abort(); - } - } - - unsafe extern "C" fn raw_clone(src: *const u8, dst: *mut u8) { - use std::io::{self, Write}; - - let result = std::panic::catch_unwind(|| { - let t = &*(src as *const T); - let t = t.clone(); - (dst as *mut T).write(t) - }); - - if result.is_err() { - writeln!( - io::stderr(), - "Rust type {} panicked in clone implementation.\n\ - Unable to panic across C ABI: aborting.", - std::any::type_name::() - ) - .ok(); - std::process::abort(); - } - } -} - -/// Free-standing, shorter equivalent to [`Default::default()`]. -#[inline] -pub fn default() -> T { - std::default::Default::default() -} diff --git a/crates/bones_ecs/src/resources.rs b/crates/bones_ecs/src/resources.rs deleted file mode 100644 index c05719fa01..0000000000 --- a/crates/bones_ecs/src/resources.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! World resource storage. - -use std::{ - alloc::{self, Layout}, - any::TypeId, - io::Write, - marker::PhantomData, - mem, - ptr::NonNull, - sync::Arc, -}; - -use crate::prelude::*; - -/// Storage for un-typed resources. -/// -/// This is the backing data store used by [`Resources`]. -/// -/// Unless you are intending to do modding or otherwise need raw pointers to your resource data, you -/// should use [`Resources`] instead. -#[derive(Clone, Default)] -pub struct UntypedResources { - resources: UlidMap, -} - -/// Used to construct an [`UntypedResource`]. -pub struct UntypedResourceInfo { - /// The memory layout of the resource - pub layout: Layout, - /// Cell containing the raw pointer to the resource's data - // TODO: Evaluate possibility of avoiding an `Arc` clone. - // We might be able to just just pass references with a lifetime for acessing it, - // instead of cloning the Arc like we do here. - pub cell: Arc>, - /// A function that may be called to clone the resource from one pointer to another. - pub clone_fn: unsafe extern "C" fn(*const u8, *mut u8), - /// An optional function that will be called to drop the resource. - pub drop_fn: Option, -} - -/// An untyped resource that may be inserted into [`UntypedResources`]. -pub struct UntypedResource { - layout: Layout, - cell: Arc>, - clone_fn: unsafe extern "C" fn(*const u8, *mut u8), - drop_fn: Option, -} - -impl UntypedResource { - /// Create a new [`UntypedResource`] from raw [`UntypedResourceInfo`]. - /// - /// # Safety - /// - /// - The implementations for `info.clone_fn` and `info.drop_fn` must not do anything unsound - /// when given valid pointers to clone or drop. - /// - The `info.cell` must contain a pointer that is writable and points to data with a layout - /// matching `info.layout`. - pub unsafe fn new_raw(info: UntypedResourceInfo) -> Self { - UntypedResource { - layout: info.layout, - cell: info.cell, - clone_fn: info.clone_fn, - drop_fn: info.drop_fn, - } - } - - /// Creates a new [`UntypedResource`] from an instance of a Rust type. - /// - /// This is the safest way to construct a valid [`UntypedResource`]. - pub fn new(resource: T) -> Self { - let layout = Layout::new::(); - - let ptr = Box::into_raw(Box::new(resource)) as *mut u8; - - Self { - cell: Arc::new(AtomicRefCell::new(ptr)), - clone_fn: T::raw_clone, - drop_fn: Some(T::raw_drop), - layout, - } - } -} - -unsafe impl Sync for UntypedResource {} -unsafe impl Send for UntypedResource {} - -impl Clone for UntypedResource { - fn clone(&self) -> Self { - let prev_ptr = self.cell.borrow(); - let new_ptr = if self.layout.size() == 0 { - NonNull::::dangling().as_ptr() - } else { - // SAFE: Non-zero size for layout - unsafe { std::alloc::alloc(self.layout) } - }; - - if new_ptr.is_null() { - writeln!( - std::io::stderr(), - "Error alloating memory while cloning resource" - ) - .ok(); - std::process::abort(); - } - - // SAFE: UntypedResource can only be constructed with an unsafe function where the user - // promises not to do anything unsound while dropping. - // - // And our source prev_ptr is valid, and the new_ptr is properly aligned and writable. - unsafe { - (self.clone_fn)(*prev_ptr, new_ptr); - } - - Self { - cell: Arc::new(AtomicRefCell::new(new_ptr)), - clone_fn: self.clone_fn, - drop_fn: self.drop_fn, - layout: self.layout, - } - } -} - -impl Drop for UntypedResource { - fn drop(&mut self) { - if let Some(drop_fn) = self.drop_fn { - let null_cell = Arc::new(AtomicRefCell::new(std::ptr::null_mut())); - let cell = mem::replace(&mut self.cell, null_cell); - - let ptr = Arc::try_unwrap(cell) - .expect( - "You must drop all references to Resoruces before dropping `UntypedResources`", - ) - .into_inner(); - - // SAFE: UntypedResource can only be constructed with an unsafe function where the user - // promises not to do anything unsound while dropping. - // - // And our ptr is valid. - unsafe { - drop_fn(ptr); - - if self.layout.size() != 0 { - alloc::dealloc(ptr, self.layout); - } - } - } - } -} - -impl UntypedResources { - /// Create an empty [`UntypedResources`]. - pub fn new() -> Self { - Self::default() - } - - /// Insert a new resource - pub fn insert(&mut self, uuid: Ulid, resource: UntypedResource) -> Option { - self.resources.insert(uuid, resource) - } - - /// Get a cell containing the resource data pointer for the given ID - pub fn get(&self, uuid: Ulid) -> Option>> { - self.resources.get(&uuid).map(|x| x.cell.clone()) - } - - /// Remove a resource - pub fn remove(&mut self, uuid: Ulid) -> Option { - self.resources.remove(&uuid) - } -} - -/// A collection of resources. -/// -/// [`Resources`] is essentially a type-map -#[derive(Clone, Default)] -pub struct Resources { - untyped: UntypedResources, - type_ids: UlidMap, -} - -impl Resources { - /// Create an empty [`Resources`]. - pub fn new() -> Self { - Self::default() - } - - /// Insert a resource. - /// - /// # Panics - /// - /// Panics if you try to insert a Rust type with a different [`TypeId`], but the same - /// [`TypeUlid`] as another resource in the store. - #[track_caller] - pub fn insert(&mut self, resource: T) { - self.try_insert(resource).unwrap(); - } - - /// Try to insert a resource. - /// - /// # Errors - /// - /// Errors if you try to insert a Rust type with a different [`TypeId`], but the same - /// [`TypeUlid`] as another resource in the store. - pub fn try_insert(&mut self, resource: T) -> Result<(), EcsError> { - let uuid = T::ULID; - let type_id = TypeId::of::(); - - match self.type_ids.entry(uuid) { - std::collections::hash_map::Entry::Occupied(entry) => { - if entry.get() != &type_id { - return Err(EcsError::TypeUlidCollision); - } - - self.untyped.insert(uuid, UntypedResource::new(resource)); - } - std::collections::hash_map::Entry::Vacant(entry) => { - entry.insert(type_id); - self.untyped.insert(uuid, UntypedResource::new(resource)); - } - } - - Ok(()) - } - - /// Get a resource handle from the store. - /// - /// This is not the resource itself, but a handle, may be cloned cheaply. - /// - /// In order to access the resource you must call [`borrow()`][AtomicResource::borrow] or - /// [`borrow_mut()`][AtomicResource::borrow_mut] on the returned value. - /// - /// # Panics - /// - /// Panics if the resource does not exist in the store. - #[track_caller] - pub fn get(&self) -> AtomicResource { - self.try_get().unwrap() - } - - /// Check whether or not a resource is in the store. - /// - /// See [get()][Self::get] - pub fn contains(&self) -> bool { - self.untyped.resources.contains_key(&T::ULID) - } - - /// Gets a resource handle from the store if it exists. - pub fn try_get(&self) -> Option> { - let untyped = self.untyped.get(T::ULID)?; - - Some(AtomicResource { - untyped, - _phantom: PhantomData, - }) - } - - /// Borrow the underlying [`UntypedResources`] store. - pub fn untyped(&self) -> &UntypedResources { - &self.untyped - } - /// Mutably borrow the underlying [`UntypedResources`] store. - pub fn untyped_mut(&mut self) -> &mut UntypedResources { - &mut self.untyped - } - /// Consume [`Resources`] and extract the underlying [`UntypedResources`]. - pub fn into_untyped(self) -> UntypedResources { - self.untyped - } -} - -/// A handle to a resource from a [`Resources`] collection. -/// -/// This is not the resource itself, but a cheaply clonable handle to it. -/// -/// To access the resource you must borrow it with either [`borrow()`][Self::borrow] or -/// [`borrow_mut()`][Self::borrow_mut]. -pub struct AtomicResource { - untyped: Arc>, - _phantom: PhantomData, -} - -impl AtomicResource { - /// Lock the resource for reading. - /// - /// This returns a read guard, very similar to an [`RwLock`][std::sync::RwLock]. - pub fn borrow(&self) -> AtomicRef { - let borrow = self.untyped.borrow(); - // SAFE: We know that the data pointer is valid for type T. - AtomicRef::map(borrow, |data| unsafe { &*data.cast::() }) - } - - /// Lock the resource for read-writing. - /// - /// This returns a write guard, very similar to an [`RwLock`][std::sync::RwLock]. - pub fn borrow_mut(&self) -> AtomicRefMut { - let borrow = self.untyped.borrow_mut(); - // SAFE: We know that the data pointer is valid for type T. - AtomicRefMut::map(borrow, |data| unsafe { &mut *data.cast::() }) - } -} - -#[cfg(test)] -mod test { - use crate::prelude::*; - - #[test] - fn sanity_check() { - #[derive(TypeUlid, Clone, Debug)] - #[ulid = "01GNDJJ42DY5GSP9PGXPGF65GE"] - struct A(Vec); - - #[derive(TypeUlid, Clone, Debug)] - #[ulid = "01GNDJJDS0JRRN6TP3S89R4FQB"] - struct B(u32); - - let mut resources = Resources::new(); - - resources.insert(A(vec![3, 2, 1])); - assert_eq!(resources.get::().borrow_mut().0, vec![3, 2, 1]); - - let r2 = resources.clone(); - - resources.insert(A(vec![4, 5, 6])); - resources.insert(A(vec![7, 8, 9])); - assert_eq!(resources.get::().borrow().0, vec![7, 8, 9]); - - // TODO: Create more focused test for cloning resources. - assert_eq!(r2.get::().borrow().0, vec![3, 2, 1]); - - resources.insert(B(1)); - assert_eq!(resources.get::().borrow().0, 1); - resources.insert(B(2)); - assert_eq!(resources.get::().borrow().0, 2); - assert_eq!(resources.get::().borrow().0, vec![7, 8, 9]); - } -} diff --git a/crates/bones_ecs/src/ulid.rs b/crates/bones_ecs/src/ulid.rs deleted file mode 100644 index e4088493f8..0000000000 --- a/crates/bones_ecs/src/ulid.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! ULID-related utilities such as ULID map and type ULIDs. -//! -//! - [`TypeUlid`] comes from the [`type_ulid`] crate -//! - [`Ulid`] comes from the [`ulid`] crate. -//! -//! [`ulid`]: https://docs.rs/ulid - -use fxhash::FxHashMap; - -pub use type_ulid::{TypeUlid, Ulid}; - -/// Faster hash map using [`FxHashMap`] and a ULID key. -pub type UlidMap = FxHashMap; diff --git a/crates/bones_input/CHANGELOG.md b/crates/bones_input/CHANGELOG.md deleted file mode 100644 index 7e766d8db1..0000000000 --- a/crates/bones_input/CHANGELOG.md +++ /dev/null @@ -1,132 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2023-06-01) - -### Documentation - - - update changelogs. - -### New Features - - - upgrade to Bevy 0.10. - - add helper for advancing the Time a fixed timestep. - Adds a configurable `sync_time` option to the `BonesRendererPlugin` - so that you can disable the automatic time synchronization in favor of a - custom implementation. - - It also moves the time synchronization to a new stage that happens after - `CoreStage::First` so that the time will be in sync during the - `PreUpdate` and `Update` stages. - - add time resource + sync system - -### Bug Fixes (BREAKING) - - - use `instant` crate for WASM compatibility in `bones_input`. - -### Commit Statistics - - - - - 5 commits contributed to the release over the course of 106 calendar days. - - 133 days passed between releases. - - 5 commits were understood as [conventional](https://www.conventionalcommits.org). - - 5 unique issues were worked on: [#122](https://github.com/fishfolk/bones/issues/122), [#124](https://github.com/fishfolk/bones/issues/124), [#95](https://github.com/fishfolk/bones/issues/95), [#97](https://github.com/fishfolk/bones/issues/97), [#98](https://github.com/fishfolk/bones/issues/98) - -### Commit Details - - - -
view details - - * **[#122](https://github.com/fishfolk/bones/issues/122)** - - upgrade to Bevy 0.10. ([`3f2e348`](https://github.com/fishfolk/bones/commit/3f2e3485f9556cc68eb4c04df34d3aa2c6087330)) - * **[#124](https://github.com/fishfolk/bones/issues/124)** - - update changelogs. ([`3f18051`](https://github.com/fishfolk/bones/commit/3f18051e023a4deb676a5f895f1478beda513f04)) - * **[#95](https://github.com/fishfolk/bones/issues/95)** - - add time resource + sync system ([`605345b`](https://github.com/fishfolk/bones/commit/605345bd3d4fa2f8f540ae106b114d52c45b904a)) - * **[#97](https://github.com/fishfolk/bones/issues/97)** - - add helper for advancing the Time a fixed timestep. ([`822fe58`](https://github.com/fishfolk/bones/commit/822fe58511e956c91a9c3b1fe338d25799696411)) - * **[#98](https://github.com/fishfolk/bones/issues/98)** - - use `instant` crate for WASM compatibility in `bones_input`. ([`5849caf`](https://github.com/fishfolk/bones/commit/5849caf064259df0530bf15f3a1985170875e225)) -
- -## 0.1.0 (2023-01-18) - - - - - -### Chore - - - add missing crate descriptions. - -### Chore - - - generate changelogs for all crates. - -### Documentation - - - document source repository in cargo manifest. - The `repository` key under `bones_ecs` previously pointed to https://github.com/fishfolk/jumpy. - - This updates this to point to the bones repo, and also adds the `repository` key to the other - crates in the repository. - -### New Features - - - add `Window` input containing window size. - -### New Features (BREAKING) - - - draft bones_lib architecture. - Renames `bones` to `bones_lib` ( mostly because `bones` was already taken ) - and adds the `bones_asset`, `bones_bevy_renderer`, `bones_input`, and - `bones_render` crates. - - This sets up the overall structure for the bones library, - though changes to some aspects of the design are likely to change. - -### Refactor (BREAKING) - - - prepare for release. - - Remove `bones_has_load_progress`: for now we don't use it, and if we - want something similar we will work it into `bones_bevy_asset`. - - Remove `bones_camera_shake`: it was moved into `bones_lib::camera`. - - Add version numbers for all local dependencies. - -### Commit Statistics - - - - - 8 commits contributed to the release over the course of 16 calendar days. - - 6 commits were understood as [conventional](https://www.conventionalcommits.org). - - 6 unique issues were worked on: [#26](https://github.com/fishfolk/bones/issues/26), [#37](https://github.com/fishfolk/bones/issues/37), [#48](https://github.com/fishfolk/bones/issues/48), [#63](https://github.com/fishfolk/bones/issues/63), [#65](https://github.com/fishfolk/bones/issues/65), [#67](https://github.com/fishfolk/bones/issues/67) - -### Commit Details - - - -
view details - - * **[#26](https://github.com/fishfolk/bones/issues/26)** - - draft bones_lib architecture. ([`d7b5711`](https://github.com/fishfolk/bones/commit/d7b5711832f6834644fc41ff011af118ce8a9f56)) - * **[#37](https://github.com/fishfolk/bones/issues/37)** - - document source repository in cargo manifest. ([`a693894`](https://github.com/fishfolk/bones/commit/a69389412d22b8cb48bab0ed96d739b0fee35348)) - * **[#48](https://github.com/fishfolk/bones/issues/48)** - - add `Window` input containing window size. ([`a85d282`](https://github.com/fishfolk/bones/commit/a85d2828c10a044524f01b0938ead015c530986f)) - * **[#63](https://github.com/fishfolk/bones/issues/63)** - - prepare for release. ([`ae0a761`](https://github.com/fishfolk/bones/commit/ae0a761fc9b82ba2fc639c2b6f7af09fb650cd31)) - * **[#65](https://github.com/fishfolk/bones/issues/65)** - - add missing crate descriptions. ([`2725246`](https://github.com/fishfolk/bones/commit/27252465ad0506ff2f8c377531fa079ec64d1750)) - * **[#67](https://github.com/fishfolk/bones/issues/67)** - - generate changelogs for all crates. ([`a68cb79`](https://github.com/fishfolk/bones/commit/a68cb79e6b7d3774c53c0236edf3a12175f297b5)) - * **Uncategorized** - - Release type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`69713d7`](https://github.com/fishfolk/bones/commit/69713d7da8024ee4b3017b563f031880009c90ee)) - - Release type_ulid_macros v0.1.0, type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`db0333d`](https://github.com/fishfolk/bones/commit/db0333ddacb6f29aed8664db67973e72ea586dce)) -
- diff --git a/crates/bones_input/Cargo.toml b/crates/bones_input/Cargo.toml deleted file mode 100644 index ab7d3bb162..0000000000 --- a/crates/bones_input/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Core input types for bones_lib." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_input" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bones_bevy_utils = { version = "^0.2.0", path = "../bones_bevy_utils" } -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -bevy = { version = "0.10", default-features = false } -glam = "0.23" -instant = "0.1" diff --git a/crates/bones_input/src/lib.rs b/crates/bones_input/src/lib.rs deleted file mode 100644 index e879262148..0000000000 --- a/crates/bones_input/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Standardized types meant to be provided to Bones games from the outside environment. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -use type_ulid::TypeUlid; - -pub mod time; - -/// The prelude. -pub mod prelude { - pub use crate::*; - pub use time::*; -} - -/// Information about the window the game is running in. -#[derive(Clone, Copy, Debug, Default, TypeUlid)] -#[ulid = "01GP70WMVH4HV4YHZ240E0YC7X"] -pub struct Window { - /// The logical size of the window's client area. - pub size: glam::Vec2, -} diff --git a/crates/bones_matchmaker/Cargo.toml b/crates/bones_matchmaker/Cargo.toml deleted file mode 100644 index 31744137ca..0000000000 --- a/crates/bones_matchmaker/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Matchmaking server for Bones games." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_matchmaker" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -anyhow = "1.0" -bevy_tasks = "0.10" -bones_matchmaker_proto = { version = "^0.2.0", path = "../bones_matchmaker_proto" } -bytes = "1.2" -clap = { version = "4.0", features = ["derive", "env"] } -either = "1.8" -futures = { version = "0.3", default-features = false, features = ["std", "async-await"] } -futures-lite = "1.12" -once_cell = "1.15" -postcard = { version = "1.0", default-features = false, features = ["alloc"] } -quinn = { version = "0.10", default-features = false, features = [ - "futures-io", - "native-certs", - "tls-rustls", -] } -quinn_runtime_bevy = { version = "^0.2.0", path = "../quinn_runtime_bevy" } -rand = "0.8" -rcgen = "0.10" -rustls = { version = "0.21", features = ["dangerous_configuration", "quic"] } -scc = "1.0" -serde = { version = "1.0", features = ["derive"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -[dev-dependencies] -async-io = "1.9" diff --git a/crates/bones_matchmaker_proto/Cargo.toml b/crates/bones_matchmaker_proto/Cargo.toml deleted file mode 100644 index 3ac0ece068..0000000000 --- a/crates/bones_matchmaker_proto/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Network protocol types for the Bones matchmaking server." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_matchmaker_proto" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -serde = { version = "1.0", features = ["derive"] } diff --git a/crates/bones_render/CHANGELOG.md b/crates/bones_render/CHANGELOG.md deleted file mode 100644 index 9cd60edba3..0000000000 --- a/crates/bones_render/CHANGELOG.md +++ /dev/null @@ -1,195 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2023-06-01) - - - -### Chore - - - add serde to bones color. - Add serde Serialize / Deserailize to bones color. - -### Documentation - - - fix incorrect code comment regarding tilemap coordinates. - - update changelogs. - -### New Features - - - - - - upgrade to Bevy 0.10. - - add color to atlas sprite. - - add color and sync with bevy - - Add color type to bones - -### Commit Statistics - - - - - 9 commits contributed to the release over the course of 128 calendar days. - - 133 days passed between releases. - - 8 commits were understood as [conventional](https://www.conventionalcommits.org). - - 8 unique issues were worked on: [#100](https://github.com/fishfolk/bones/issues/100), [#110](https://github.com/fishfolk/bones/issues/110), [#112](https://github.com/fishfolk/bones/issues/112), [#114](https://github.com/fishfolk/bones/issues/114), [#122](https://github.com/fishfolk/bones/issues/122), [#124](https://github.com/fishfolk/bones/issues/124), [#76](https://github.com/fishfolk/bones/issues/76), [#89](https://github.com/fishfolk/bones/issues/89) - -### Commit Details - - - -
view details - - * **[#100](https://github.com/fishfolk/bones/issues/100)** - - add custom camera viewport support. ([`8751bdb`](https://github.com/fishfolk/bones/commit/8751bdb1f2f403761e792bf489216aad02beaa92)) - * **[#110](https://github.com/fishfolk/bones/issues/110)** - - add color and sync with bevy ([`b96133f`](https://github.com/fishfolk/bones/commit/b96133fec89330e3837575c110e587f7e11bf3a6)) - * **[#112](https://github.com/fishfolk/bones/issues/112)** - - add serde to bones color. ([`c57a208`](https://github.com/fishfolk/bones/commit/c57a2089f4dcf6bd63e8f0e0609cf6ff3506084f)) - * **[#114](https://github.com/fishfolk/bones/issues/114)** - - add color to atlas sprite. ([`ad6d073`](https://github.com/fishfolk/bones/commit/ad6d073a33dc342d5aed1155488e4681cf1bc782)) - * **[#122](https://github.com/fishfolk/bones/issues/122)** - - upgrade to Bevy 0.10. ([`3f2e348`](https://github.com/fishfolk/bones/commit/3f2e3485f9556cc68eb4c04df34d3aa2c6087330)) - * **[#124](https://github.com/fishfolk/bones/issues/124)** - - update changelogs. ([`3f18051`](https://github.com/fishfolk/bones/commit/3f18051e023a4deb676a5f895f1478beda513f04)) - * **[#76](https://github.com/fishfolk/bones/issues/76)** - - add 2D line path rendering. ([`6abe6ee`](https://github.com/fishfolk/bones/commit/6abe6ee3587f737966bddb5ab0f003e62aea3291)) - * **[#89](https://github.com/fishfolk/bones/issues/89)** - - fix incorrect code comment regarding tilemap coordinates. ([`b912295`](https://github.com/fishfolk/bones/commit/b912295c0806196e607c1d769e030e54f4e3e548)) - * **Uncategorized** - - Release bones_render v0.1.1, bones_bevy_renderer v0.1.1 ([`5b33433`](https://github.com/fishfolk/bones/commit/5b3343305a0871914085eb1b98702ef82b84d98f)) -
- - -Add color type to Bones SpriteAdd color type to clear colorAdd color type to Path2dSync with Bevy - -## 0.1.1 (2023-01-24) - -### New Features - - - add 2D line path rendering. - -## 0.1.0 (2023-01-18) - - - - - -### Chore - - - add missing crate descriptions. - -### Chore - - - generate changelogs for all crates. - -### Documentation - - - document source repository in cargo manifest. - The `repository` key under `bones_ecs` previously pointed to https://github.com/fishfolk/jumpy. - - This updates this to point to the bones repo, and also adds the `repository` key to the other - crates in the repository. - -### New Features - - - implement `Default` for sprite components. - - add modules to the prelude. - - add audio module. - - add a helper method for creating an `AtlasSprite`. - - add utility `Key` datatype. - The `Key` datatype is a small, stack-allocated identifier, - similar to a string, but avoiding the heap allocation. - - This type might better be moved to a utility crate, - but since one doesn't exist yet for bones alone we - put it in bones_render for now. - - add resource for controlling the clear color. - - implement tilemap rendering. - - add helper methods for creating `Transform`s. - Adds helpers for creating transforms from either a translation, a rotation, or a scale. - - implement atlas sprite rendering. - Adds the `bones_render` types for atlas sprites, - and renders them in `bones_bevy_renderer`. - - This also adds an asset loader for `.atlas.yaml`/`.atlas.json` files - which can be used when you need to load a `Handle` - in a `BonesBevyAsset` struct. - -### New Features (BREAKING) - - - make `Key::new()` const and add `key!` macro for const construction. - - add asset integration with bevy. - This is a big overall change that adds ways to integrate Bones with bevy assets. - - draft bones_lib architecture. - Renames `bones` to `bones_lib` ( mostly because `bones` was already taken ) - and adds the `bones_asset`, `bones_bevy_renderer`, `bones_input`, and - `bones_render` crates. - - This sets up the overall structure for the bones library, - though changes to some aspects of the design are likely to change. - -### Bug Fixes (BREAKING) - - - fix panics in `TileLayer` by returning an `Option` from `get()`. - -### Refactor (BREAKING) - - - prepare for release. - - Remove `bones_has_load_progress`: for now we don't use it, and if we - want something similar we will work it into `bones_bevy_asset`. - - Remove `bones_camera_shake`: it was moved into `bones_lib::camera`. - - Add version numbers for all local dependencies. - -### Commit Statistics - - - - - 19 commits contributed to the release over the course of 16 calendar days. - - 17 commits were understood as [conventional](https://www.conventionalcommits.org). - - 12 unique issues were worked on: [#26](https://github.com/fishfolk/bones/issues/26), [#29](https://github.com/fishfolk/bones/issues/29), [#31](https://github.com/fishfolk/bones/issues/31), [#34](https://github.com/fishfolk/bones/issues/34), [#35](https://github.com/fishfolk/bones/issues/35), [#37](https://github.com/fishfolk/bones/issues/37), [#43](https://github.com/fishfolk/bones/issues/43), [#44](https://github.com/fishfolk/bones/issues/44), [#54](https://github.com/fishfolk/bones/issues/54), [#63](https://github.com/fishfolk/bones/issues/63), [#65](https://github.com/fishfolk/bones/issues/65), [#67](https://github.com/fishfolk/bones/issues/67) - -### Commit Details - - - -
view details - - * **[#26](https://github.com/fishfolk/bones/issues/26)** - - draft bones_lib architecture. ([`d7b5711`](https://github.com/fishfolk/bones/commit/d7b5711832f6834644fc41ff011af118ce8a9f56)) - * **[#29](https://github.com/fishfolk/bones/issues/29)** - - add asset integration with bevy. ([`89b44d7`](https://github.com/fishfolk/bones/commit/89b44d7b4f64ec266eb0ea674c220e07376a03b7)) - * **[#31](https://github.com/fishfolk/bones/issues/31)** - - implement atlas sprite rendering. ([`d43b6ec`](https://github.com/fishfolk/bones/commit/d43b6ec3aa5ef9fc587b4463d00445f43acec2ce)) - * **[#34](https://github.com/fishfolk/bones/issues/34)** - - add helper methods for creating `Transform`s. ([`f11fc28`](https://github.com/fishfolk/bones/commit/f11fc28734a7bb402fe5485aca3e1b0aab13cfc2)) - * **[#35](https://github.com/fishfolk/bones/issues/35)** - - implement tilemap rendering. ([`0a7fec6`](https://github.com/fishfolk/bones/commit/0a7fec655cd951f18bb7e8e134a534d3e79999c1)) - * **[#37](https://github.com/fishfolk/bones/issues/37)** - - document source repository in cargo manifest. ([`a693894`](https://github.com/fishfolk/bones/commit/a69389412d22b8cb48bab0ed96d739b0fee35348)) - * **[#43](https://github.com/fishfolk/bones/issues/43)** - - add resource for controlling the clear color. ([`34c5ecc`](https://github.com/fishfolk/bones/commit/34c5ecc7b2f37b99fa3b415558a858ec26ec1bba)) - * **[#44](https://github.com/fishfolk/bones/issues/44)** - - add utility `Key` datatype. ([`6d813cc`](https://github.com/fishfolk/bones/commit/6d813ccca3ea98f61fed0bdeaa2f15997c071b12)) - * **[#54](https://github.com/fishfolk/bones/issues/54)** - - implement `Default` for sprite components. ([`e76de9d`](https://github.com/fishfolk/bones/commit/e76de9db7fa7116b9e1237c301e939f22de5e370)) - * **[#63](https://github.com/fishfolk/bones/issues/63)** - - prepare for release. ([`ae0a761`](https://github.com/fishfolk/bones/commit/ae0a761fc9b82ba2fc639c2b6f7af09fb650cd31)) - * **[#65](https://github.com/fishfolk/bones/issues/65)** - - add missing crate descriptions. ([`2725246`](https://github.com/fishfolk/bones/commit/27252465ad0506ff2f8c377531fa079ec64d1750)) - * **[#67](https://github.com/fishfolk/bones/issues/67)** - - generate changelogs for all crates. ([`a68cb79`](https://github.com/fishfolk/bones/commit/a68cb79e6b7d3774c53c0236edf3a12175f297b5)) - * **Uncategorized** - - Release type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`69713d7`](https://github.com/fishfolk/bones/commit/69713d7da8024ee4b3017b563f031880009c90ee)) - - Release type_ulid_macros v0.1.0, type_ulid v0.1.0, bones_bevy_utils v0.1.0, bones_ecs v0.1.0, bones_asset v0.1.0, bones_input v0.1.0, bones_render v0.1.0, bones_lib v0.1.0 ([`db0333d`](https://github.com/fishfolk/bones/commit/db0333ddacb6f29aed8664db67973e72ea586dce)) - - add modules to the prelude. ([`a16443a`](https://github.com/fishfolk/bones/commit/a16443a0860e46bf44fed534648af85d540f975a)) - - add audio module. ([`c61b845`](https://github.com/fishfolk/bones/commit/c61b84553b8e4322a96de377b1b8f132894166db)) - - add a helper method for creating an `AtlasSprite`. ([`2a52b68`](https://github.com/fishfolk/bones/commit/2a52b688bb9b8920c9b0001fe536c4f82c86b127)) - - fix panics in `TileLayer` by returning an `Option` from `get()`. ([`6419a8c`](https://github.com/fishfolk/bones/commit/6419a8cc1652c10d94192816cbd2f5199624faa5)) - - make `Key::new()` const and add `key!` macro for const construction. ([`2c7d5f4`](https://github.com/fishfolk/bones/commit/2c7d5f4372291a9c6e81bdc19a511e4fb0a45e8c)) -
- diff --git a/crates/bones_render/Cargo.toml b/crates/bones_render/Cargo.toml deleted file mode 100644 index eb09900a44..0000000000 --- a/crates/bones_render/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Core rendering types for bones_lib." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "bones_render" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -bones_asset = { version = "^0.2.0", path = "../bones_asset" } -bones_bevy_utils = { version = "^0.2.0", path = "../bones_bevy_utils", optional = true } -bones_ecs = { version = "^0.2.0", path = "../bones_ecs" } -type_ulid = { version = "^0.2.0", path = "../type_ulid" } - -bevy_render = { version = "0.10", optional = true } -bevy_transform = { version = "0.10", optional = true } - -glam = "0.23" -hex = "0.4" -serde = { version = "1.0", optional = true } -thiserror = "1.0" - -[features] -bevy = ["dep:bones_bevy_utils", "dep:bevy_transform", "dep:bevy_render"] -default = [] -serde = ["dep:serde"] diff --git a/crates/bones_render/src/lib.rs b/crates/bones_render/src/lib.rs deleted file mode 100644 index da1993f875..0000000000 --- a/crates/bones_render/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Standardized rendering components for Bones. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -pub mod audio; -pub mod camera; -pub mod color; -pub mod datatypes; -pub mod line; -pub mod sprite; -pub mod tilemap; -pub mod transform; - -/// The prelude -pub mod prelude { - pub use {bones_asset::prelude::*, bones_ecs::prelude::*, glam::*, type_ulid::TypeUlid}; - - pub use crate::{ - audio::*, camera::*, color::*, datatypes::*, key, line::*, sprite::*, tilemap::*, - transform::*, - }; -} - -/// Create a new const [`Key`][datatypes] parsed at compile time. -#[macro_export] -macro_rules! key { - ($s:literal) => {{ - const KEY: Key = match Key::new($s) { - Ok(key) => key, - Err(KeyError::TooLong) => panic!("Key too long"), - Err(KeyError::NotAscii) => panic!("Key not ascii"), - }; - KEY - }}; -} - -#[cfg(feature = "bevy")] -mod bevy { - use bones_bevy_utils::IntoBevy; - - impl IntoBevy for super::transform::Transform { - fn into_bevy(self) -> bevy_transform::components::Transform { - bevy_transform::components::Transform { - translation: self.translation, - rotation: self.rotation, - scale: self.scale, - } - } - } - - impl IntoBevy for super::color::Color { - fn into_bevy(self) -> bevy_render::color::Color { - bevy_render::color::Color::from(self.as_rgba_f32()) - } - } -} diff --git a/crates/quinn_runtime_bevy/Cargo.toml b/crates/quinn_runtime_bevy/Cargo.toml deleted file mode 100644 index 39eb670ae4..0000000000 --- a/crates/quinn_runtime_bevy/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Quinn runtime implementation built on Bevy's IO task pool." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "quinn_runtime_bevy" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[dependencies] -async-executor = "1.4" -async-io = "1.9" -bevy_tasks = "0.10" -futures-lite = "1.0" -pin-project = "1.0" -quinn = { version = "0.10", default-features = false, features = ["native-certs", "tls-rustls"] } -quinn-proto = { version = "0.10", default-features = false } -quinn-udp = { version = "0.4", default-features = false } diff --git a/crates/type_ulid/Cargo.toml b/crates/type_ulid/Cargo.toml deleted file mode 100644 index e1b4690df8..0000000000 --- a/crates/type_ulid/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Trait for associating ULIDs with Rust types." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "type_ulid" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[features] -default = ["std"] -# Implement TypeUlid for basic types in the standard library -std = [] - -[dependencies] -type_ulid_macros = { version = "^0.2.0", path = "./macros" } -ulid = { version = "1.0", default-features = false } diff --git a/crates/type_ulid/macros/Cargo.toml b/crates/type_ulid/macros/Cargo.toml deleted file mode 100644 index 93ba288a9d..0000000000 --- a/crates/type_ulid/macros/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -authors = ["The Fish Folk & Spicy Lobster Developers"] -description = "Macros for the type_ulid crate." -edition = "2021" -license = "MIT OR Apache-2.0" -name = "type_ulid_macros" -repository = "https://github.com/fishfolk/bones" -version = "0.2.0" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.43" -quote = "1.0.21" -syn = { version = "1.0.100", features = ["extra-traits"] } -ulid = { version = "1.0.0", default-features = false } diff --git a/demos/assets_minimal/Cargo.toml b/demos/assets_minimal/Cargo.toml new file mode 100644 index 0000000000..df333a9573 --- /dev/null +++ b/demos/assets_minimal/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo_assets_minimal" +edition.workspace = true +version.workspace = true +license.workspace = true +publish = false + +[dependencies] +bones_framework = { path = "../../framework_crates/bones_framework" } +bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } diff --git a/demos/assets_minimal/assets/game.yaml b/demos/assets_minimal/assets/game.yaml new file mode 100644 index 0000000000..d06fae3bcc --- /dev/null +++ b/demos/assets_minimal/assets/game.yaml @@ -0,0 +1,3 @@ +# This is our root asset file, which corresponds to our `GameMeta` struct. + +title: Assets Minimal diff --git a/demos/assets_minimal/assets/pack.yaml b/demos/assets_minimal/assets/pack.yaml new file mode 100644 index 0000000000..b5c73f5291 --- /dev/null +++ b/demos/assets_minimal/assets/pack.yaml @@ -0,0 +1,3 @@ +# This is the core asset pack file. It's only job is to specify +# the path to the root asset. +root: ./game.yaml diff --git a/demos/assets_minimal/src/main.rs b/demos/assets_minimal/src/main.rs new file mode 100644 index 0000000000..8a577c804d --- /dev/null +++ b/demos/assets_minimal/src/main.rs @@ -0,0 +1,52 @@ +use bones_bevy_renderer::BonesBevyRenderer; +use bones_framework::prelude::*; + +// +// NOTE: You must run this example from within the `demos/assets_minimal` folder. Also, be sure to +// look at the `demo/assets_minimal/assets` folder to see the asset files for this example. +// + +/// Create our "root" asset type. +#[derive(HasSchema, Clone, Default)] +#[repr(C)] +// We must mark this as a metadata asset, and we set the type to "game". +// +// This means that any files with names like `game.yaml`, `game.yml`, `game.json`, `name.game.yaml`, +// etc. will be loaded as a `GameMeta` asset. +#[type_data(metadata_asset("game"))] +struct GameMeta { + title: String, +} + +fn main() { + // First create bones game. + let mut game = Game::new(); + + // We must register all of our asset types before they can be loaded. + game.asset_server().register_asset::(); + + // Create a new session for the game menu. Each session is it's own bones world with it's own + // plugins, systems, and entities. + let menu_session = game.sessions.create("menu"); + menu_session + // Install the default bones_framework plugin for this session + .install_plugin(DefaultPlugin) + // Add our menu system to the update stage + .add_system_to_stage(Update, menu_system); + + BonesBevyRenderer::new(game).app().run(); +} + +/// System to render the home menu. +fn menu_system( + egui_ctx: Res, + // We can access our root asset by using the Root parameter. + meta: Root, +) { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&egui_ctx, |ui| { + // Use the title that has been loaded from the asset + ui.heading(&meta.title); + }); +} diff --git a/demos/features/Cargo.toml b/demos/features/Cargo.toml new file mode 100644 index 0000000000..ec3e4425f7 --- /dev/null +++ b/demos/features/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo_features" +edition.workspace = true +version.workspace = true +license.workspace = true +publish = false + +[dependencies] +bones_framework = { path = "../../framework_crates/bones_framework" } +bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } diff --git a/demos/features/assets/atlas/atlas-demo.yaml b/demos/features/assets/atlas/atlas-demo.yaml new file mode 100644 index 0000000000..54173f65c2 --- /dev/null +++ b/demos/features/assets/atlas/atlas-demo.yaml @@ -0,0 +1,17 @@ +atlas: ./seagull.atlas.yaml +fps: 10 +animation: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 diff --git a/demos/features/assets/atlas/seagull.atlas.yaml b/demos/features/assets/atlas/seagull.atlas.yaml new file mode 100644 index 0000000000..ce03ceb93f --- /dev/null +++ b/demos/features/assets/atlas/seagull.atlas.yaml @@ -0,0 +1,4 @@ +tile_size: [96, 80] +columns: 14 +rows: 15 +image: ./seagull.png diff --git a/demos/features/assets/atlas/seagull.png b/demos/features/assets/atlas/seagull.png new file mode 100644 index 0000000000000000000000000000000000000000..4a89d187e66816db92e1781074b09909383de04b GIT binary patch literal 87707 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

+UFls zp|i2P&TFa+s3*ZhFcHUgvFpX;0e!QGlY>KBrY6jhW1aKaqXCd9<~m8PvNQPn8hX$- z4@sxcBOv^p*zcKc-9J08gigS!X%~}{6iDXJdrD6rlf)?9=;__^YdJ~&CJW1y6^s3( zbslC6fGB@n)2RrY{mXyY;g)@#>3`9AN0~d4%c{VsAZ$s-&BNNJdh8O0P{o|tQtPQA ztBTXdLi|uV3kfX+F)lO);nOB1#T*sP_WixFaYhoDP@LZ5NrveaLWD@Romxm>$cV{c z^+K9w`se11Mi0;1L)#Ijl??R{s7z`F?$J(tme(74+vq#;@H6ncR~zzcupws}yHW8B zxe&nOsFbCScyac%1YDdrLbHdj^@OzRfvP?26LBi8gQ;0&}Jq}v62 ztbYxaa@8-qvw^kQ4)FLr&?cZp37yXwuJD=MrH^=knT7ayD2YFlH#UDUdE(|G=h1vx zwlbhIErjSJKEl#y>CGT1$0(@~5oaCu*@RXBVSAllXYGsa_}!mEPNtwDqh8JyO~oal z3P|yz#Sw6mN}w_#4rfPhdyzHPsqGUwjdW6_wItVDEBT1NOj5R$plik16qEP9++b%Y zJL)uc>(=?1EVKWe$xjJ_ok+}XxtU~@j)M~8RMcRle^K|Y46_AwLUViM#kdI_#VU>A zjvcgFV&@looj1h>HPHpdUZ@zJ`&eH`tJ_fxbeEi4Q~!{tGoBOsI##3LOZDfX>bzU5 z1TzO)crf@a5LtFLC%<@v{pcWk?vAZ3EPZp7om+!cKKc9gsq2TSeChptZkq;HN?0YV z(uPXG)^TJ+78DFcq32fyuBG!ll0HW4CZ|!5aXBSO+Y||C0k?d0W#z>)69ulPmJ1w=$hz;I zxGA59Bv9yrab44~wl0Okk|AEz>%d=kI`)>2?{4rhpx$vpGM|i5;0#89d-nt=apey#vLkJMp-mqCug%&RoBtqKHp>0~?epp4Z(Na( znU)hqjhD~6HZb(>OYcH#JdCz)9MRIbK8+Xu=&ir>vNOMiNZJu-jQ;kwpKQ#~?@@IQ zA<+7)JpHq^g~fk8O9Z>JGVZGrTfTYskIt@3_STZMKDBYrtImP@KD3QeSHJG%BRj)* zx#PgC!2l8akb9~FH)2h5_5Uo%{lHyZdD=z8PjrB=G2JdD+|6zsYu_Bc;wn~wvElDOsR8|98A7WK~*Y!|(D;fQ`wiOd9By|3iFKBp>3 z`%7P%Eih-lab@FmbXfVPv#$k5`}I%k>Nk2Mk-!|e*nNx1>7tiq?hgO0CB#uAt{vxF z9=(g(@rAA4E+5KO!+S~K*BmslzCo-DWO7||TZ>Y>sUFYu6#4kK8j*wL3*NVoSHCojUpZG}) zU#J%gWT88!<{x;aFU_aLD*-lbh^<2id|qvDWZ!>W`fi!w2g#Uh*eNyEJo7LXpfX_r zkQ4i6{^xVnzVXCsXM5E1sT4z>b80r01VBeavAs*TFUIFnaie$ty)X24Q?&d=ZqCuV zC$szdGM~*X83Ibw`2})>Ir%J4SC-}w91VCa{)I>=PtQ<8oV8_y~DH|5T|9!H?KNakNo#fX2=IF{o_?R=>ICu@?>Ow6NBfpMz|8<6- z?)I;x`khXUNok0NOo!-QPtttn?@>WbA38NML;Ke>TdzLuxpD(=h$7D4jbT(xk-;Oj zliYw$_BSV^RRF!wAY^ZdiEM5Xg_scW8DBIuix%vhOEMcIFLv-j+WV%jek`<&__p{Q zJZic-494ec=JfcQJ*dPa_gO@TY(d~6@1EEYD@7~5p6zU*;dqAcR`6g%okiq|L?DmR zE+JcDKj!<`I)vxdK$!P2Kz#eOtl(M`Kt6tG9hrO^oxn?SyMF@^I+4;dgL|-UNG`gl zisDH-0LCAKkzUx>&%GcLt_UYDfYz0lR!1gnK0tAG{?eG^ESH3fWytF#MUzgZHqnBw z5FmZ0k&YGO%zeayg0vDNK(-tG!ird~KFPtKbn->0n{o= zI#DD#p}%gvK1hn?9mJLt`K~ibFV%Ju_I=FrfdjlqVQ_K+y0H$~qC>=|x%mP$KeNMw z2ttU~Q!F#)a8D)1@CK&C;3b*`xG!A)bX-KarsTCHV_Fgi1gOo6>x9%92w$$;|5i%} z1~Z64{N!6bMeyj#4v$bwRJeX*`?>dDw8XJN6IcDs0H6mStQDsMy;@*%pTjnJ2ZNzs zp~famU3WtoBa>89P+LTQRZ|y-uFic~?hJnLfY^#KhbE4FajxfaX}lxYZ3{nfVuCS! zJ*Nc|=?N{xQ|U7u$N@pjCE3&QBZ&Dlqp>-5G5d_AtTK81k$Blra0ey7q0R~691@nx zE>)z46bL|QdDw1A#JdiT6CU$)0As#i*?L^854O1U;K`0x6};4kp*Tte#emWilyKYN zb??g(J45Cpy;Sqnfn-%1D2ra|q$d?o4nHG~Txbu~(=W;4W1J8vyY)EpxHm70hN(IN z5#~97_py#P^A&K2VoE=38?$fhC=Lw3?_Z!B_R&%ETKwojGZ*Q+?IU*EbUA>-%dNZl zOP1k2g*OM`pNz-KFyM<+6m~AMccKXDbzeLYUP)gn)qQeqopp}S&G~cI@jo$fkJ01d zm_x|pXt$EfbvSp3#j9uqCgCeojWfET4iDDGsiB_a5xI>rtGGA6&H1M&**b=#P%M@LTc)U;EOb#ONec;bK{n;0cRd8OfWu*)t&CRUhRFjBlbQWpkSvw ziv1W^AC=-&-roigtyMlHn%%z9+(DyS0cd0ck?!Ov(v-&QciYxw048r<*Ya=I?xz@^ zdFuSlTXt;E9}EkSY}ZHj_O}Tt1`c@pqI2-U=XwSX=;#?p+1uMSt5~2ixur>p;19Re ztc1T~L+oEw8%I0CM#{Z(;Z0qzf8)zH-j6ueo7n?`a6?cniE(a{JPOsJ0iV&C!ou$} z??d!ejJ`m6^4Y_j$Y~!!=en?H?~Pl50iF9r=i~XqilSEJQ0e}`GqG^!?r=|5a+I`` zR-XUtlA?*AUNX}NT~&>svDvD zb*i%*mTL^7+#Hl1JC2z-#f-11YP}?0ZtEIwhX;FXFO?!Yng*;ly-)HI4r0Fi$H3PB zEaGb!&a~yY1G~(9V_b3^n^1I~8Hl892Dm zOGFNb5&7%<@|MhRx`u-)^7UmCyYZQHOobTC;Snhg^Ik`7jO=j%JLZZC0BcIx28@n}a}R!XD ze%N5sy#)YUU3cD^sXo`!R$@daUbm+Hj8$yCophgV5iVay{!9{8Tl?Bil~cGGd& zNdKFzO>d3{;W1j=2?oOjK^o(K%>}Lv8eec_(VNAoe~=IZJvw{8be{M(F8X?B_n)7v zJ7?R3e4V+l1t|Z2b%saZTV_XN&12X@HF}z`=>GSAxciqvJEU^}z(${1Guj3C?91(T zW=RiZ_xOizuC|b^^2La4lqI2A3S~IJLtyvgs8fE%bth*zuc>Xs&i>q zUF|OB8utv2O*jM(Ob)AVE*{e1^Zy`hSpHM4XTX+A1#K5x)rhdxu)QMWkcPhe;YUp9 zUS@6hmu5k6hZ7=;(oE`|9Ot&$TKAmRK=k`Q^1e5|9|i7iGtRGkyhYLt_QVv-(63(Q zR?PeWaMaqi>o1!oNy{_ejX9HN_7`eUrf?!?SlkY?O&x&&k*j2|_b#}502BgN&%}nl zPryO-1B9OIiZ9O6nS{>>y@yLid@POW$xjUpU>Ka^gAHc#@ye{=v+D|zJGPfGR%DW{ z>xn84pyJ4@SrF}7EZGoK-ioLDj}XsZo4Sb1Dpi%`MNJFq=+;J0iV#@@U?=5VX)Ka$ zM#%GR@wKG=M^OS5P@FftXa+)}i>j}W7_e`3A8WzJDja)_P9q}7Z|#)6L*StOu`EjS zJ}>c+3@MMD?FY1p?nHL73M1hKOMievb^O|;fPWhB%6{Dhu|R7G_d_j}rO%2lq_%Kr z1wD;W!dG{*^AMveqr1!wlk;9p!Gj@Qj$AK-x06E%Sgy;*As?mZGkD`psal(`BG>!< zr<<@$tssm@)&cR5S2(n}X=%iQ8U@qrr$1mfu2gFvd~i0HT!AgQNPy$wnDYW|8+m7; z_E^A)!v%aJl@8LHjLG6;hAkxb@^!l`0k`oXQ`Q5fhqrIPNMS!V34w3oTg>~omtl34 zPt75@k9xU83JurouhyMbHK+6*l}vqg{5av5uVi_gID3sOm~4fuo&2$JOJ*`KMFVWk zd1buKJd-r5H2_0bQ^#PtoHd;caaBE9Rh-QT6_&SW;6k9zr9M^Y&IUOV&D4+fhqHG% zsDPsf$yxaM+E8LYj6x^Wu49Dcb}j#fhzWQ_;roxFNk#J|(-(}IUBm#VC*3Gc z!mFf#=BXP@mZr4?4Q%5!Sx;-HeMJ{p+mHduAA6yjG!zi-BLRYvuUP5hwr z%>8yCzYd2LjCoa>%D39squdOj4GO~)cPw$GfZqm@$!Gnt;0pOM8ueh7csG1ha>NNL zXB*~$7Wx;*^=v!tlJUV<>HX&J=1`T3OS80o3Y*<&>zZH2+GWj! zkS0{_7IaSfQz9{jr)qU%obT^=+c zST0MI6UZHIGQt#G+IG^plG?x-&J@Fe4)8yC4L=7mW>&ceW`5AOg2}^(Bkl(Hz#HE? zv3Hxn8`}8Gla>JjZfTb$Ke}455BS=QmbNG%?Tf3*`*I)Vj~V?Ua@#=!terX2U9BJC z7)7frtj2K}3nTr5UA=Cnz3@9oO56M@WrsESx}uf?O!57pDIs?u4Hr(PP?5S*lYQ1D zLt;k5DRQ$Ept{l1$W$Zwc&R3_Dh(L`gJz+bAmu39%X?kvo3wG}wNGO%Xgq#0#^F5Z z@N>@26M(X_{mt~itg*E3CkVTGPpt?n4JQw8P5S#&V45r>Wh-q_jK<*3rtmmzE{FuP z&kk#M@_J=BpW89uQb-G5?ISL-lw~|1?wW4v`nrw4099sF1_u5~)Pa1zbHC_^yV~uv zrkVYTvrN+ptnvsBhH%Zmh0iN{i*1qHj=O;YZ|^50BMo2v`o77jcMdM>w^P?=vZ$BH zIK9pR)tOq*%AO%m6m|FT-6$QZlCT@zLu8#r$bI4Bn#kUJjT$}nbvDr^b*5X9{(+`u5zIUse zlIW`~iBT5ODY~3nCwx<<$Mksgp*CA>-2DF2eyeauH$!TSt_Y5Db}IP}gMMWKIeHeM zoUO7{Bm+ZP z2`D)s31jIeN7`je6lYLBPbkQ_jz~pYfj)Qe&r@7NDlDr>0YNS4ic$5p(%^q;ue&u5 zNoxi)4=ue=8^dH_h^7+V{lQ@aPFi~|nmB0qwukQ!PJ{Yi?|xK z3P!1v5fi33Rh^;9*B3rrEPLv-Jyn~|vd#9Me`{}iBlj`CkmiEm8b|BKS9^8?7<-Bs zvNM>NM!)IORVI}QMeA6&u_Q{0n&4}7LuRorlh_W-Aw`oFsak>{$QI2b?0*(*F|l(4 z0z?suEH%hXin@cE&xtsv_ep8#Y!ap&u-x2nlIM1C<;;F}fL$6TQ~5^oLjAL~)Hvrx zHOJtqIg(Dp`(Q>|z+ClffktTaM~7rj zy?FkQblXxT=Mi!xlvAP2gBRqh3J{YcnVZQ%)j^sFPCl<(WdYy$5V>O;=HUHOfQ0+5 z#y5DnSdRxClJ>v&l5{dLee9H< zX$fFg^@~(3vHp6H9^BxyW>f7n6TKci=Cx47IEDdOVz$L;_y77;wl6ScD^8T%*nrFj zKZ)Kk{cJO)A%Q$JQAhLEQN_;+gR7`C6$ll70Eovry$NWn#OzN2?}|)wmjsNFT_xbC zT$}uE`BH9<{nMaaSB^m6nnH91=7ChK^2EjlBDuubJ$r%~4fD1B@hQg^(}F-$O(Rt8vWxd8iL@_(l}cK79ldP1GGp3y3?rJFx(D5tM{{DVD5iq+(jH`RXDS=!E&Z=Iu0sCDKGeO_cybmm{tPE zfBKUXI!tt_x@LSGP?GDu^(p%=f_uos^lO8JGHo!`Vog`3(eFVaue~sVwZ4Dt7CYIW z)b%`K?~6+-er@Y3g29@Z&8~HK*mkbWs3xl$6)AOaX3l{cJhDIeoUE&JF%7DjWBL`@ zCxVZcIz>e*`C}ug;YE?sHK!iA8>t$sZ|lXr@3*GBdJj%^+v3!FEm}j6%`Y)kw8zN_O#nI@iD;RvmSZ3&B#k~%bgOTu{NUm@{vcCJqn zCnK;Xa3BPUVyfv~j|m##Z}AH&2`6l$5!(A@rJGV=A9;GqlHSyRc8gGwO5k6@@TC5X z>9pK9ADN(Tk#bx%x1GG{%oAU7Rcs=M%slI26UG4rQO&WhIGe6Xr7K5%>wNxfq*>}& z$|uD)L>hZibK}r6^!mmWS<=|hhEV3GWd8DLZX|qtsWNEq4xhYYeCa!&(#}8U1LGBw zx63#U1VW`*DzlqPDwNHK6E%V)jN7lrNRv7Dp;|61bzKd@W@$DS$!7om`S5?v=l@D1 zan>LB`y11QNJIGloyr?Tfi3KO!Z3W`_fek5N zUAWbMpwIv7>}-bYen?_d^q{PQqy)wT^+)=DaGUSu)F^7K%OO+~M5P!tb4T~=d$rx< zPx@4vVw7+lJ>cb!cJz;W51g6yKV!@epp)h_`6#Qv2l+@G&NO=EE(i)x80v_kN<)w3 z$#Qf|Ne4g)Lt3J^84Knp@%3n1tJp=`IB@5_FS(QxIf!V&M$RKZ0zWf)vqZIHobEy_ z6VJW4GhA6Fs^Awbd!YNDB`joLO~3`+&d`H}!3DNL^m570{OAEL^;beevgHENL)+Pu z9Sh8&@8pt~mM{{Nokvqx2yp$*`L^AU_>-!|*WG!`;q18`d{*V-G$d$Bv z!|vx|K^hSS1y?C0QP@R>w`8MvT?f_^2isFkaJ@Hh(G!hmLaA6BMOJ zQn~Xc#bhjo({55b^2UT@f}+w8WUN_bgoyw~m{a4a5S_|Hz7;jNP9s-UW9xw(2YX#! zKcHDU5bZ$?I2S!#50N|Rj0k1~ zMd*nfVa}gAYn!cY?C9v~I6U{pxg*W-4yx%PW5h*nyfSF+*AfCfi0c@=?FIAG&>4MQ z);xnJXSgu(!YMzb;x2P3vJ}?By~#0v5+-O1k8Z34L71O9ua|lI&>aVT!^qm?F_XW9 z6nv(W-FY1C8Hxpk>pjdz9uO?zUHe;r3g!3tweZD;vlryeDr!WLu}OEx;Bye8n{1w< zFPTS*yny@2=C-ZV_M`)MgNvqqzDV5hak&dD9Ls~E9@NILqN(~sVn!dEI-yIZh0Q|b zvWl^`rQKu08j=U%&iSf+bS}P5HNxkDM)+_eB584*ReqjeA6j})^OE5r5l||?DTe#{ z{Y|&D0n(pox~m=^{NZJ(wPK?vP^GL>I%dC?A0 z-CI}9_pb?b3u(gSxP@*dH?(Vj0?WjIm$|&KBLmm3A=L^AFCSjH!XqE&2vgEJ#lQrL`=YIX7J@I&X644At$di zkr&OXuQz+GTkyoDm4Y1~8y;3WyMRDWTIPjDP6sOch@`vPYpkfVdr2RWl1HH}&Z(%t z@(_i-A)Ge9{w6|>F1!)kP=mQf9wvu8V(;Kx9qK*1mcFmq~P zxX4gXOJQIh$6l5rO)yAyhFEjhfCnL>&>7k3)aq18(Y|j<57}yr_Nw@G;nRf zM}S^ppF%$-`Pu{@$zeOAe%r?QXh&q8VQGM?7%olobQ+^%;LzyB-aX1<0-``e((FcP z=b6JJCXTUT!8UgWYS&wyZV1MW5e$DHc0V$U-VS2gPv0L{D%UP)efUcetad^tmNgeO zND(*mWbvj9!?Xs7<#dQD*y`rQSZ}ohpPzj0;tr9vn?GXO$7ozNstpm+hV1w}+|sCU ztO&FX0wVv|@0$S-#6+pQjGlw0M%@C{6*aehWnq!ecSlXf8K;b-W zZPPbhf4TzeI2_UGuid6vSv;9D@_lppgiEx#uc(s*Z9H{^F*j!j4-V-W-HK8QSiUEF zR-$}QW}_k-8;tXU#tAK1cXyp~Z}EBW>4AY$6Q#wP&TjXKx+5#7k0tLqjt;I!l$(s}GY{GPI7Chn=osqNmur(3v6LXXuPh z0X-zxq~^azeqG<%(Qll}xzYY)HY?~euF6h*>Kdnr>6l>}$dQ70?ZyrkcX`QkHyUd! z%r1-%7grfuc@JxGBO}Nl)%o@3*ZR@jO{GrmZj4Eup0m6-?Ty3-cv*m?iy zhBG}ugw7BaPN8o?!b_JgzbhcefXcCFtmO!Nwv^&+faLj#&v#_=#+-tXBiPaEf4^1u zzr*`)0^P1`RV5aud5WfjtnRt0UniB}hOrS1x?>(qjRYqZ;4CRRSJ2${b?(#N@>AFR z^g(j;*da(ryz_|luTd84JFk~+;E>#tmWIm22b+hp*zx40Jz5fv@ux%r9U8fZ`Im1! z*9HFs*xnr=hcT@bQm z`NQ0?&(5ZJ@?3P0L@qZ(JWv_EDZJp8I`|=I@LbtdJ4dcYI94QcLnIa@805mC=Yx2q6E1&B-UzpqA_%>IC{G5s zS$|}E!TZ15@kv%n_y{~ckHJh~&~PZL8_`J}x%c}yye1062a=V@E4w&Z>0z_3vh|)| z`P1A;;eG=8w5q=q5N`y&EJtNR#UoW28xKs~_sO{IWYrCc;pfA` zMdGv&@obDp4B^Gj5c_8b=xd|MZm`HQP5t32)DESXk#2T6j7teZ`@5T+j z%*rP;o%}Yz|LwpyVpzrnN91@pdibnaRw?Qy4LMJJ$ zYW+I|XBJz69rT|cA;QD%DO4%O65zZbyUOZ$000Zsf4>mlQW0hwb3LDRaFVB)JSsYfc?*mTPPElQ*I&tQ5qzxX>TY2}K%+cn(El73#HZl9k6u#C( zk^*g(IwS?I<&hr)%u4U3kN7YnHcCCZuYUYZ5?30-Xe6abql+W-F7wuz{}Of z=kF*e^I;9J=)pc+STjRIMn=(uvL#=OTO*5ze40W8}4zC4v z`PkfDUlIi0ejJT5rrCVeKe#1?toxu!T#x)z$2LAhn`PY%3UJ&!lYZ55SMckcGwB#N zi3>8Gh1*+2drpi#`rbR84ihpjAQ1zhBa}LIJ}2|cIem6+bfNS|&&jkgD$?FzHQvnx zU;)(A_(dIwkq6&H+PTj2zriB7f?1(-%gSZZHH#uT+>i&_1|LUjTFkIO?x*9+m;R7c zp$*_u*WtkH91Z%!7s77qSHojk=swqL-2DB^ki<8jHM100ADhymc*$Jxx*()q6Cwmk z^{LZyBgIe8^>vO#KH6#wah44RyR)F}u8y@pcq``}gs#Q=_OGDahFuCsXFDP+fAFK` zo+cJMngjLjE1E*_#?eXeM+|9rw?5})nREn=JqNaAiGm{?^=@M@!LWLh3`Z1vi8oGD zL+apLAI@HjdaGn<=IAa`qOWM565?#75$yeOR3AmrP4au-!Y?uWdG!O`P|$Y`@Nd zJv*5M8r?Ak#!QZ#SGy@Z&+=6#Zk4<46XP^djKD!G#mxJVw^JKM6!KWN&|9^D+m0~g zlZya^)DMXpWbOeyYk|TI-^Z3lPuH+uLSBFLKAM>1VeLcA(Z2XrLe}k*BgM)HfYwXL z*NClH^PO4K-~*O@409tL@GdnAxDG4BAKTRPFs0i`kzqx<_D}@IC~pC)4H91VXB|D} z9&mPUW?lbjipHOW?B?Jap-1 zo!Wh|-%Fp9T}c7?KAj}QfFWL|zqw}r@)vcjig(b3(RAF6T28S4&eIOM8aOYob^0em`QQ3cb1W_+K$%1;Q`KNk+0^tGiD9lOJh5|;H3ZSW#m&mxnYH28cF{#$#j z&sOv)f1as1+`O_yS=Wd=+P4?GDX#Qw=soaT#qbvyhk0gz?**7XLfAI}FtjRZQYo;& zgfnRkot zbYE;|))9cZS~vh)w}039JvzjHy<0Z7De&NKh3Duvyaa4t!SS>3XUgP+`LWlW+Aa9z zVLS2%*y?hmmsv^2&|X)^6a8`5dK@3E%nwr#F56NImiGK?wP?vA^XYyeo}fM}7Cj}+<#_B(~o->+1KUPULhB2R%4j1}~GKOoKYh->YVlkEQ1M5_# zS0O)1;)8}|iq3zZ6)BN$JzQ5i9@4V$1I9k&!ju)n43I@7!)aUpXl;}jE+RZ$lW`T7 zAh;8BTIq#N@coR<11}HhJr^=10QA;%ufj&*VJ@|-``?B4j|yIRyV*<0R;z-;mcCiL zd#KG7STnz1Oi>J-n{Jxl0ApR|4R=3W;7(d&AX)1}O1nWPyF*I7%o5U=X(_7RU&1Ze z>ZsnXaBRDI0Hy}WK%M=qocZ~o!QyspC^8JnKv&F>Iif@wK)C}}(;9{e_2#9+Tn6SZ zL1D|8t|~|4p>O$ZRl+^{T)|)~nu=B?ZDpNl5H^+%TSe~_?5p54TklG0%D(mfak5;5 z6>g6GK!ynsh3cu&iQ-r+ys%?FHV)OZzbqk`xQ<{Mo)0ixuERQljic9JoHqJ!^;9~s z3oZ~dx}gLOu$0Mu`1fTMea9QxUKGSXe=W<`@I4_>lpQ&{FDVfwAqe<%go3}qX&43; z@1(*Yn_BHW;eEpM&u>yKO>=8aP>;&w+5>9aDq%l^{#SFv>BA-goX*v^0Z5B{pki@!%!#Pair3O6X0#(ZmTdL^;eM}hWFD{#5io7d&hYP0k8u*!+# z@i(f?{L{oQ#0jJQO*>L9QR?(Zfq14Dv>#q_LrXd;`7ar$08^LYAu~IgA8u+$-CTRo zVX~{G4sbiahv&+g1qlxs?Va6ffM%`~LenBk+*Z^iPv2ct~kBTjgY>1IME+wB>18fzw!O=80!~> zB)SfbhT+RX2+P@kEQ620Sn5@EOK!EvI7IY4asb=|DTWd$E?*BEMJtU!9hIgmz{|JQ zD^q9{*pUS{!hnlv;ny*DOm^vLGzHyiYG!u(wrUA6wXChR3j82wPq2FIcPPUGWPMP} zI0&uPGYB(?%7|xAP>HfC@{ZJf9rh@`Wx1AlJ$+67c_v?Tn0(grS=Ca<;Mv{Twnu)eTSKJ9m(M6rcol8qn%1p%68F_869H>1Y} z_jwCC&L|2+3Mxd(_9dn+VlrL}vjLsPpNT%&*_HjVmDGmoa601hG}%{Z3&Zx1@KIlsCNViuCIi1)4fp2c4#(_{Dru;;-s( zvqyVV6~6eOy6On9m4`kClo8&$ba$hdj|wdw47(heJd6l7x!46^4A&8P&hst^4&l4V zY*w2zQ77_yAZwUSB?&ty6f~Vn3=SG_h9=Tstm5dq`V^0b_rk7z?%xw$J9BtxYvaJP zXvI&(FZW^kBanG9hydlkui%>Yw6LDUi}q@EH_EPO$s_jD8?HCVy}u1Qtop6iQMWdI zF*y^~j`Pqr=2(R0wa!lT!MYDbYqzgVIm<*SuO@4~EuOc}2bZ>%H*ow_jvsI55uv ztyym!i(*PxQfOg;n~1*UF_@52oU3Z4j3Hg^`dY6jAV5eZ;`$^uRcQ{a5U|ZV{XLN( zsv2_e;u#9^^;wVfpaNW_@aFjvPJ@a^Zy0?l`hBrezbiPT;MicrYu` zB?|649r;lob&<&T)fgbc8zAv|AAjHAo)~23j=^D+-3dBOiQ>q8-uOGWhS434=`uT! zDf?LFG9+Vsp5fbRp_W0EYv$pz8a$_p=_j~%40Shv0Y*_N@DMamvlKge^;ypS-bzKv zh4HiaeJimQELm zm#A0axmN4Ve`SGzIE#{9_;UYh=zmN0i`Z8h*ETKMJHDJrx)E0y{ab)Mnm`jPVT<98 z5dIo=!KkxjGY0xw8Dx(M;9AMtJP^kzhXR2=Q-JA81@@>pBOH^x}sT9?i8xJ0D<`bkW%-}9rTBAQ*01EO2f zCf%FhPW$RX0)D?~yBB$fno=jf$wm#~;yu#QIL@=#lbzo< z|HctZVjwmn4}AW=LNlm8!Y~+90G*s@KqNk!82(FV0nytqFL2*Io4o8y#WViR{6K9w zzbe?~B+6^T>oxYn4XmK0(aSAb)Xudn83-FTLFRP?93>vHRMMM#vIkV`FPa)hW$iO_7j*h7$4j zlBZWLlF8ro0^WG4hbwauGK1xahh29wTQ{Kdo=r>#|681ftDXP^@RZ6d#R+@GI`rr3 zM;Bl0IWW)*WXx6h0ipx@hq`Rd)6SB^CbUa;K@gAzI(XifFp-6Zl2Cl3VD(d;r`q`4 zx86mf2mwNVhnrr7m*TXF{n2+Ch7~rDOmgDI*{F%$3&fY)Z(u(kBSz2wTFqon&wrAdO5I|hwgEt3$AF1hZTU%L zO9Ie3!ulMju$5{Cu)&-9wx9you*&s}xDowCxc>2+@I7xsz=i>?@dQqfSGc|M^EMA6bD$X0xDk0nA!N;EuF*_UfYuySppW0DABz_h>`J)61eCap;PB zD6k>D=!#hSb3^P1PEI??JdJn$GV@I1TCgikP)<(<_{7L@;`!52jD9d)|acc zE#P2K>dVX3d5-&XId2~^_O7r5?fotEk=RBIDr^{;hSmEqA`m1qBo(+#MHQe6Ih?clN4#F{WOAM(^rt`95( zOS)|OY5s@!uI-v6t`GXN+7dnQL+@U#IRrLjl1kQ7?5sZ`y7$PFda8)AGN*6iAT@1{7(V-L zS!73#V-R0EB2Gr!bL$@ncQIFXsrGnlQA^G>cGZ%y|V(b@AMELfRwB%>cHk*-^c2R;Zqcp8=uIUMKR z!)d~6*@Y076a`<=fRwMyc&{+`R1qN~<5|W8hVt>vb6-27m!TvPrx?f$xf&QlenV3G ztd`%NtfGBRaO<$59~pmMS!?GswEnTXeHGRwy#9 z(idJb_IF z@rmJWU#Ib5=URd?X%wjC5qHOk>qW_q-8X+{u5<|>u0m=&!5VJasy z_$W6N9KC!Z{xq55x4j@q1oxCx=ea;Z2jmbZtGlLgXpT+M?r#0Ubp3EmkAD{tV?Vjz zpR+f2?Gw@)kD(JUyYKv3W6W68#Fzewn_6LnJJGmC8-nC*sEfcf4BQqx|G;`9723*# zV1Gxz(=I9uN78IUQ_QR=TtEFt8DLg0!jaQAPBi*%hIJPN%R7QGG&~*4vu!b6LRc>| z((`2i8{ob!JlKpaD{?Y%b;%R2kNbS^izp9Q9XYNU#V=bP_wy$;U(^S>hUeEeA+G=s zA2bf-2RC1gmEFVDBkH}DD)XNbEGJld28&PRiln%sq;H?XETpBLM-b+wkgIcbg<<~N z@Ba1^u7sFP2fSTr>HD)>5{ch&O@%jycf5wR^dx)tr2ph!}r^d9!ZzMRU%c($B)TMOD_uX$LYWm(-XP^6<~_Vpq9s z0VDMl9Nw;!0qixZ<>xZOA7yh0ptM)^gX&DlfZEH=xc~Jio*wLeGNROO$*|8jdd%S| z!yb~&IB%Y65S1HLbVf<%Va+rE*?ydam*E<~8u`q4FBaxXEz~30u&wwz7a9WZVHZgj zxj#6y>lwBuXbU>1c1j&T@tHW_3;j7eZeO_#L(%0p0|37dxqkxFg`+=>lGa`ydHf2> z;mf)x+`;qO|P2`vfQn086*7!+W)bWRa8t{kjBdim`!vph^)_QnRu4 zSUpDUz1+Bp-Deoul-gRr&RUm4Si=6&ExfjzeYl{)0HJuOCnSAM&Yl?d+BU) zv?HX+NM5c0xBjfbS>EL1+#N+w&I4&F*jgYVfdZ0YA_*!edvp*ou7^w!!X{4`Kj*gM zk%rg-8DW%r`Xn;;FHWwg``f+2sMNvCaOWU0iklW91s^}qE zBw}#)rrTQQ9r8Y?kuDx8oSqkz(FpFwN6^}}#;JDzbr1J-Z=Zj1vVFwIcF`S{xT;L< z?CKBkFqIjeP8Z(;@pueQ3Q5yha_kM1UV}CKsEjDAdh7!Nf!a#!j+Beh6nSZae7qM3 z%4san+Gff#&gX~&k)cPuQ1qK#P-Oyj7O==D?W7m)RDDRqjk1&VMWS^D( zFE2RVcL-6RIYIi7?>PYBdr5NWEz?_?n{S8M3{WV_vB|vUWbQY0Ok^_yVM2^Pk|nkc ztny&y4<3&bs|pZ1G;t+N@cI>I!70$SUs2%ZACSXkd37e-kfchB_-_}+^AO=!+I>&K zFud`t7MhOgO6%#I4wiz#rkN3Z8f4Mau6N*RQ$5=);T$D34=h?o42i1^`@PX%h-i^G zw&1$!56)vXr{|&R!X+~Bp8^gNZq_@lxGu;~nd#oW)QJEU)%_T}B45iliW5rven(%o z27UD3%6dAqu-Lge#xt7?y&Mri9_DC`;XEGEmNBobjVVY&8t8ni5g=I4Y(`x$fU&96 zfJj3&xBBI$(u&_(<6udGyTNduE{b;0&TI_XDoN5mz;^t=A?>YvFWKb$E)`qDeDoaW zJ|ec8Xg^%3>z5g!TyJmqI1v#LY8S#MT4UDVF@M*M2y!k^vBWb!1M)&a-o+XH-|l08 z*w=y)b*a{4;e_sN8pbqNwk`-N2+swVA|CbuZPG$$z!?e1+&kO1?aWulf9tjJ^Tcme zKc8okdOY`#Y*8OH~05v0fB$mlU|q zI*xHlsMk63u}Z7tjT0K+@#{u-qxz0* z^((~$V%&o2%}%q_5-V%q+>UNq)1AAQ!200mO4edM{YYXiNnC^1L&dMJX4~&-JN@aQ z`yKeTKk%=V(;}*aU)*GO`=?me6X%xz0XD5Uub`lQH&}ck zkONG8?A@Fwa4mhIN)oXuDLQTQv9yn!=l__OYLjs}$g{dYYpCn?Y5y+s%?bYYIDiAE zflAyZShwC?T8K9Jw<|-bqJk?t&pB`_o3Tz`xx4Fk_Ap+(L@g~LT%L8}Vn~aIe8@0n zHLyO1`ncETv(1XhV*lz014I?nx6a{Sf>zY%O~3`GdNji?PoW2Et!6Gnp7 zU$7f{dKunh*l{~$=nG*jbUK}5L4KgBD2tnXbK1r!QVy2annxgZ{%;V)$I0@mD(Veh5*Q>9Or$t_njoj zjwq%4Au5B>%yvqHhmtPKT~z^p%Z89n7;jtCXZi2N(Jmy|I+nTHK#cveXo8PD=i!2Q1b(>e>+h}N!USa=sa9AmhyBWO) zKx4a@#?W8LhO!lXn5Xo9*QvwDtiq{|ecKY;z;*O5zlM%86*tUbV|{gD_OoczcQMf( z_r&B5Tu?lveIGj5W~;bjPC$%PHM25>(E|!CcSIV6oBY0Q>Qh0mivxG=0W0xJAy2I_ zFR2c8OCD77e!;C=M}+=NGh4HAN@?AXj*YM9(CYAw(T&lbRCG`>o8G<4fs0^z(4yr3 zdKa`=K7#toH6);M6B7zGtgp|}sE?(|rDZrOdNwk5@N&t2)85CR2CN4MD@-tMJuFhRh$3FTVsTV+v@Styl z1ju?I5H#lqmgwrn_DXJ2n~so)IR9G1LIm-cZtguON!-kaY`8wG5@|=U9(kCF5DH+% zv$kBnzO}?5Z*oqiFIF+nYxGifuLsg+@Qur8n}j`9Nf&Kgcoi<%EjJO`5Gi|A7?$NC zS!)_X^FAI){yS=&EEm0-Rka-1X6m;|M=&G`YiKD1Ya_Tpz`~!=VJm2$|=za&K zV2~z&E{r5w-V08!%NfcPEP@|g8PeJmr+DS+JV?fKk8)yS!Irg{PNe#&SXVX}64tlzg4|9wqPIH)KYwVcG=Qq%To+*t zVX)w>hX^UU*dQ|ul2k0{B)VOmuFf_;qmlxx*qWgb>(52i+9&>0%WGg z{F6YXGoLL?rZW24uT)_bzX#a{B?W2Bv)RFeIA?sGC0Nnt3pV$ZuQfL7`AaB^sbQ8x zX0t`C4$mXwk=9}2H4^0}Fd4|&D575n#Nn>Q_Z$fym=Cw)xt-vi`97u;cE2i%6q}?e z;`=i-?kxU?Tr%P{8=!i!qmIR0%+!TmI3UIhgG61PSC2hSW@f1v^Z85JhTK^F;~esm zCoZ?U_N@l2)C8gyS{vsEiKGXIE6U)VpCgLDVEt@9KpmqHSO&otTS%gG_YDa-G1U3& z%9h2FTs~y`<8D!2vykzAw}rhj zbA2HTfl7xT3=8;Kr#xY6U8EH5vqfU0B)v)pQuVEe*JRS$XUpKTCO=-^cD-v+Lr@_k zA*cLIp#50=k*DigVwG_K5!%x5IRT0hMkD%FQ)ww=#eedqNCHihP+dgBTq`q2CBmwj z{DSJe57h;>+KGM+hBLvhlIXp2F-pRxYsSyxh<&N7k;2!GZ8q&D&3DE^+oe+e!|vfV zUO4wWcb!nU;&+)Xxy;lNFbp#VUm4(*z0KO7syyD*;v!-#Iq6jTe*%0X;^u(bk^Bz<~GQgoaNKEyM7u6IlT5((qywmoBKY+!< zH@93qy8Wi;=g@yJ9D()9!8b^2ONNI>)G#1CK!Lap3 zryHuL<81Dx1JdMPI>r99sHn^>&CTDl2~aOQOL12TG~6Rm*=_ZQiuo+VE|59mrQW92 zJx$UN&UgN;+Lf-k4#FUpJ`>Z)tuWy5%-F)i`Jkwo{8m0Av3%yIy|P3GvhCyKURk&l z<&_2dm$(1!>6!dv-_82V54?i5?ofAgRbm{UXqD1R@CU-trBMQ*Ux3K`l&9R;`r^xk z7gA|e_Z3XM#nuJ1Li3EwJcf5XIC#^YSLLAlc3N_+uW0ZP=bN_~wTO}Hlq2`1Md^Gc ztZftFc|E&o_mqQEz8)Nff1(^A8Q`g$-|99%jn=&bIx};u|GaS7cmN=c)6^bvk~Fd~ z2-{OXWI(7*eZ*+f5rf5Tzf}ZRiq`+Kcd^owizmM2l-&{}(AU7FJ92tI_-PVz5P^*+ z#0r_qxPYE^V|yBMJC#-sxw`%(xSXQh;=({sOSR^ch6D2o5|6i-L@?uVmce*;1BaX)nQv+eNuG~K{_K2aLLZxI>{BPk<3R*k@%s{U_uss5u?Ni4fnz*7 zF;YwhT+sFJm3=(9QKD#_cw)6aP=U~F4ISrl_*j;Gy9@V;1Q}hP!pGE@f`Ae2EH2&L zk0m9aN@;L|KcD{ty@UN9JaHmA-M-ksA7as;HCfzsbi~)annonV51xC^g32WKP9O&Q zuX<$#T10s6J;@$-&o3UMmMtEMi*6ii`ukgdp(N7eyf?%^H{FLjS~CI@08_s+qKrV{ zU~hf>C=7oJsYQKHp9bFh(Dd$3f<>f`K^}Aya8rLJm^|9wU9}kMhOge6YMvsL1V_Ev zpoEla*y;~p&_2q~?L`mL=>e{)%@< znu15QtcVz{8tZVd%h8<29*LIj_VlL9J1%}+8mR6OKGygdlcD9fDMenwL?v=oLitOR z>H)z2Y!Sf!_I0|`_e)dumi1uVf6g5YkPuQ9Sd8f!%8D{L!LK`Q+uno6n(JapkKWFd zcLeG>T!?iuk{JkuIx`xWT(y z{Y7$L8yv%8MWn#<&k5%NZ&8qYa*_iYIqu%QhQoHAzs9r56k`IHbq*~}Jr1b*1>qvIkqN)CvU>j@)snqp;kQ!eSU$9Z!N(+dAh+dF$7Jr9 zSn5Ur`xZ6DcAQ1C>-2w1mUO!3g$<4(-Cr@}GpzuZgUI z%mBCzxxf^nWeYpF&(taB&SuPbKKV_CyD&es-veeyiRSq727LHMYh6|G6{3rnDFR(^ z!Cw<25|M6T#YBvH%7pxAi?cHr1kzpXUBAz6Z1DMeewI_5+Z>VdZrZ5OUZ9IXHwRpg zav{Lo+1S=ubQG9^SzS#QLx6d&0j#u0U=>fh6%lB)etpr7L1utvHocs4q1QVcMvE)1 zm|2*Bi3ih?6UquGC2<|74Z1uJHvziKqmN@G?(Y)sreq4WJ!(u0knq1ne@Z<1ch2P=0fZ&zJuaKub*hBEhLHPwB4X#>1cwim^1((Yn-#!BO`m_ zEn=LSLLLd9-1u7q8Zv9Q)mPy7&CmmuulIX<%ikea1<2ggTVBmywQLwUM?R*KeOc`{ z>Be>U!%;=h5KKhmNx?I9NVyV==9WYa7b&KfxzH5?Oj+?mu;71*CSCi14DW3;ems1( zKKVT(c|Ey@4DMrk-6R^U(x|)fDO_sksXyPgy&~I)@05(VB8eU1Z{o?dttK9W1PASd z+Q%z=yP_QU2f2HF?-}=(R~|ZqgEvC)tchyM-wTFZQMN|5KH4uFSzlicP_HA1BVSEV zwyUG;8ALsbBHpsW!2#n+F(3m9CM<=~D={ouw3jr^%Aj?(>Cv6oR#^m%8@<(w#?^Pu zy2*P_(#7Ww5k9DC{MZuvTzwbtK%@%1Z{Vi@b6s{@}KH z1x$#|k<3=Q-p6~{|8!JiOOyT}jCEUW5Ov>Y{O-!ap*TY*o9}?|HI8V^7JIXwo({k# zk$Q5gtdhV;^L^IT1Jfa`p;lcD(ExdyUWQhcU5`YUKZkKNF?8_{=x%rgi6TL!*YGS> zoY&GGPjL`=q6J>8sDI4o#?RAid0ks21{;W*U~0JGttMA+S#;*J(u4+1@95`vGyPG{ zZ^8ZXYcbm8c_#P6{wI<;%wzjwtDrMTLrd#{Uq(JQ*^C)utIswxi+aT~^IF6b~E%W zBeM0p>i4NJw<|sVYpDX=Wt)bb!9x{mPg4)yi{gT_hd-0lWd_Z5m>V43Br3mqG{g;G zb)M-o;(|WnmpJE~a3Lx}Tv?S$5wq_OueEg$v>+!;-qH%G8kEVL+yf(R-2>FF?6jaH z6Qm-q9rDgR?42YdL<3dvg>Fv~hF6l9oC};w4ZFc>_SIm8gDZ`fE4^g{k?Z{XF5U+P zgCjW4V+@7tLdh7gV?Wf+2l|cLlX<_2#DK*%x6I+sD93voR-hZcLY{U{uvZ2zf&&+h zh(-!kN_?Hti&%S`YI=Pc-(q^ciw%2CV?5!D>J?cmn;e@4ct20chvGL}&1-izS zjGiB326ID!WSfsBHA9{oARri#yqWKwN+79QbHmvZvA*9Oj>IhRabEkK_%!KH{o6lKzAKBD}zQCR~cY`!iNOjLwd%WB$$z;bPfdM%0{ohh3~79S19NZZfkF#yK_o~J*aZM&*pBJpKs@U0>gPP_j^_X0S4p%Nl%7*2r-WMS!KSrT1=4qX`3TM z85WK{y%e0NC=Oqxt7uwc->1hl;nUhB)kKmW{ODc*O1n4}B`BVjU#vhg+YD7)P+KF! z2El|9!<5MK)|z}B*R3#6Z}D36?u-r=q^6(&$fuV+9+3pY5YPb1i4qgrKU_QJQ`(h- zHV%zPA=7bJYr?k`9N)-QxG%s61sLZ7#qh3tlAoT7_3|1Y#vqHzPE29w&M_%FXlmy% z84Du&>glmU?<*|R0a|A_@@#sxN+A(up{fv!+{*6UsTFRuhM)3i>$gF~IuwyzZLV+F zLuu77m)smU%U0!CH^Ha?7vk49Y!Terkyjty2w#gwJKho_qip4Wx%>QaAJ1K;`B5sI zxD-xRKT`~O7)LY)wsBv)C4-S&EbWF;Tp1 zNb_C1pirCPu1Ufq1_57$1EozzV0D$H~Xc`Gae2M46~xV45LCyN)|r8 z>S6DS#S;=)f0WbRN#Gq=hor^~J(|~Lu=B{@{79>WlXio3YN75xRlvumu?u~ApIiU) zNRRz!)Hf?$j)#8cKD1&1l>eXZX2!@kh($~sn+Qv1*QpF48&^BucRs%l-b&rDGOEB7 zT^{fyG0FL57E!$Kmq~}V)Q=CUzVXP^-{EYBFyM(8O!SYZoWEe{<>ETxUs5m%+HY)H zx5Z(alsYT@K7dq^6eNmM&}v4ROC`-PP1X?G`e?8HDb`SaL_Q$E)aQxgGS;f76$TV4 zDhwmhmcGy4{aelM5uK29{s4VVR7v113s4U7A0~@(>zZn6=;u zjlu@$`XWf>D@`vXv#GU$F789534xvzc7(X{aDyn&m=JlC219Ex_f}zlp8PmQrSpNQ zHg7MCo>6RXrpvU9qs=VXHfUyZo@Vj-l3n3AT=jVAg_FIr8Ss0=)8C@lS^u~4_0Tv09i16Lgt7oJwgRsv%F5%5ny zF9+doZfJi%8XtLk*YgWc=`i-kd&AG1X{1`Z^Zoc~*12+sda;HQ6&;Y@9favp%L)v{ zs^@t7m$F!SP5M*vpURpN{ETbA^*Ad*GqtR=y7T^|t?>K7GrYs0B!p{0<96N#&n-|A zg46$yV{&y{jw4zLm&ONhDg=r)Rn#&d+Lwl=p>fejex{YJOmFN^MZUcwu^J(;j}%7t zd6yQaTXdzEz*WvIDk(U`z>I@WOStgn;4i@-8o)ebBNon2YR(wvnsy2U5jA_sZljm^?f*qMc}7A!>qqi(jLaqfT!CRh!i) zdv{H|^%XER#v4CIt(XP?(j_vXM{ZD&{_-9Gt=LcYl$u#=CPO?rP0j~UJ7^7@^$tU7q?o4h#2{-Bs-Z?sxH!pB^DuC~t-8 zyu5B4sTQtHW8{Yhgi*u630s@6l+MaL1o^H|3yY^C59s=hmKKsH24qVCG};REiIP&x zyt&E+Dh%|m4Jksr8IiW-gTK3ra!oeS(hjE-X$zM#`KhzY`#soe{c6&W%}gs4K~1hr zMkcgV{jNrRNI9WQA^Sd5{awsg_At%E`*#hC)dJYOc_r9cGD74E2%B`}XJ+1Uqoz3U zuKQTk86S+a_I69sYjW-f0;?5OeUib2P*;_sT{P8TNYgHxfjm{YuUDm4U{bXGM_;=> z2MIt4f6*NLAjeP60H-ehmYxRvYZ{ihN0Z_(h3I@rT9SR1-n=aoTFY?VR1jm7F2(&G zjv1nHeFCGG=ejOj5LltA!ajtfYu)k{S~~Kj3ELDmDk-BiNqt-wzUc>)qG4O@f~%2w zOE$K!vMH6Hsj<02GB;0W&g{UeLkQb1$N2(Bno$4tTc=!(E!=gY_ zrroqDyOh*}F8&;KU^Ji~!V>5)`KyX2P^+n#V}c5&G9}b-qx9yb$-nZ>%bvWaDm?@R zF%8M}y=X0s6)gkH)F=No!H?e1GPO^Y{5}ZakpQy_HX(QkPpWEVwwy?pn?Sq~yRaQ*CgI{<{GLe^6D5xq>ABJ)HccB0 z3hMW4Z9{I4^`pCnD?bP1lnRE<@KHQl^#^}f2h9I8cJcL5w)iZ`Saxe z9#Tmt?#W0Xgr*vIRGS7u?1KPA;LtaYu2!PJ=V0<> zRx%LSTZKt;e4;fOR@Tb3xUPAqT;kQC5Y>9L`7AH0&&^!@T@CN`2T>mI=Q_X1dv?1d z>4{sBxH?`aH!?}s)nveLkn?GeZ~sWNHW@PljXuh%wxcriYpIB`_JUnib{;D4SCYyp zg|KX~djhzcKO;cm>?fJMe& z!b(+9t){IiH-mU$7%X7d2tKPU#yjg}CI-gGuQ|bU7s|1|57TDW2|MxbwvDcq(Lr|L z>U7bS2a*my*LqQ{S%LY-DVrYO!iH$JkykGc^Mu*6U{N#z8<-G~B}h=2BX^)3J9VbVK%h{2&MZ{`fv%re2U6M%ld^Q@4v_jysDIIKq&T^=MhdQ6YC` z!UI!&8LEx3Cps^WkdXXm(M_TRws0HyQ}kBNQXn?uyssUWfoS_w*oCqAchF@eT#^LpXB8?^w{n*^pvVTSKSu<7lH<~zS^j{qe|t6>^-)Cti_-) z1v^PUNS+^(E~l=ATv)&Olp;U;$}zRrQ-ONtn8AfbFTtDho4DnMZ7hM$tlMh z%aJsDBsM*b9igrOBXRlT%QZy51~X3{@?Xf!q5$JP32qpfqcL918hLQy zc_BS?#aC@wv8i&?L?|0PGnMt01gcW*<;oTGr@$FQcFV1y37YwKj}Wz5i)HI#CA zR8ZvbSpAmX(!m4m%8H+7P!P@WMA1r`DGv`PF!}+#a5H}ka>78tmJ_+^oZ)o=NzesAT&xN`zwctc3G}(w|6tfzAJte_l1S@Ra2+jg3rKoQ>nZ?g#A5? z6A4)qXe}hE>MZuagw*Jkx)E7@$k(Cc_K+(Sjpgyu(H-d(V{mFMRQuGPBkLDu z3s00r13Df z#H(S!HdqJpq_H3D4Kej)y@7&hQQU#jV7eEPuk=y#({B|bx{>w#H`xt5m!SLWv2N*o zPdp%^FMJsp6cDU;wI)EH6>u3Bq_?6VXniq>viar&w8#PSk1NQ4JfelUJ6FSZCr^>Y zfF#NKKm7%E1=+n>BxHLZvD7)PMdJ@FR#cc@OvtBKeG6gr7z0HZqxyw= z(=_Q`KB z9wN7#kM7fYXBXI}pv5zJXIX%YH$VDIRNW>_0R1a5xqOf4#SLgeE4m`g5EE@3uWNjX zeNexC1ed#IH96&s9pmtlp&Zg^pg3G5(x~^?=0Ek&P|xDT+aI<06py8p62GIqa9_pA ziRm{vo7XFA`#mil6~}@x%Ed;^juXHY0JtuwwK1kk#hzL#z|-9eys(REwCwARJeOd+ zVGFZyyJao;Z&kh#w zP3qr(2Z4-WxQ(t4zT3mwA(A=4LwfqvbhI#R-*Zw+)p0M1s3TcpNSws03R^kt~Bbd7(dEdd>cq1?Z16H5n`B3P55Yp!VV*#{s#>k&j42U=(rJ!~BKbo#GsH(1Q??ZQ&G)PHz zcL-7v(kO=xk?uUCbV#E}cXxLqEg%ij-AKc?pLgb);TJLNv+7>=ReOO&Pih%TPhxY0 z&Y?3%6=*>egl~>mg;Npz9g=m~UDW(>m05E{v0}c+B2K}G-qji39+VzubsjXQ*eh+^ z5m#QGdC_M&TOCJ{vjA1{2>YJrT3xct?nAD~ADBlH1&dcqPtNs+Q5g0a!UWZ(0IT8V zl>AWtQ!^i-MfM&Pfn%2(^|03;O7(ZOel_h!rMj^cr6RCt)esrk&F!TKFMH(CobO1< z0^b#oNV0?sYPCVJ z?2p1NkO|1FsMfIzoUYGio^Q#62&v5?uwk|`%LIUy5Nj)pG*B4`nnLG&MU<$x2o`1= z?(Y=;pnO3bJLCVxJzia%l+UQ^=`L)5Lq3{`2!G z9T6b%%?JXNTG0+FX?=3dC6IXQYg6{fT@e(7Cf&pFKZQXT6A#YsAz8jg3)Nh9pNJb= zdLsTZE2|ZDgGT%*@^9NP1=PKlRV^W=R?cFNzC3@eo*D$jH%$4#HX{wObQpG=^^%SX z>ru_Gzk^aW`=Ie*%NS`2STQ2dH9xSK;Huj3Bftsi$_;>Rc*s>aa~1R**5*c;s?Qzl zM!w_p2;Lof*>aWrL@8~Z)%ELV!twV$STu(g1y7; z`GH9Gu9^tGhI38Lw(;Tj3;6y|G48YIg`}WkPr$}~s7lGQhaPjmaXc&Rl4CN}PNnSKpJt-#ctV{Kw82wZ_#?QTR zaV!)V^slN*wAD^9NS?G3VJ+BM`3=eZbybx*zWWB2S3PW_0AdrS69B)$4BQ@{|gvs^7ow0w1X#2MaRc$P>7I)EL1>qs@F%=R#fgUS5 zO11y&(*0&~4q0@6f@OV;jh$i6RsULG9NJoEYz|;*MV>m28)6vZW%Q%yetC_ zur&c&h0_$>%gCy%vg4F*4{%N8zSC0-pE6REP79jy?dZva2xN~+ zeWci9?Gev%<@FrkuK81F(xm_WZmasf@zYhxLQVcpfu`;@98z(8TM!EMG+!8>B}Qsb zYhUd)5)O;cf{){4X1}E6|Aw`Gd$$o>ZWX~RfAijT*?GkD-w>%Bh|xu`nJMXyQ7jxV zsJSK5B;BhV>WVT!BsB>{-QN}se&Z^Ara?k8$5Nb~((orOrzWW2PQP+ArhF&wt-OL# z+yd@fFKVuJLl4XCn*3eN1bhtIRybWJ(=R(!9a)F*oD4gh0(nkeRO5U&m~iKAwIQHaaIr@za@R`Tng~0a@BPgszmk`$IOcbaG&xuiVmW#@Ru=-9v{vqgZojO`vsf9?vv&DHXQ()oWPb!tIU!R zv3X;G86MyTa0*8Nt|9MU6PupJKN^Tp30yilbA1N2z@RBL0*^_VbO6f}G_zv0AGvzr z=hN+tVE|A-GQw{ICpWTq%Y1xOIAaI@&V<(JDiu?opa+~U(K+%JiP;B<0hcE`wvu%Y z`~~O&gBDyhyqg@HAd&eodJSm0IM_|2-ecvl_>g_8(}SP$0;)RNLeNiCp*S)G`Prxd zJp|(FsuMg%fi*2$EW<{ z(Y7ow4+X5P77ifvSuc(gI^kNBhmyEFcBevWmHpMqPRP&hWl?fMV`VC31ay~nyLDb^ z^s`^fY9HXR*Z?2OhE2!8(P(@fFKox9A1QmE)0c!9kh`wjhLY=l1}8azZ~TDCO_S(k zQGf&tVWX;k;zHGy$Q0F%Wf$CJ13BgyKvOi4I}lIu7s4?u(=ZEUX=4Lq9pPZ6kU|!s zG$^Jb%wyIHQ3@64TKGo#+`e`&LL#cRgJ_C03=4;gZsh+@W8SsBG0!AO9 zw(uPxTsfM4BgWUx!{$4g(9PXQW-rM(nhHEb+7cem(S`eXghT*eHv|`kr=0(4A?wpl z6(>3oK;-v!;Q38HvqK6!;0PZTlhdtVbMF}yu#PXQJvn^*Jg~%FgE09gXe_Slkjk?^ zvYRQIoI0L9Y8s_FOL%?rE&D*KvZxb`gmc+5w?9*}F>o(Qbf@fhK({vcqT*Y5E)kn| z8TIc=jcSRC-{Zt$8O)~SO)Tn5eBm+gDRKWRA+ zg?T%1tjNi1oB&J`R<86#pDwySjU(!i{W(eKJfmy!*J{@wKIj_%AzJ%m(1$KQ<&KGb zUN&0ClanUI;tgW|IWq!IGsIlg0bX%f5kQ*_`|SBv4s`~C(asvTY@sl(6PVmqHiHRX zh%)@u0!k5?cA=fmRbH_jKwdFv{N<3?yby_ zdlW*rbuAhQAZde0192f@Vj%35Dj;&;W0<5Q6QNu7j#h*<1cu>pdVs4dq78>r_m>4@ z>~JwRqalwZg+Rfz;GZi!gZr{ED-OL!p)x7fbFb*P63r?xa(aN|SKWRzW@S?8Oj7Fy zkDTk*s2PZ8uc1~ua>C?ku-L~pWGYCmCZp)Db`;=yAl5DlS5tK|hAGRMsG%Xi%3=mF&kjO`7Jh3+X`lAx9_Lmw4InDRTw*w?|K?G@hFA5UFKW?6e?bBV|H>dZ|aw=+c{>`ec6k!IP2?i zYlmD;21z8Hk`1&6p_v^WoHMob=@Vm zrrz$_3U|7(Q2Pr@1vD67W?iW+*{onKswP+bhwX`afDnzKRuc%`^7K!46WJwsJIHs% ztao8yfSEy94K4iZ9o+lrDKV{KYIC?L)LIfg$qT$Kv8k+_YIacg%6+vw@${bt--HfG zyPPWFHYVbo>tQCDWC%1 z7VDL|1N1m$|DkS;3LPtL4aYsDjCkx*FfK8rf0SPoX^YIVB3k zK?7exgquBX9|?lPLJz0VUZ9kR4E3_ju$W=J#{M~srp4y!8#pfF$;K4eBg`xaj8JAFq!;Mp45H=x2dZl8Pjk`|&_5sD}- zbtjDvS$0~VsOFX&S0Jpva>a5|8rbLBaaj)&FOqr~9=wX zm}6WdT{vWEpW zr)pKW)H&Zd$nIrL+v9Lrgk=kxfe)cr3XU9dokRC2q`wzY)e`Ws;GOfFmVPJYFxrne z0q{#G4AI9NDZCiWf_)|MX9_9^Ux?qyyj#R~P&frc3F6zum4)zY!lxc(n_kZ2e67bA z$0m83Zkg91{0i_U#Nk$IrT;-8#uu{9C(*MIy~CWiBB+8GwpwE3fYmTbQJDWCM2!{} z0Sdwb(gneMxtJjPqzY|<%yztFJbVQ>5~VA(vxGgyDfpUO22_NWI#qgsLEf!+Q$-&A zGf8#f={21%7))abK0g5qoT7Xv3 zlY@OeQv}M>r5pfw9LiVBsn}}KOMY_QQi6D$b>boavqzbdIj`&rDWX@-vW5j_J`(;i zNCZ?|LXHZH$XjIvUz?!MQOBsnHkAm&!Yq0~B?kaqA{eN}okWT}vbl8m&;0(Cx9A-q z4%S}gqsd4$j7L`qM0`!Zwjq6fHR9?TLi;b<6kpqz;6f&jDo_}I?>GW`dOK_~`-P5= z@j^xm%xrjUdR4j<9YeeM%TxzY7C-{J2(UHV9JBzQitdg$P;xrsWFT!%jD)I4yzlbH zGHXAmc;JTN?aMlwGO5$%TX`iUKmnQj4vnpm-tZR>5o2Nd!#Q_lPafDhnSKXvcgz-; zZ^0n7Gb5O=-!g)lWmQtujpU33)P(@j)j=Jp$q2$#M_Qcoinm=xv~yr@2TJ0}coM`B z9$oB4qfTWy?}k`bdDd8u$jq9;)E zCq*e0RMS5;?T=nwe|(|$yd=H^g>D#abG5*(cRT6P+`K%-zkZhXj= zq8f>jYGhze5^#)aH3Q$w<8Yp;wj3&tJ>0@oPs!wxHb$d=6>;!a4Lfcx?gJ_gVxWZ> zk<^Ei73C1>i`pbOquZZ#)70{1U=W)0rp{}$zhIJNu_VbvXiuOy!ZGON0v+)-d|!h@ zN%B<*^hhVy6Ltn>H?wny$LGgT&RZ;I2#WGdhYY4{KU^S`I01A|>|pUx|4Z!X9X9AM z$A%zAD!QdWmJUx+nlz@fZnyb)f5XTO3)AGyg4M2+EIb}ZwTmp<%&>a-6TE5e432>RE8bnj)uuROOXAADGH4!a`mHUHo+4BYyV68fDGg@#Et(4fv_g= zdxG03uU2x?8$VotHF++_B{h47NcuIJBGe(Mxq7}%SRHvAbTKbC;WpI_tIKvI6@RQZ z)Unmiu+oiZvWNm5viiG%hB;b=nI=A-LFIk77jL~$*>Z$4*0D*x&__xL{IiUm`c%U) zv~k3+h+yJ#XEF!2`QRK|3DU=kfR5_wTj!ITyfaK=FFTZ{B=iKN+*~G`i5r*vs%uY| z%u3FR+FOrH=~P9YeKB#{a`J7o#fQCc_5Z|LT4D|V274Gg@XuWG9o~`vQ4|+`dq(4` zSA2E)(LUBCB|>>?4TvMJI5`NcU}rhjY8%(#0r%j2e9M6U$L~;HYGrDxgr4+Iw+IWn zVMrT2qXQ0va0)9xJR<;vcg`Q9d;T!Z-@*tJl(d3!5`DKrF8Y~@J!B50@-ETz8!o;O^`ad%DUE^>rk(0^T$60a+`PbKUc zIED%)7D+3Kc`1Zo^{ISOPYbyx0bv~?HPzM>hcD0(XLzF>be&>BZsZ(j$ty?<&p`!Y| z_So8TJ~?ecOo;JmXxL@ZCEYGLPr1h2x#pwBx9M+_7hDkJ&8&GOTUN7$ZubDqul5=2A#ckogXWEhsGV6-4U&x4brXz@sY4O@=w@6axQ3#2 zO6Frb*Z`nJEVf-1r#-6eQi>d*6AY`LB@SS6`}P!ISKut=iUgrbU~?hjlG~D8rD* zaBnz`n19R}V-(Vkbgk~B&XC@7jV)xf>7*d7VgH4ey`QULM-b{yUH7tk-k{Q7uW{Lx z6u7Z2x;s#%AW5Q}VVhAyd;)&VZDOA=fx&c)ZyH|H4Bek4HM2q?n}2SaN5=w(IktPZ zUB1#{*oqG~iYfZVlj2H-zq6PrBV^Be7J8eSFjSkWer!0j z?8xGA?5Vx4Rd?~5mKU3T6FX+(eiF^Fmbm(yJ($2xQ%m?2!q-htr_gU5++E?V{1-j$ zeHtg%Y=_7<@@jBWC0?X#H#`#3Ma&+T3iEqXIWq__^0_f4Q{s97)TJSgkzj<4O2S^a zL=2B0T}}cM7?w;BVFXudGPM8}YiyX|{q{kpwJQ>k{-=OBq`@X?xSd)EY0)n7r(anh z1YpCk>H=Q8a&y-gnzNyyG5UR={216Z4Ij6k<|2?P27d3*m&~}bkjAt1UV13Z>hMrw~vtx7Z z)34y~gex@gp)NX7hUVnwbWKpG*0&@euWNWCJt#zlHM_~^ZX(_swzbgX%y7J zIi88z;Cq8B`{twEi4(hZ5x;v4P7Kb6Mu%zd-a10I)2=J56xl@Wk#+K)s@Y7|jAHI7LzbBcDqH;SQ*&1-ky-84GqFFMo+AO| zXd*&M#vDZekopQCe)n{iS=)95FSTFx0=bJ~V_H4IuZ0vHNG=OuL;Y1-O^!6*@j)!% zhfO5YcFOi7iem5P(btcAs%Xho3eWt!oL$zuTWa0iFEOn>3d~%$xvDSY??$jTxydF9 zJYEvEtqZd)eO8Pxa>aYBA%>X=sHpDPNbir11N2p-ZdTr4VDm!jo7nA80=swpxx8%O z!?2yWc@1qdEBo}bUP-3Ww6!Ig5Vpa6XodHR2%|(l}=YJ+PQesq<{ypFQ@(|Rl z9wtFS{ddlJRY_3>HN>vdu27B)Y5wG`xnK`)fk=aaVgGE64LB?X)f;!2NRlB@AF`XtX~xqX}5*w2kvoI~;yMAGM)s8Oc<-%MdqurZElg`gbE z!vzA%K>#SZ-<@hV^GvItf>@*cXly1xhTma-5{^IHH(1VmXx%#h+WzA!IE`n6nfszb zNpIi*pv6nc#sL3wI$Sh6QnQoBc}Su+-Yh_U6L`=MX!}?r*>^yxUku3ccIIWW#a>i> zbU)N@hp{(LMhZWiBMOceL_@ZU=-$V^eRLp6V!GMO>g}b>$Tzysuz`~#L$yM)d%9|6 z^(FgRaE&?8iI({LYjs$_N~V62#XGbfq+;Fn*NINDsTSS5>Ouxa+^M#56?C#8GLJo8 z*=^}V*@!HFEpFBi2V%mDa$PHyfvnUWfu^dvA0ys&ke7?P`XXgM<_8OCIb(7_U=%EyKY6L{EU`e*$N$FgC;IkRuC-Q0}=*-VYk(VbH~F1a#d zQORq`qO>{nEj1kIdvDb_EP~Gv89u*pU9hVmhG4iXTqGmet74$1MzZu}Rk#0uAu-vH zaq}j-^KG#=p_#(@hQwaF^U&>lp7Yh~lt^YfXp^Y6>)VbAB6kTD2FRyNVR7eCyAbQ7M&kjewWxB$8T-+JHFt%`9T3cU#Y-S z8-GaBD@z^yv#n~r8eg3}&o5~H=!!^f9kU>&N@6*378U@W;3B7eHIm}yKku9^0M3QT z>Aa9<;Yf%N+6=4mP1>TL(17(F9F|x+wr!1u1i%YDQ#E|Ni_EX|83}l!<8B0JFOChn z>j4gIxmcm{it60VH5G@*fDJ2ub$K)-k^e|#1DZA88EtU7WETl1`B-{7AY$ z6I+27R~AwbXhU2Q8TF>5dL=#|lOLss(e<;r4?b&VsR;2S+W%_-;IDZ?;)wh5zY#=Y zT%@M{!V2*ERkFCn==AY#y3Qz2lSpL#_ct;BP${xaW8|;2nx}yuSG8~%vD(k!-6Y$d zVYu<|z&TZ|W0i>>QptnZtgy3G0*l-i$xmz~>l&H&y+B5FccbmGU_~(ofQiABj8ZhK zM~Sa_;XT*(_Yx64b-#NKpSE`Q;=Q5mnyAHZD`jsY`PT*`ik#QM&coy}Wd?qTr1OKa zj2fg))0HF$e-L2$j-e-Qq}D%Zbw;B5VB|>tFWE^;MqZ6}023($3!wgd^7Rf(UXRa8 zUMmVm0JeKx&7}Jk>>J-n`PrHUWMu3mm@uqNY+n!#k8L9&GmqIQNRSF7`l&vdN=fF5 z=j!GdUphtjNJSK^BbiEK1%b?sD2uQ=U}dJTYl1K7TTr;O$J6qT&pEy6Z|L+c+5n^e z$xk5ErGtISQUbU1c}m&(&<}GTnWFacx+jYrW9W0&WgvtI>2edUxF0CH*CFsd-hjDf zOXf~$6O-t!!DmwaGAz%8z$oayUk4d8lIU%JOc*!nyfUrHe3n(2LiVj|jZnoc)n;Oj zYn7QH%Kl2|>&L~3EyY>*eAn$}!TH|6#+4E?BhQN50u$-qoAJe|-AAV%8RiBX^VKrV ztvSxoA*%a2QC?HT=^1-}WZ{tEF*VORppvfu*LVIOuc6H@b4SUVO0i5iIR$AI=-6eS z{hr#i#9xVWRG+qyvsx!$N}UKdUu}Ak@J;=hW36@5)T6^u!d!YyXOGn$Pf`R2ID#St zA6^29!fPR+@qPh7mjg;*3vM}E*m72na#?$(iyn((3uV@ls;wS8FG zQ~Ye97QU1RUa5m;QO;IE5NbRrA&jfkK*S!{-!X~fskn#qocH=h)ifW^YnRJnai zo}V*Cqa6vw&g^`d@|##N3#wNAO<^V#yEr^Z0S);Y4?CNMB@zKKJsU652%whep?2_G z`EF5DbJ=pN?o;h;mhTqgpAnn_#k}znUO7=K5v5?{i(f)#dr~3kd$|X=`p!PsKj_`%URpFtoQ*fd?L@R#ShG$T`zy60%_NA?+7};T>88aN zj|&FbDoxQ(f9ne6zgVuq8E3W8VIQPP7-GofgJ1hYSa zFr_Tbe~I=9tjNd1skMtWiIiZeOnoklm82I+;?&u4ay&A3B$dJfDsE&Qcd&*tb$nRI zdtf|>=mC&fv2Nj5pG0l%^ZPdPev@U^v#ht%OxQG6WXM7!P>ZEleiA__Q_B=u_&(8U zF;o*P0{xfn6r=C^pvR%xu5C7yeA()u9HscaRtxtylhN?a%aho!Iae6|6=?zV6K57V z0>_k0S6qsH{mnuajW$cG(|~adR4s~J^2ScN-(A65f14sBB4|-?2YnWSC_*LIN!LPp zZn9b%ygNOzk|PmB!}LGLEz0!q8GpHn_*bq~0Hxu~T14yCM%p9^4vQn6PF zzvb*!)9g!<-!z#4Gyy@7NXQ~XXt;hJp0u?147Ers!uGqL>9MJNh)w*uCs<=x@S)g^ z3)V){a1?xXqa4zLzdfH6k}^%#^pWw@bZC5&(hFtzrG8FjMyjNmx@R?9X>vz+F^PWP zAoU~epPMmL{0^O0#=VoaH6f57C1Ww2kDfIWQ~IEHPEGQod9Cr>KX{m6l;XdCQmpZV zotKjUms|eB&?YiQ$kZvvOLJl1EyfI|B!r7IGMFz;@=^MSdtX1QE8(J~{2yw@`z-M` zrSA>_Z|K~QqBRRN#!rS0{9>LGqA|~YPX(*&>4IL*RNS1EMONbZ@z>om1`g+UlzjUs zzu;T>lD_5fajB63EWso5ZTx?hF$a)xa@a+gcFJ_=0LiNmX8)9rS;3yd{o$5@5cWu& zWm10kAmAYJTbX&BK=2{eNBRBT9Y0vwrz}G2 zx84cz`Rxp}iy;P9JY~}*IYkY_a?*;vD$Tj7^$B%}W8%igbKszs%>Scc8SCTBz#Iqtgb0hbkk9;1j77OU8 zMDmS2uo+p8Fi#z@UZu9F3YnJHa>}29X7TV(V#DiUWR1>b&By9RY5g0<0~oLjzWCv) zcT?ID784`!PDTOH#%0BX=;w?Cr(K&R&F;5fSv==UEsdDFVf|gRR0e;149dbGTEL_` zdN4vpS1LN?kVuZ8^_D>rW8-EsbpedvybH@y;}yxnu+_UNQdhLkXmKotj!{bGi3OcS zWksI}R~*&sXOir{^}E3zi>u5z{(1d$E9Qpj=)DF=!#pwVj?+) zwZ{eb_MTmf$$0;zPc-dLfsU7UPoae>~5N-vsP>h z8q!1eK0WeBM3XN@47|FD$0tCAGbkdZ@h)vrnbT;{>UlfDwzz8pKJx$?yt28i@Q}_*I|uV=_f?QE>YH| zJ<%_H%{%N2ovV~3dsu05#xojblhw%6<=soEB{QYfWy$`{YGsDO1av8)ao-rfhcWAbAp)d}mpbr>;w++rZFI*_U z>T>vG)#hBn7?*Uiw$`f$sQNqiNWPOEL03AT`o4rCwDZj?cKZlU)Z0yz*tV7JV2!Z8{tc@_zWGlJc^WP=0g}S->rdz98-uuvtbI{mE|oH{>7^^R z^#IRzk(XG4B9j+tyw0SGv~{8%s^Nz^OTQpQImgeC0jNZ%7__(Wu$wFuVy9;AMY5yaz*G=*d2a5aW({yijCJtfI*Q}_br%TK2>y)O`V|#P$5AX1*ImS?3*Yq`#4inw|0znf5 zCdTo4^676UF{=`y5fI z;V%n9w>3d?HybMOfYSS&MtAp5&&`G48opstmN$GGxxT`^+9D(jf}_X9S%r)T&-kc3 zkRt=>q|S>?^@n33o>;TA0AG0uw#@s`q$MiHbUxdBn9UVU9cY$c9= zMr%B-0)}zZV+fo!*YBixeP?oaP^#ZTjUicoImQsNQG!ZMo=f6R?QsUN8;Jb3*Pb8y ze@Wc*tu@3xP0iF)483q!o zSX$CewEMFh@5@)o`y$ITbNO43-!mfR=h<_vR2dD|Nvtfv1MI~#21Bapf1bAq#}lqj zBS^^%f4|GFR;qC$V1K+BkBOB(Zj?EX_&m$Vy~?&p;#J;OYQ}_G*zq~DhFDGaE1sTb zi5fgS4cA)hVxstg<4JVK^*+qrI}sg8S3WEn!Hyi5Iph3H*H9dk&qsUF***Syv=*}`3;Y85sYTc8YRWn<3JTGwN%V)I&6u+#bwOR>0 zC^d-CMFj@iF-4(TV&4|5T7SHm+OJ>?-0f(+pEcq;^7S-}j`KK)jG`e6?Qz#_RsUHi77^yt;v1*@~q=@L zE1v?p7vMWuk=GDm_}<=Io#RF}T@807hxbrJw#HoN7~IPM!zt+Z!O(qbpvt8i9050% zq*mxjtEMEz(1ZyBOqFEjsF94)Y7g%8Yi9Ihj>CpSJ}I3!f3F6B&S**>?i*b zkLD|qs&1J5p!2E8C4(Z_p;j#(N8QdmbO)Y@-9}BTdJK6>4()rrs+0B4TbxbU2)y4;I{M0=#uElcA5DN>*s@hfSiUvORLNjq4FF-AT z$aK%NMz1OA+WkO>LUk2JAfwNiPI`7GhZisuO8+CuYlK$_n7u9dRNEL+znG<;O_iqxDFTTxUzpgYo zJPg=omVkkjh7ccJ@k6wp$dyHg$|DhT)51yGr$?2lfNAek%|L|Nzi14Yg0(lYsuC++p zdoZdtfj@7ENF=W&rZ;7v1Ce5ll;chL_Al;z%VhZAJ&4QXl~)Ik>%z%uDs+3*imkCe zd$};Q94tYI?j=~B0|()sc0WcLl)ht*^f09p|IgG1F+b5g|dT!HC)$>*Z+ zZcB^iWoJ7yd{YHp2hEUgfJmO?WU%cWtZ9nLV~&}uk1F_W zcxc)iZQS|SB?yj_0!K;JGDrW!ny=Ix!997_d_J5oc7mNxR_0*qp0(?}$>_g0`cIbi z9pF$PDX#rb)i5#Qx1jJ0u-=%a^Yj(fRQ_@PCt#+e#%*{f;peo5FXLvz;&4N+&(AO^ z7>1v`6}EE3M2!b7oS!O=mO6`#Vm39&qyKUiKG}Zye(@zXog(Cay!N+|L6)pN_hr5S zwrBp#;2a5%Xsl(kKYi0Ak@$9sqi-GC2t{`^r@avR5gAOf>1P3nuu=+HxE>g1T&8W9 zk%I?6g9DK=F0h5oRPy2p;^`XI-uc^;BQGrUYD9b${wmBfN9i;`;u39)zE_7Zdvx0I zvx`$+aWhNwC1uC4`sSOL{bCGs0H1ZCS8l)szcqtMQ3R|%J}0N&{(Kt4qQIy!W(sZN z?PadpsS=#*5b5Fi`0YV;x$(+ixr5EESL7nrQd-+gwwD2g6${E8aX|x~T_+ywVUNSS zIp!;rI^Xx+Q~qOiLW*QGoWC;u2ZCfjRnE_ISAFWp(V0(7KU8S3MO`_^pL@Tv{w;GB znW9xWaXov;im7hXN&-)p6ZucXk?NJcjwg9rFeEFP&fI=a|LfWBLGqiG{S{9-50F_E z{*n*xQwG&Uvf0?U3t5reBN&1aiMvJGzaA3ib=?bHn;0altYo_9%M=VnQ;i!F zZ#<33x@ft|u`e!=veaSi{T`VXz+<#;t&bfZH?gCm<}p7Rx)sn*vOXEgi%`@bz7De) zxG=Jm^FR3UtM*ODiBj$dQd2pq!|q$#NkkJNl5ng}i|?fES?FyD@I3%fcf|mv7)`Rx zB|WCescSFEZpKSct6qH`Y5&O>^UA>#9fQJf|Agf?zZZZ1S1h+2zWKUJZu(je?|>a* zczUWsfJ5LMmvgD^bc=+@#-bYLVCw*3+^QOe%@ zCmd3iFZ+?yLqJDQz~%4u?XC3xzkX0Fhy>!w|DwnZy!Enus!}&bE#rx?)40G zAlT-@agi!u`uz<><8n3FgQ;&vGJYU_I|>I+qJ**n>~_s)oCHm-3Kd#N?6Tiq-m(a1 z3bqLA4wiY^Q38AF_T=>gX~h_GQD6z+Uo-gE7SDJklz5=Y>h8yY0B(d=<*~AMdM;17PpWypOBUWQc8CQ2L~YpTFSqyDq9lL) zW9IVRf}0Pw$w)DseX>*5eFlrzbf#pGY`+Gw9 z9Z6$-?kFdJ+xO5VO~*swGcmLd`Jf~O9|&ka#buOUaL2y2YnJfYm!m=xCWZXDr#n${ zxp{+6{c9q9qG#brENdLK0w2im>dE3=c!9~aJPR$i|0Vp1hM@QKgHL+EgME=+5Q?H^L1x| zq^3p`2N!q8KO26RGJK02W>EbSckuI*vN=@s=68pzty+GGcDt~LTiZgjdR%Ch_2=m0 zoNHl;NW*&wbT7?mGN_j5936`^#rcN*J`)4@K)9Za2k4snuVwT!%Q$=oo$|N6pA7qh zC>pADsPRXzC<58mP?MFx*F>big{$>Wbc9aD=2`AsgXn6jm(oCt;6Jsv)0jtz<-?DM z2_SfB(ihl56)*XHXu5EH>|;h+T$Qy^|L)YO>m&

7VF=fanFQRTaAm$kR~R3tqg zCkk%C^?la7{$Ae;|3byGAfNW8i!;mia^CMwWWU2#dg)nx`L6%p$!~3$<;=_4%6Cjx zUX=W(uVOR{{viw{beGZ6hk&{$F2jZjmO>Sdm%hMh{|EO3OYC{Sa{;YOdB*4|`xnF0 z$wreM#h(MSf_I#@-ppao6K$+1pX#P32D(4C=Q|UZbV>Fqd!)|^bnVl_0n;}xr-(eN z^_v3=trN(MKOL~Kc`>j7QCz62vu#&+PVCXDdqqudd#ONc;~dT7plNPpIeHcRwL7+n z&k?scSSL4}^0@i9jJRq(K78U#L5PMw zB&Jm+TGO00Sv}Vn`RXM16%h}hiwez*6W#LNnw!%Qy^O_AyCdbGCHlnf+;Vq*HYCxT zJ^_bi%xuS;If%q9gtP_3S-Ljozj0Q7QIft_|1c*UJVCIs=f5#+vN#v9b|i}Dvqg!` zLh~oDC#OJ8z4c*vBKCcrKR++u;8tIO^W@knJsB|l@iZm zJ_;H~9R26A9(eZx?ZKsQ$sP z{!UivLy=MA`uEf0s9JdcIB{NtqT9M{ZSb4THt_csz*yn{)Z{u72fIh z$U-90gZd$JKUz7AuPuhrg}u9wIR|{Zuz3NS4M>P7Q~VQHKqoEXP{#Dth+Mh}2q9%l zn|})fR^o<*Bn0xWa%1KMTOLQeixH^LoNH{AY zBoRU~DLR&>2j*F;Pap$}|5A8nX5b@3IMY9CeL(mH=qk#WgRgqeoM)MA`%3j#{_9S! zWx8|BR4Ee<)X8K_AQkoXegF4n(|-ZcOX3*Tz>$~z+&oiAd-{wlfB#1~n2mC&&zNOI zj4v+?7yZHy9{7bd<~MDQUR4AyB6rP^^2~GdAYL^cD^OC}NJk|>Dsccd9dj@7{{PH@ zwuz~w<@^^Ae`VtcwPbs`hyNz$P1#&O+o2O+7!-PYLWxoGbixfJ=bfA$uua+JH19p< zv)?DaWJmwAB~-Q=Ggdh3&B$}^KfGgKHA5w+80mBY+WYt&!8g9;J(qrwho>pB??|LxA$5q*{{ud z7b{=^dCISFGCfzyqYgeSSmpl$#{xM0`zj_;{;8KO!22)#8v8!vir?4~KXbnioqhnQ zDg8IA=vVGi5gI$e=9gM}6U`e$j{eQvmlz&!XrMCUS>qo#81`2sO z(f^=HKLC9HXH)5y+|d_6Xw8WcOEZW#6-es8=kf5*zSfBA_Y^<-G60r_=%-Hs?&1|s zxF(+P&cf}v$wVLT>gAs_B|;6QH#>0zkCqft2Soa%z{#oTvISU@4i7-t=Xq^iV8?So z+CAg1r08A|q{k;{}6UJV20zmfzW5efGXQDKb;S^RrsxN=SX%KZn-~f=>m6O0vke;hrIw%?YprIfJGtq=~Hm+u(kOLSi0a( zULRc+eVqrn3ve&!nQsPmJQpm)%K4uy-w9rPz6bpujVlj!!+$EGrnm16W{1JjRo=qg(wkWGZSiv(Jnto?;}hv+7wASvJj`$w zaX%!X2Y|Ny?*c&j6o|uc{Nccs?*uJfWx|z-C%ns|U)K}a=CPeUP_7S(!(izG`&Jlw za3uirJRZiO(3*4G%K)e$@!B(4H6@;e9_1csyh!d*<)>c)MbTFr;9#HV2Tks|hk}c* zyFjpRkBe8pzO@_&x9^UaeUN&K=fPbDfR?P+96nT(bv*ao$?=qwXgm*v9towJH;8h% zhw8lkvgqqSq%YE^plJsHz}f%O2>@%q9>4lyYxvwMfU%l4>i+{(f7p(!w~br?0000< KMNUMnLSTZin>hpk literal 0 HcmV?d00001 diff --git a/demos/features/assets/tilemap/tilemap.yaml b/demos/features/assets/tilemap/tilemap.yaml new file mode 100644 index 0000000000..95d9f2b983 --- /dev/null +++ b/demos/features/assets/tilemap/tilemap.yaml @@ -0,0 +1,27 @@ +atlas: ./tileset.atlas.yaml +map_size: [9, 9] +tiles: + - pos: [0, 0] + idx: 0 + - pos: [1, 0] + idx: 1 + - pos: [2, 0] + idx: 1 + - pos: [3, 0] + idx: 1 + - pos: [4, 0] + idx: 1 + - pos: [5, 0] + idx: 1 + - pos: [6, 0] + idx: 1 + - pos: [7, 0] + idx: 1 + - pos: [8, 0] + idx: 2 + - pos: [4, 2] + idx: 56 + - pos: [5, 2] + idx: 58 + - pos: [6, 2] + idx: 60 diff --git a/demos/features/assets/tilemap/tileset.atlas.yaml b/demos/features/assets/tilemap/tileset.atlas.yaml new file mode 100644 index 0000000000..2eab7fa754 --- /dev/null +++ b/demos/features/assets/tilemap/tileset.atlas.yaml @@ -0,0 +1,4 @@ +image: ./tileset.png +tile_size: [32, 32] +columns: 7 +rows: 9 diff --git a/demos/features/assets/tilemap/tileset.png b/demos/features/assets/tilemap/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..42be6234e1e25472631c4247797a1af2bb18cabe GIT binary patch literal 17653 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 literal 0 HcmV?d00001 diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs new file mode 100644 index 0000000000..8fc7240704 --- /dev/null +++ b/demos/features/src/main.rs @@ -0,0 +1,355 @@ +#![allow(clippy::too_many_arguments)] + +use bones_bevy_renderer::{ + bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + BonesBevyRenderer, +}; +use bones_framework::prelude::*; + +/// Create our root asset type. +/// +/// The path to our root asset file is specified in `assets/pack.yaml`. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +// Allow asset to be loaded from "game.yaml" assets. +#[type_data(metadata_asset("game"))] +struct GameMeta { + /// The image for the sprite demo + sprite_demo: Handle, + /// Character information that will be loaded from a separate asset file. + atlas_demo: Handle, + /// The tilemap demo metadata. + tilemap_demo: Handle, + /// Localization asset + localization: Handle, +} + +/// Atlas information. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +#[type_data(metadata_asset("atlas-demo"))] +struct AtlasDemoMeta { + /// The sprite atlas for the player. + pub atlas: Handle, + /// The frames-per-second of the animation. + pub fps: f32, + /// The frames of the animation. + /// + /// Note: We use an [`SVec`] here because it implements [`HasSchema`], allowing it to be loaded + /// in a metadata asset. + pub animation: SVec, +} + +/// Tilemap info. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +#[type_data(metadata_asset("tilemap"))] +struct TilemapDemoMeta { + /// The atlas that will be used for the tilemap. + pub atlas: Handle, + /// The size of the tile map in tiles. + pub map_size: UVec2, + /// The information about each tile in the tilemap. + pub tiles: SVec, +} + +/// Tile info. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +struct TileMeta { + /// The tile position. + pos: UVec2, + /// The index of the tile in the atlas. + idx: u32, +} + +fn main() { + // Create a bones bevy renderer from our bones game + BonesBevyRenderer::new(create_game()) + // Get a bevy app for running our game + .app() + // We can add our own bevy plugins now + .add_plugins((FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin::default())) + // And run the bevy app + .run() +} + +// Initialize the game. +pub fn create_game() -> Game { + // Create an empty game + let mut game = Game::new(); + + // Configure the asset server + game.asset_server() + // Register the default asset types + .register_default_assets() + // Register our custom asset types + .register_asset::() + .register_asset::() + .register_asset::(); + + // Create our menu session + game.sessions.create("menu").install_plugin(menu_plugin); + + game +} + +/// Menu plugin +pub fn menu_plugin(session: &mut Session) { + // Register our menu system + session + // Install the bones_framework default plugins for this session + .install_plugin(DefaultPlugin) + // And add our systems. + .add_system_to_stage(Update, menu_system) + .add_startup_system(menu_startup); +} + +/// Setup the main menu. +fn menu_startup( + mut egui_settings: ResMutInit, + mut clear_color: ResMutInit, +) { + // Set the clear color + **clear_color = Color::BLACK; + // Set the egui scale + egui_settings.scale = 2.0; +} + +/// Our main menu system. +fn menu_system( + egui_ctx: ResMut, + mut sessions: ResMut, + mut session_options: ResMut, + // Get the localization field from our `GameMeta` + localization: Localization, +) { + // Render the menu. + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&egui_ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add_space(20.0); + ui.heading(localization.get("title")); + ui.add_space(20.0); + + if ui.button(localization.get("sprite-demo")).clicked() { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("sprite_demo") + .install_plugin(sprite_demo_plugin); + } + + if ui.button(localization.get("atlas-demo")).clicked() { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("atlas_demo") + .install_plugin(atlas_demo_plugin); + } + + if ui.button(localization.get("tilemap-demo")).clicked() { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("tilemap_demo") + .install_plugin(tilemap_demo_plugin); + } + + if ui.button(localization.get("path2d-demo")).clicked() { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("path2d_demo") + .install_plugin(path2d_demo_plugin); + } + }); + }); +} + +/// Plugin for running the sprite demo. +fn sprite_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultPlugin) + .add_startup_system(sprite_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui); +} + +/// System that spawns the sprite demo. +fn sprite_demo_startup( + mut entities: ResMut, + mut sprites: CompMut, + mut transforms: CompMut, + mut cameras: CompMut, + meta: Root, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + let sprite_ent = entities.create(); + transforms.insert(sprite_ent, default()); + sprites.insert( + sprite_ent, + Sprite { + image: meta.sprite_demo, + ..default() + }, + ); +} + +/// Plugin for running the tilemap demo. +fn tilemap_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultPlugin) + .add_startup_system(tilemap_startup_system) + .add_system_to_stage(Update, back_to_menu_ui); +} + +/// System for starting up the tilemap demo. +fn tilemap_startup_system( + mut entities: ResMut, + mut transforms: CompMut, + mut tile_layers: CompMut, + mut cameras: CompMut, + mut tiles: CompMut, + meta: Root, + assets: Res, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + // Load our map and atlas info + let map_info = assets.get(meta.tilemap_demo); + let atlas = assets.get(map_info.atlas); + + // Create a new map layer + let mut layer = TileLayer::new(map_info.map_size, atlas.tile_size, map_info.atlas); + + // Load the layer up with the tiles from our metadata + for tile in &map_info.tiles { + let tile_ent = entities.create(); + tiles.insert( + tile_ent, + Tile { + idx: tile.idx, + ..default() + }, + ); + layer.set(tile.pos, Some(tile_ent)) + } + + // Spawn the layer + let layer_ent = entities.create(); + tile_layers.insert(layer_ent, layer); + transforms.insert(layer_ent, default()); +} + +/// Plugin for running the atlas demo. +fn atlas_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultPlugin) + .add_startup_system(atlas_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui); +} + +/// System to startup the atlas demo. +fn atlas_demo_startup( + mut entities: ResMut, + mut transforms: CompMut, + mut cameras: CompMut, + mut atlas_sprites: CompMut, + mut animated_sprites: CompMut, + mut clear_color: ResMutInit, + meta: Root, + assets: Res, +) { + // Set the clear color + **clear_color = Color::GRAY; + + // Spawn the camera + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + // Get the atlas metadata + let demo = assets.get(meta.atlas_demo); + + // Spawn the character sprite. + let sprite_ent = entities.create(); + transforms.insert(sprite_ent, default()); + atlas_sprites.insert( + sprite_ent, + AtlasSprite { + atlas: demo.atlas, + ..default() + }, + ); + animated_sprites.insert( + sprite_ent, + AnimatedSprite { + frames: demo.animation.iter().copied().collect(), + fps: demo.fps, + ..default() + }, + ); +} + +fn path2d_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultPlugin) + .add_startup_system(path2d_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui); +} + +fn path2d_demo_startup( + mut entities: ResMut, + mut transforms: CompMut, + mut cameras: CompMut, + mut path2ds: CompMut, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + let ent = entities.create(); + transforms.insert(ent, default()); + const SIZE: f32 = 40.; + path2ds.insert( + ent, + Path2d { + color: Color::RED, + points: vec![ + vec2(-SIZE, 0.), + vec2(0., SIZE), + vec2(SIZE, 0.), + vec2(-SIZE, 0.), + ], + thickness: 2.0, + ..default() + }, + ); +} + +/// Simple UI system that shows a button at the bottom of the screen to delete the current session +/// and go back to the main menu. +fn back_to_menu_ui( + egui_ctx: ResMut, + mut sessions: ResMut, + mut session_options: ResMut, + localization: Localization, +) { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&egui_ctx, |ui| { + ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + ui.add_space(20.0); + if ui.button(localization.get("back-to-menu")).clicked() { + session_options.delete = true; + sessions.create("menu").install_plugin(menu_plugin); + } + }); + }); +} diff --git a/demos/hello_world/Cargo.toml b/demos/hello_world/Cargo.toml new file mode 100644 index 0000000000..947524c651 --- /dev/null +++ b/demos/hello_world/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo_hello_world" +edition.workspace = true +version.workspace = true +license.workspace = true +publish = false + +[dependencies] +bones_framework = { path = "../../framework_crates/bones_framework" } +bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } diff --git a/demos/hello_world/src/main.rs b/demos/hello_world/src/main.rs new file mode 100644 index 0000000000..cfd5b327e0 --- /dev/null +++ b/demos/hello_world/src/main.rs @@ -0,0 +1,25 @@ +use bones_bevy_renderer::BonesBevyRenderer; +use bones_framework::prelude::*; + +fn main() { + // First create bones game. + let mut game = Game::new(); + + // Create a new session for the game menu. Each session is it's own bones world with it's own + // plugins, systems, and entities. + let menu_session = game.sessions.create("menu"); + menu_session + // Install the default bones_framework plugin for this session + .install_plugin(DefaultPlugin) + // Add our menu system to the update stage + .add_system_to_stage(Update, menu_system); + + BonesBevyRenderer::new(game).app().run(); +} + +/// System to render the home menu. +fn menu_system(egui_ctx: Res) { + egui::CentralPanel::default().show(&egui_ctx, |ui| { + ui.label("Hello World"); + }); +} diff --git a/deny.toml b/deny.toml index 99193c8d9a..10b74fa0cf 100644 --- a/deny.toml +++ b/deny.toml @@ -4,27 +4,28 @@ [licenses] unlicensed = "deny" allow = [ - "MIT", - "Apache-2.0", - "Unlicense", - "Zlib", - "Apache-2.0 WITH LLVM-exception", - "Unicode-DFS-2016", - "BSD-3-Clause", - "BSL-1.0", - "MIT-0", - "OFL-1.1", - "LicenseRef-UFL-1.0", - "CC0-1.0", - "ISC", - "BSD-2-Clause", - # TODO: Validate that it is not a problem for us to have MPL licensed dependencies. - "MPL-2.0", - "OpenSSL", + "MIT", + "Apache-2.0", + "Unlicense", + "Zlib", + "Apache-2.0 WITH LLVM-exception", + "Unicode-DFS-2016", + "BSD-3-Clause", + "BSL-1.0", + "MIT-0", + "OFL-1.1", + "LicenseRef-UFL-1.0", + "CC0-1.0", + "ISC", + "BSD-2-Clause", + "OpenSSL", ] default = "deny" [sources.allow-org] -github = [ - "fishfolk", -] +github = ["fishfolk"] + +[[licenses.clarify]] +name = "ring" +expression = "MIT AND ISC AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] diff --git a/crates/bones_asset/CHANGELOG.md b/framework_crates/bones_asset/CHANGELOG.md similarity index 100% rename from crates/bones_asset/CHANGELOG.md rename to framework_crates/bones_asset/CHANGELOG.md diff --git a/framework_crates/bones_asset/Cargo.toml b/framework_crates/bones_asset/Cargo.toml new file mode 100644 index 0000000000..aed4734490 --- /dev/null +++ b/framework_crates/bones_asset/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bones_asset" +description = "Asset interface for bones_lib." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[features] +default = [] + +[dependencies] +bones_utils = { version = "0.3", path = "../bones_utils", features = ["serde"] } +bones_schema = { version = "0.3", path = "../bones_schema" } + +serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10" +bs58 = "0.5" +anyhow = "1.0" +serde_yaml = "0.9" +serde_json = "1.0" +paste = "1.0" +ulid = { version = "1.0" } +semver = { version = "1.0.17", features = ["serde"] } +async-channel = "1.8.0" +once_cell = "1.18.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +notify = "6.0" + +[dev-dependencies] +bones_schema = { version = "0.3", path = "../bones_schema", features = ["glam"] } +glam = "0.24" diff --git a/framework_crates/bones_asset/examples/assets/pack.yaml b/framework_crates/bones_asset/examples/assets/pack.yaml new file mode 100644 index 0000000000..dffb60c3f4 --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/pack.yaml @@ -0,0 +1,2 @@ +# The Core asset pack YAML simply defines the path to root asset to load. +root: ./root.game.yaml diff --git a/framework_crates/bones_asset/examples/assets/players/jane/avatar.png b/framework_crates/bones_asset/examples/assets/players/jane/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..39b5937521b30770304ddb5cbe5b6bff045c976c GIT binary patch literal 38787 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 P00000NkvXXu0mjf%6L@{ literal 0 HcmV?d00001 diff --git a/framework_crates/bones_asset/examples/assets/players/jane/jane.atlas.yaml b/framework_crates/bones_asset/examples/assets/players/jane/jane.atlas.yaml new file mode 100644 index 0000000000..c521a1f61a --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/players/jane/jane.atlas.yaml @@ -0,0 +1,8 @@ +# Struct fields can be filled out by name, as we have seen before. +tile_size: + x: 25.5 + y: 30 + +# Or they can also be filled out as a list, which is particularly +# convenient for glam vectors. +grid_size: [2, 4] diff --git a/framework_crates/bones_asset/examples/assets/players/jane/jane.player.yaml b/framework_crates/bones_asset/examples/assets/players/jane/jane.player.yaml new file mode 100644 index 0000000000..997b470371 --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/players/jane/jane.player.yaml @@ -0,0 +1,20 @@ +# This is an example player asset. +# In Rust it is represented by the PlayerMeta struct. + +name: Jane +age: 20 +atlas: ./jane.atlas.yaml +avatar: ./avatar.png +stats: + speed: 70 + # Left out fields like `intelligence` from the `PlayerMetaStats` struct, + # will be set to their default value. In the case of PlayerMetaStats, + # we have set the default value to 20. + +animations: + run: + fps: 3.0 + frames: [1, 2, 3] + jump: + fps: 2.0 + frames: [3, 4, 5] diff --git a/framework_crates/bones_asset/examples/assets/players/john/avatar.png b/framework_crates/bones_asset/examples/assets/players/john/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..a96df3d53d32466f89c2e7454cfd0be54c16d396 GIT binary patch literal 34581 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
7VF=fanFQRTaAm$kR~R3tqg zCkk%C^?la7{$Ae;|3byGAfNW8i!;mia^CMwWWU2#dg)nx`L6%p$!~3$<;=_4%6Cjx zUX=W(uVOR{{viw{beGZ6hk&{$F2jZjmO>Sdm%hMh{|EO3OYC{Sa{;YOdB*4|`xnF0 z$wreM#h(MSf_I#@-ppao6K$+1pX#P32D(4C=Q|UZbV>Fqd!)|^bnVl_0n;}xr-(eN z^_v3=trN(MKOL~Kc`>j7QCz62vu#&+PVCXDdqqudd#ONc;~dT7plNPpIeHcRwL7+n z&k?scSSL4}^0@i9jJRq(K78U#L5PMw zB&Jm+TGO00Sv}Vn`RXM16%h}hiwez*6W#LNnw!%Qy^O_AyCdbGCHlnf+;Vq*HYCxT zJ^_bi%xuS;If%q9gtP_3S-Ljozj0Q7QIft_|1c*UJVCIs=f5#+vN#v9b|i}Dvqg!` zLh~oDC#OJ8z4c*vBKCcrKR++u;8tIO^W@knJsB|l@iZm zJ_;H~9R26A9(eZx?ZKsQ$sP z{!UivLy=MA`uEf0s9JdcIB{NtqT9M{ZSb4THt_csz*yn{)Z{u72fIh z$U-90gZd$JKUz7AuPuhrg}u9wIR|{Zuz3NS4M>P7Q~VQHKqoEXP{#Dth+Mh}2q9%l zn|})fR^o<*Bn0xWa%1KMTOLQeixH^LoNH{AY zBoRU~DLR&>2j*F;Pap$}|5A8nX5b@3IMY9CeL(mH=qk#WgRgqeoM)MA`%3j#{_9S! zWx8|BR4Ee<)X8K_AQkoXegF4n(|-ZcOX3*Tz>$~z+&oiAd-{wlfB#1~n2mC&&zNOI zj4v+?7yZHy9{7bd<~MDQUR4AyB6rP^^2~GdAYL^cD^OC}NJk|>Dsccd9dj@7{{PH@ zwuz~w<@^^Ae`VtcwPbs`hyNz$P1#&O+o2O+7!-PYLWxoGbixfJ=bfA$uua+JH19p< zv)?DaWJmwAB~-Q=Ggdh3&B$}^KfGgKHA5w+80mBY+WYt&!8g9;J(qrwho>pB??|LxA$5q*{{ud z7b{=^dCISFGCfzyqYgeSSmpl$#{xM0`zj_;{;8KO!22)#8v8!vir?4~KXbnioqhnQ zDg8IA=vVGi5gI$e=9gM}6U`e$j{eQvmlz&!XrMCUS>qo#81`2sO z(f^=HKLC9HXH)5y+|d_6Xw8WcOEZW#6-es8=kf5*zSfBA_Y^<-G60r_=%-Hs?&1|s zxF(+P&cf}v$wVLT>gAs_B|;6QH#>0zkCqft2Soa%z{#oTvISU@4i7-t=Xq^iV8?So z+CAg1r08A|q{k;{}6UJV20zmfzW5efGXQDKb;S^RrsxN=SX%KZn-~f=>m6O0vke;hrIw%?YprIfJGtq=~Hm+u(kOLSi0a( zULRc+eVqrn3ve&!nQsPmJQpm)%K4uy-w9rPz6bpujVlj!!+$EGrnm16W{1JjRo=qg(wkWGZSiv(Jnto?;}hv+7wASvJj`$w zaX%!X2Y|Ny?*c&j6o|uc{Nccs?*uJfWx|z-C%ns|U)K}a=CPeUP_7S(!(izG`&Jlw za3uirJRZiO(3*4G%K)e$@!B(4H6@;e9_1csyh!d*<)>c)MbTFr;9#HV2Tks|hk}c* zyFjpRkBe8pzO@_&x9^UaeUN&K=fPbDfR?P+96nT(bv*ao$?=qwXgm*v9towJH;8h% zhw8lkvgqqSq%YE^plJsHz}f%O2>@%q9>4lyYxvwMfU%l4>i+{(f7p(!w~br?0000< KMNUMnLSTZin>hpk literal 0 HcmV?d00001 diff --git a/framework_crates/bones_asset/examples/assets/players/john/john.atlas.yaml b/framework_crates/bones_asset/examples/assets/players/john/john.atlas.yaml new file mode 100644 index 0000000000..23b18d372e --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/players/john/john.atlas.yaml @@ -0,0 +1,8 @@ +# Struct fields can be filled out by name, as we have seen before. +tile_size: + x: 25.5 + y: 30 + +# Or they can also be filled out as a list, which is particularly +# convenient for glam vectors. +grid_size: [2, 8] diff --git a/framework_crates/bones_asset/examples/assets/players/john/john.player.yaml b/framework_crates/bones_asset/examples/assets/players/john/john.player.yaml new file mode 100644 index 0000000000..f680b453b1 --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/players/john/john.player.yaml @@ -0,0 +1,12 @@ +# This is an example player asset. +# In Rust it is represented by the PlayerMeta struct. + +name: John +age: 22 +atlas: ./john.atlas.yaml +avatar: ./avatar.png +stats: + speed: 70 + # Left out fields like `intelligence` from the `PlayerMetaStats` struct, + # will be set to their default value. In the case of PlayerMetaStats, + # we have set the default value to 20. diff --git a/framework_crates/bones_asset/examples/assets/root.game.yaml b/framework_crates/bones_asset/examples/assets/root.game.yaml new file mode 100644 index 0000000000..a35f8d97f8 --- /dev/null +++ b/framework_crates/bones_asset/examples/assets/root.game.yaml @@ -0,0 +1,11 @@ +# This is the root asset. It is referenced by the pack.yaml file. +# In Rust this is represented by the GameMeta struct. +# This asset will reference all the other assets that are included in the pack. + +# We can have global game options here. +gravity: 9.8 + +# And we can reference other assets. +players: + - ./players/jane/jane.player.yaml + - ./players/john/john.player.yaml diff --git a/framework_crates/bones_asset/examples/packs/pack1/pack.yaml b/framework_crates/bones_asset/examples/packs/pack1/pack.yaml new file mode 100644 index 0000000000..bae04f874b --- /dev/null +++ b/framework_crates/bones_asset/examples/packs/pack1/pack.yaml @@ -0,0 +1,24 @@ +# +# This is an example asset pack. +# + +# Each pack has a name, meant to be a user display name. +name: Pack 1 + +# Each pack has a version. Packs will use semver to match dependencies between packs. +version: 0.1.0 + +# The pack must also specify the game version that it is compatible with. This uses +# cargo's version parsing conventions. +# +# Note: In the tutorial example we set the game version to 0.1.3. That is compatible +# with version 0.1.0, so this asset pack will load, even though there is not an exact +# version match. +game_version: 0.1.0 + +# Each pack has a unique ID, which is made up of a string prefix, an underscore, and a ULID +# identifier. +id: pack-1_01H4PKNEVFFW3TH09R1N8006BA + +# Finally, we specify the root asset for the pack. +root: ./pack1.plugin.yaml diff --git a/framework_crates/bones_asset/examples/packs/pack1/pack1.plugin.yaml b/framework_crates/bones_asset/examples/packs/pack1/pack1.plugin.yaml new file mode 100644 index 0000000000..4089cac51d --- /dev/null +++ b/framework_crates/bones_asset/examples/packs/pack1/pack1.plugin.yaml @@ -0,0 +1,3 @@ +# Plugin assets are very simple in this example. They just have a description. + +description: This is my super awesome demo plugin. Yeah, it doesn't do much... diff --git a/framework_crates/bones_asset/examples/packs/pack2/pack.yaml b/framework_crates/bones_asset/examples/packs/pack2/pack.yaml new file mode 100644 index 0000000000..4adf1a2b4c --- /dev/null +++ b/framework_crates/bones_asset/examples/packs/pack2/pack.yaml @@ -0,0 +1,11 @@ +# This is an example of another asset pack. + +name: Pack 2 +version: 0.1.0 +id: pack-2_01H5TWR2EGEZ9CKQBVGK9S90HX +root: ./pack2.plugin.yaml + +# This game version is not compatible with the tutorial version of 0.1.3, +# so this pack will not be loaded and will be listed in the incompatible +# asset packs. +game_version: 0.7.0 diff --git a/framework_crates/bones_asset/examples/packs/pack2/pack2.plugin.yaml b/framework_crates/bones_asset/examples/packs/pack2/pack2.plugin.yaml new file mode 100644 index 0000000000..a8703012c4 --- /dev/null +++ b/framework_crates/bones_asset/examples/packs/pack2/pack2.plugin.yaml @@ -0,0 +1,2 @@ +description: | + This plugin demo's being incompatible with the game. diff --git a/framework_crates/bones_asset/examples/tutorial.rs b/framework_crates/bones_asset/examples/tutorial.rs new file mode 100644 index 0000000000..e641e21f0b --- /dev/null +++ b/framework_crates/bones_asset/examples/tutorial.rs @@ -0,0 +1,209 @@ +use bones_asset::prelude::*; +use glam::{UVec2, Vec2}; + +use std::path::PathBuf; + +/// We will use this as our root asset data, as we will see below. +#[derive(HasSchema, Debug, Default, Clone)] +// Every asset type that will be loaded from a file needs either the `metadata_asset` annotation, or +// the `asset_loader` annotation. +// +// The `metadata_asset` annotation tells the asset loader to load the file from a YAML or JSON file +// with an extension like `.game.yaml`, or `.game.json`. +#[type_data(metadata_asset("game"))] +#[repr(C)] +struct GameMeta { + // We can add global game settings, for example. + pub gravity: f32, + /// Handles allow one asset to reference another asset in the asset files. + pub players: SVec>, +} + +/// We will use this as our player meta format. +#[derive(HasSchema, Debug, Default, Clone)] +// We want to load players from `.player.yaml` files. +#[type_data(metadata_asset("player"))] +#[repr(C)] +struct PlayerMeta { + /// We include basic info. + pub name: String, + /// This will fail to deserialize if the asset file has a value outside of the u8 range. + pub age: u8, + /// And we also reference a separate AtlasMeta asset, which will be loaded from a separate file. + pub atlas: Handle, + /// We can include nested structs of our own if they implement HasSchema. This will not be + /// loaded from a separate file because it is not in a Handle. + pub stats: PlayerMetaStats, + /// We can also load key-value data using an SMap + pub animations: SMap, + /// The player's avatar. This is a custom asset type, which we will implement below. + pub avatar: Handle, +} + +/// Player animation metadata +#[derive(HasSchema, Debug, Default, Clone)] +#[repr(C)] +pub struct AnimMeta { + fps: f32, + frames: SVec, +} + +/// Stats used in [`PlayerMeta`]. +#[derive(HasSchema, Debug, Clone)] +#[repr(C)] +struct PlayerMetaStats { + pub speed: f32, + pub intelligence: f32, +} + +// We can also implement custom defaults, so that un-specified fields will be set to the default +// value. +impl Default for PlayerMetaStats { + fn default() -> Self { + Self { + speed: 30.0, + intelligence: 20.0, + } + } +} + +/// The atlas metadata referenced by [`PlayerMeta`]. +#[derive(HasSchema, Debug, Default, Clone)] +#[type_data(metadata_asset("atlas"))] +#[repr(C)] +struct AtlasMeta { + /// We can include glam types! + pub tile_size: Vec2, + pub grid_size: UVec2, +} + +/// We also want to support loading asset packs, so we create a plugin metdata type that will be +/// used for plugin assets. +#[derive(HasSchema, Debug, Default, Clone)] +#[type_data(metadata_asset("plugin"))] +#[repr(C)] +struct PluginMeta { + /// We'll keep this one simple for now. + pub description: String, +} + +/// We can also make asset types that use a custom asset loader, for example, for images. +#[derive(HasSchema, Debug, Clone, Default)] +// We specify the file extensions and the asset loader to use to load the asset. +#[type_data(asset_loader(["png", "jpg"], ImageAssetLoader))] +#[schema(opaque)] +struct Image { + data: Vec, + width: u32, + height: u32, +} + +/// Our custom loader for image assets. +struct ImageAssetLoader; +impl AssetLoader for ImageAssetLoader { + fn load(&self, _ctx: AssetLoadCtx, bytes: &[u8]) -> anyhow::Result { + // We're not going to bother actually loading the image. + Ok(SchemaBox::new(Image { + data: bytes.to_vec(), + width: 0, + height: 0, + })) + } +} + +fn main() -> anyhow::Result<()> { + // Locate the dir that our core asset pack will be loaded from. + let core_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("assets"); + // Locate the dir that our other asset packs will be loaded from. These are presumably able to + // be installed by the user for modding, etc. + let packs_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("packs"); + + // Create a FileAssetIo to load assets from the filesystem. + // + // We can implement different AssetIo implementations for things web builds or other use-cases. + let io = FileAssetIo::new(&core_dir, &packs_dir, true); + + // Create an asset server that we can load the assets with. We must provide our AssetIo + // implementation, and the version of our game, which is used to determine if asset packs are + // compatible with our game version. + let mut asset_server = AssetServer::new(io, Version::new(0, 1, 3)); + + // Each asset type needs to be registered with the asset server. + asset_server.register_asset::(); + asset_server.register_asset::(); + asset_server.register_asset::(); + asset_server.register_asset::(); + asset_server.register_asset::(); + + // Load all of the assets. This happens synchronously. After this function completes, all the + // assets have been loaded, or an error is returned. + asset_server.load_assets()?; + + // No we can load the root asset handle of the core asset pack. We cast it to the expected type, + // GameMeta. + let root_handle = asset_server.core().root.typed::(); + + // We use the handle to get a reference to the `GameMeta`. This would panic if the actual asset + // type was not `GameMeta`. + let game_meta = asset_server.get(root_handle); + + dbg!(&game_meta); + assert_eq!(game_meta.gravity, 9.8); + + // The GameMeta contains a handle to the player asset, which we can get here. + for (i, player_handle) in game_meta.players.iter().enumerate() { + // And we can load the `PlayerMeta` using the handle. + let player_meta = asset_server.get(*player_handle); + + dbg!(player_meta); + + // And we can load the player's atlas metadata in the same way. + let atlas_handle = player_meta.atlas; + let atlas_meta = asset_server.get(atlas_handle); + dbg!(atlas_meta); + + let avatar = asset_server.get(player_meta.avatar); + dbg!(avatar.data.len(), avatar.width, avatar.height); + + if i == 0 { + assert_eq!(player_meta.name, "Jane"); + // This should be the default value because it was left unspecified in the asset file. + assert_eq!(player_meta.stats.intelligence, 20.); + assert_eq!(atlas_meta.tile_size, Vec2::new(25.5, 30.)); + assert_eq!(atlas_meta.grid_size, UVec2::new(2, 4)); + } + } + + // We can also check out our loaded asset packs. + println!("\n===== Asset Packs =====\n"); + for (pack_spec, asset_pack) in asset_server.packs() { + // Let's load the plugin metadata from the pack. + let plugin_handle = asset_pack.root.typed::(); + let plugin_meta = asset_server.get(plugin_handle); + + // Print the pack name and version and it's description + println!("{pack_spec}: {}", plugin_meta.description); + } + + // Finally, there may be some asset packs that are installed, but not compatible with our game + // version. Let's check for those. + println!("\n===== Incompatible Asset Packs ====\n"); + + // We can iterate over the incompatible packs, and print a message describing the mismatch. + for (folder_name, pack_meta) in &asset_server.incompabile_packs { + let id = pack_meta.id; + let version = &pack_meta.version; + let actual_game_version = &asset_server.game_version; + let compatible_game_version = &pack_meta.game_version; + println!( + "{id}@{version} in folder `{folder_name}` is not compatible with game version \ + {actual_game_version} - pack is compatible with game version {compatible_game_version}" + ); + } + + Ok(()) +} diff --git a/framework_crates/bones_asset/src/asset.rs b/framework_crates/bones_asset/src/asset.rs new file mode 100644 index 0000000000..116cc5c13f --- /dev/null +++ b/framework_crates/bones_asset/src/asset.rs @@ -0,0 +1,343 @@ +use std::{ + path::{Path, PathBuf}, + sync::OnceLock, +}; + +use bones_utils::prelude::*; +use semver::VersionReq; + +use crate::prelude::*; + +/// The unique ID for an asset pack. +/// +/// Asset pack IDs are made up of a human-readable label, and a unique identifier. For example: +/// +/// > awesome-pack_01h502c309fddv1vq1gwa918e8 +/// +/// These IDs can be generated with the [TypeID gen][gen] utility. +/// +/// [gen]: https://zicklag.github.io/type-id-gen/ +pub type AssetPackId = LabeledId; + +/// An asset pack contains assets that are loaded by the game. +/// +/// The game's built-in assets are contained the the core asset pack, and mods or other assets may +/// also be loaded. +#[derive(Clone, Debug)] +pub struct AssetPack { + /// The display name of the asset pack. + pub name: String, + /// The unique ID of the asset pack. + pub id: AssetPackId, + /// The version number of the asset pack. + pub version: Version, + + /// The game [`VersionReq`] this asset pack is compatible with. + pub game_version: VersionReq, + + /// Schemas provided in the asset pack. + pub schemas: HashMap, + /// Specify schemas to import from other asset packs. + pub import_schemas: HashMap, + /// The root asset for the asset pack. + pub root: UntypedHandle, +} + +/// Specifies an asset pack, and it's exact version. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct AssetPackSpec { + /// The ID of the asset pack. + pub id: AssetPackId, + /// The version of the asset pack. + pub version: Version, +} + +impl std::fmt::Display for AssetPackSpec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { id, version } = self; + write!(f, "{id}@{version}") + } +} + +/// A requirement specifier for an asset pack, made up of the asset pack's [`LabeledId`] and it's +/// [`VersionReq`]. +#[derive(Debug, Clone)] +pub struct AssetPackReq { + /// The asset pack ID. + pub id: LabeledId, + /// The version of the asset pack. + pub version: VersionReq, +} + +/// A schema reference, containing the ID of the pack that defined the schema, and the name of the +/// schema in the pack. +#[derive(Clone, Debug)] +pub struct SchemaPath { + /// The ID of the pack, or [`None`] if it refers to the core pack. + pub pack: Option, + /// The name of the schema. + pub name: String, +} + +/// Struct responsible for loading assets into it's contained [`AssetStore`], using an [`AssetIo`] +/// implementation. +#[derive(HasSchema)] +#[schema(opaque, no_clone)] +pub struct AssetServer { + /// The version of the game. This is used to evaluate whether asset packs are compatible with + /// the game. + pub game_version: Version, + /// The [`AssetIo`] implementation used to load assets. + pub io: Box, + /// The asset store. + pub store: AssetStore, + /// List of registered asset types. + pub asset_types: Vec<&'static Schema>, + /// Lists the packs that have not been loaded due to an incompatible game version. + pub incompabile_packs: HashMap, + /// Channel fro the [`AssetIo`] implementation that is used to detect asset changes. + pub asset_changes: OnceLock>>, +} + +impl Default for AssetServer { + fn default() -> Self { + Self { + game_version: Version::new(0, 0, 0), + io: Box::new(DummyIo::new([])), + store: default(), + asset_types: default(), + incompabile_packs: default(), + asset_changes: default(), + } + } +} + +/// Struct containing all the game's loaded assets, including the default assets and +/// asset-packs/mods. +pub struct LoadedAssets { + /// The game's default asset pack. + pub default: UntypedHandle, + /// Extra asset packs. The key is the the name of the asset pack. + pub packs: HashMap, +} + +/// Stores assets for later retrieval. +#[derive(Default, Clone, Debug)] +pub struct AssetStore { + /// Maps the handle of the asset to it's content ID. + pub asset_ids: HashMap, + /// Maps asset content IDs, to loaded assets. + pub assets: HashMap, + /// Maps the asset [`AssetLoc`] to it's handle. + pub path_handles: HashMap, + /// List of assets that depend on the given assets. + pub reverse_dependencies: HashMap>, + + /// The core asset pack, if it's been loaded. + pub core_pack: Option, + /// The asset packs that have been loaded. + pub packs: HashMap, + /// Maps the directory names of asset packs to their [`AssetPackSpec`]. + pub pack_dirs: HashMap, +} + +/// Contains that path to an asset, and the pack_dir that it was loaded from. +/// +/// A pack of [`None`] means that it was loaded from the core pack. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct AssetLoc { + /// The path to the asset in it's pack. + pub path: PathBuf, + /// The pack_dir of the pack that the asset is in. + pub pack: Option, +} + +impl AssetLoc { + /// Borrow as an [`AssetLocRef`]. + pub fn as_ref(&self) -> AssetLocRef { + AssetLocRef { + pack: self.pack.as_deref(), + path: &self.path, + } + } +} + +impl From<&AssetLocRef<'_>> for AssetLoc { + fn from(value: &AssetLocRef<'_>) -> Self { + AssetLoc { + path: value.path.to_owned(), + pack: value.pack.map(|x| x.to_owned()), + } + } +} + +/// A borrowed version of [`AssetLoc`]. +#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)] +pub struct AssetLocRef<'a> { + /// The path to the asset in it's pack. + pub path: &'a Path, + /// The pack_dir of the pack that the asset is in. + pub pack: Option<&'a str>, +} + +impl<'a> From<(&'a Path, Option<&'a str>)> for AssetLocRef<'a> { + fn from((path, pack): (&'a Path, Option<&'a str>)) -> Self { + Self { pack, path } + } +} + +impl AssetLocRef<'_> { + /// Clone data to an owned [`AssetLoc`]. + pub fn to_owned(&self) -> AssetLoc { + self.into() + } +} + +/// An asset that has been loaded. +#[derive(Clone, Debug, Deref, DerefMut)] +pub struct LoadedAsset { + /// The content ID of the loaded asset. + /// + /// This is a hash of the contents of the asset's binary data and all of the cids of it's + /// dependencies. + pub cid: Cid, + /// The asset pack this was loaded from, or [`None`] if it is from the default pack. + pub pack_spec: Option, + /// The pack and path the asset was loaded from. + pub loc: AssetLoc, + /// The content IDs of any assets needed by this asset as a dependency. + pub dependencies: Vec, + /// The loaded data of the asset. + #[deref] + pub data: SchemaBox, +} + +/// An identifier for an asset. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct AssetInfo { + /// The unique ID of the asset pack this asset is located in. + pub pack: Cid, + /// The path to the asset, relative to the root of the asset pack. + pub path: PathBuf, +} + +/// Context provided to custom asset loaders in the [`AssetLoader::load`] method. +pub struct AssetLoadCtx<'a> { + /// The asset server. + pub asset_server: &'a mut AssetServer, + /// The location of the asset. + pub loc: AssetLocRef<'a>, + /// The [`Cid`]s of the assets this asset depends on. + /// + /// This is automatically updated when calling [`AssetLoadCtx::load_asset`]. + pub dependencies: &'a mut Vec, +} + +impl AssetLoadCtx<'_> { + /// Load another asset as a child of this asset. + pub fn load_asset(&mut self, path: &Path) -> anyhow::Result { + let handle = self.asset_server.load_asset(AssetLocRef { + path, + pack: self.loc.pack, + })?; + let cid = self.asset_server.store.asset_ids.get(&handle).unwrap(); + self.dependencies.push(*cid); + Ok(handle) + } +} + +/// A custom assset loader. +pub trait AssetLoader: Sync + Send + 'static { + /// Load the asset from raw bytes. + fn load(&self, ctx: AssetLoadCtx, bytes: &[u8]) -> anyhow::Result; +} + +/// The kind of asset a type represents. +#[derive(HasSchema)] +#[schema(opaque, no_default, no_clone)] +pub enum AssetKind { + /// This is a metadata asset that can be loaded from JSON or YAML files. + Metadata { + /// The `extension` is the portion of the extension that comes before the `.json`, `.yml`, + /// or `.yaml` extension. For example, if the `extension` was set to `weapon`, then the asset + /// could be loaded from `.weapon.json`, `.weapon.yml`, or `.weapon.yaml` files. + extension: String, + }, + /// An asset with a custom asset loader. + Custom { + /// The loader implementation for the asset. + loader: Box, + /// The list of file extensions to load this asset from. + extensions: Vec, + }, +} + +/// Helper function to return type data for a metadata asset. +/// +/// # Example +/// +/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`]. +/// +/// ``` +/// # use bones_asset::prelude::*; +/// # use glam::*; +/// #[derive(HasSchema, Default, Clone)] +/// #[type_data(metadata_asset("atlas"))] +/// #[repr(C)] +/// struct AtlasMeta { +/// pub tile_size: Vec2, +/// pub grid_size: UVec2, +/// } +/// ``` +pub fn metadata_asset(extension: &str) -> AssetKind { + AssetKind::Metadata { + extension: extension.into(), + } +} + +/// Helper function to return type data for a custom asset loader. +/// +/// # Example +/// +/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`]. +/// +/// ``` +/// # use bones_asset::prelude::*; +/// #[derive(HasSchema, Default, Clone)] +/// #[type_data(asset_loader("png", PngLoader))] +/// #[repr(C)] +/// struct Image { +/// data: SVec, +/// width: u32, +/// height: u32, +/// } +/// +/// struct PngLoader; +/// impl AssetLoader for PngLoader { +/// fn load(&self, ctx: AssetLoadCtx, data: &[u8]) -> anyhow::Result { +/// todo!("Load PNG from data"); +/// } +/// } +/// ``` +pub fn asset_loader>( + extensions: E, + loader: L, +) -> AssetKind { + AssetKind::Custom { + loader: Box::new(loader), + extensions: extensions.into().0, + } +} + +/// Helper type for storing asset extensions. +pub struct AssetExtensions(Vec); +impl<'a, const N: usize> From<[&'a str; N]> for AssetExtensions { + fn from(value: [&'a str; N]) -> Self { + Self(value.iter().map(|x| x.to_string()).collect()) + } +} +impl<'a> From<&'a str> for AssetExtensions { + fn from(value: &'a str) -> Self { + Self(vec![value.to_string()]) + } +} diff --git a/framework_crates/bones_asset/src/cid.rs b/framework_crates/bones_asset/src/cid.rs new file mode 100644 index 0000000000..fd1d735ff8 --- /dev/null +++ b/framework_crates/bones_asset/src/cid.rs @@ -0,0 +1,31 @@ +/// A unique content ID. +/// +/// Represents the Sha-256 hash of the contents of a [`LoadedAsset`][crate::LoadedAsset]. +#[derive(Clone, Copy, Eq, PartialEq, Hash, Default, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Cid(pub [u8; 32]); + +impl std::fmt::Display for Cid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", bs58::encode(self.0).into_string()) + } +} + +impl std::fmt::Debug for Cid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Cid({})", self) + } +} + +impl Cid { + /// Update the CID by combining it's current data with the hash of the provided bytes. + pub fn update(&mut self, bytes: &[u8]) { + use sha2::Digest; + let pre_hash = self.0; + let mut hasher = sha2::Sha256::new(); + hasher.update(pre_hash); + hasher.update(bytes); + let result = hasher.finalize(); + self.0.copy_from_slice(&result); + } +} diff --git a/framework_crates/bones_asset/src/handle.rs b/framework_crates/bones_asset/src/handle.rs new file mode 100644 index 0000000000..ac02449d59 --- /dev/null +++ b/framework_crates/bones_asset/src/handle.rs @@ -0,0 +1,178 @@ +use std::{alloc::Layout, any::TypeId, marker::PhantomData, sync::OnceLock}; + +use bones_schema::{prelude::*, raw_fns::*}; +use bones_utils::{parking_lot::RwLock, HashMap}; +use ulid::Ulid; + +/// A typed handle to an asset. +#[repr(C)] +pub struct Handle { + /// The runtime ID of the asset. + pub id: Ulid, + phantom: PhantomData, +} + +// Manually implement these traits we normally derive because the derive assumes that `T` must also +// implement these traits. +impl Clone for Handle { + fn clone(&self) -> Self { + *self + } +} +impl Copy for Handle {} +impl PartialEq for Handle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} +impl Eq for Handle {} +impl std::hash::Hash for Handle { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} +impl Default for Handle { + fn default() -> Self { + Self { + id: Default::default(), + phantom: Default::default(), + } + } +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Handle").field("id", &self.id).finish() + } +} + +impl Handle { + /// Convert the handle to an [`UntypedHandle`]. + pub fn untyped(self) -> UntypedHandle { + UntypedHandle { rid: self.id } + } +} + +/// An untyped handle to an asset. +#[derive(Default, Clone, Debug, Hash, PartialEq, Eq, Copy)] +#[repr(C)] +pub struct UntypedHandle { + /// The runtime ID of the handle + pub rid: Ulid, +} + +impl UntypedHandle { + /// Create a typed [`Handle`] from this [`UntypedHandle`]. + pub fn typed(self) -> Handle { + Handle { + id: self.rid, + phantom: PhantomData, + } + } +} + +// +// Schema implementations +// + +/// [Type data][SchemaData::type_data] for asset handles. +/// +/// This allows the asset loader to distinguish when a `SomeStruct(u128)` schema layout should be +/// deserialized as a normal struct or as an asset handle. +#[derive(HasSchema, Clone, Copy, Debug)] +#[schema(opaque, no_default)] +pub struct SchemaAssetHandle { + /// The schema of the type pointed to by the handle, if this is not an [`UntypedHandle`]. + pub schema: Option<&'static Schema>, +} + +// SAFE: We return a valid schema. +unsafe impl HasSchema for Handle { + fn schema() -> &'static bones_schema::Schema { + static S: OnceLock>> = OnceLock::new(); + // This is a hack to make sure that `Ulid` has the memory representation we + // expect. It is extremely unlike, but possible that this would otherwise be + // unsound in the event that Rust picks a weird representation for the + // `Ulid(u128)` struct, which doesn't have a `#[repr(C)]` or + // `#[repr(transparent)]` annotation. + assert_eq!( + Layout::new::(), + Layout::new::(), + "ULID memory layout is unexpected! Bad Rust compiler! 😡" + ); + + let map = S.get_or_init(|| RwLock::new(HashMap::default())); + + let existing_schema = { map.read().get(&TypeId::of::()).copied() }; + + if let Some(existing_schema) = existing_schema { + existing_schema + } else { + let schema = SCHEMA_REGISTRY.register(SchemaData { + type_id: Some(TypeId::of::()), + kind: SchemaKind::Struct(StructSchemaInfo { + fields: vec![StructFieldInfo { + name: Some("id".into()), + schema: u128::schema(), + }], + }), + clone_fn: Some(::raw_clone), + drop_fn: None, + default_fn: Some(::raw_default), + eq_fn: Some(::raw_eq), + hash_fn: Some(::raw_hash), + type_data: { + let mut td = bones_schema::alloc::SchemaTypeMap::default(); + td.insert(SchemaAssetHandle { + schema: Some(T::schema()), + }); + td + }, + }); + + { + let mut map = map.write(); + map.insert(TypeId::of::(), schema); + } + + schema + } + } +} +// SAFE: We return a valid schema. +unsafe impl HasSchema for UntypedHandle { + fn schema() -> &'static bones_schema::Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + // This is a hack to make sure that `Ulid` has the memory representation we + // expect. It is extremely unlike, but possible that this would otherwise be + // unsound in the event that Rust picks a weird representation for the + // `Ulid(u128)` struct, which doesn't have a `#[repr(C)]` or + // `#[repr(transparent)]` annotation. + assert_eq!( + Layout::new::(), + Layout::new::(), + "ULID memory layout is unexpected! Bad Rust compiler! 😡" + ); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + type_id: Some(TypeId::of::()), + kind: SchemaKind::Struct(StructSchemaInfo { + fields: vec![StructFieldInfo { + name: Some("id".into()), + schema: u128::schema(), + }], + }), + clone_fn: Some(::raw_clone), + drop_fn: None, + default_fn: Some(::raw_default), + eq_fn: Some(::raw_eq), + hash_fn: Some(::raw_hash), + type_data: { + let mut td = bones_schema::alloc::SchemaTypeMap::default(); + td.insert(SchemaAssetHandle { schema: None }); + td + }, + }) + }) + } +} diff --git a/framework_crates/bones_asset/src/io.rs b/framework_crates/bones_asset/src/io.rs new file mode 100644 index 0000000000..b9063b2357 --- /dev/null +++ b/framework_crates/bones_asset/src/io.rs @@ -0,0 +1,201 @@ +use std::path::PathBuf; + +use bones_utils::HashMap; + +use crate::{AssetLoc, AssetLocRef}; + +/// [`AssetIo`] is a trait that is implemented for backends capable of loading all the games assets +/// and returning the raw bytes stored in asset files. +pub trait AssetIo: Sync + Send { + /// List the names of the non-core asset pack folders that are installed. + /// + /// These names, are not necessarily the names of the pack, but the names of the folders that + /// they are located in. These names can be used to load files from the pack in the + /// [`load_file()`][Self::load_file] method. + fn enumerate_packs(&self) -> anyhow::Result>; + /// Get the binary contents of an asset. + /// + /// The `pack_folder` is the name of a folder returned by + /// [`enumerate_packs()`][Self::enumerate_packs], or [`None`] to refer to the core pack. + fn load_file(&self, loc: AssetLocRef) -> anyhow::Result>; + + /// Subscribe to asset changes. + fn watch(&self) -> Option>; +} + +/// [`AssetIo`] implementation that loads from the filesystem. +#[cfg(not(target_arch = "wasm32"))] +pub struct FileAssetIo { + /// The directory to load the core asset pack. + pub core_dir: PathBuf, + /// The directory to load the asset packs from. + pub packs_dir: PathBuf, + /// Receiver for asset changed events. + pub change_events: Option>, + /// Filesystem watcher if enabled. + pub watcher: Option>, +} + +#[cfg(not(target_arch = "wasm32"))] +impl FileAssetIo { + /// Create a new [`FileAssetIo`]. + pub fn new(core_dir: &std::path::Path, packs_dir: &std::path::Path, watch: bool) -> Self { + let cwd = std::env::current_dir().unwrap(); + let core_dir = cwd.join(core_dir); + let packs_dir = cwd.join(packs_dir); + let mut watcher = None; + let mut change_events = None; + if watch { + use notify::{RecursiveMode, Result, Watcher}; + let (sender, receiver) = async_channel::bounded(20); + + let core_dir_ = core_dir.clone(); + let packs_dir_ = packs_dir.clone(); + notify::recommended_watcher(move |res: Result| { + match res { + Ok(event) => match &event.kind { + notify::EventKind::Create(_) | notify::EventKind::Modify(_) => { + for path in event.paths { + let (path, pack) = if let Ok(path) = path.strip_prefix(&core_dir_) { + (path, None) + } else if let Ok(path) = path.strip_prefix(&packs_dir_) { + let pack = + path.iter().next().unwrap().to_str().unwrap().to_string(); + let path = path.strip_prefix(&pack).unwrap(); + (path, Some(pack)) + } else { + continue; + }; + sender + .send_blocking(AssetLoc { + path: path.into(), + pack, + }) + .unwrap(); + } + } + _ => (), + }, + // TODO: Log `bones_asset` errors with tracing. + // + // Also see the unwrap_or_else line below, which needs an error log. + Err(e) => eprintln!("watch error: {e:?}"), + } + }) + .and_then(|mut w| { + if core_dir.exists() { + w.watch(&core_dir, RecursiveMode::Recursive)?; + } + if packs_dir.exists() { + w.watch(&packs_dir, RecursiveMode::Recursive)?; + } + + watcher = Some(Box::new(w) as _); + change_events = Some(receiver); + Ok(()) + }) + .map_err(|e| { + eprintln!("watch error: {e:?}"); + // See todo above: log error message. + }) + .ok(); + } + Self { + core_dir: core_dir.clone(), + packs_dir: packs_dir.clone(), + change_events, + watcher, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl AssetIo for FileAssetIo { + fn enumerate_packs(&self) -> anyhow::Result> { + if !self.packs_dir.exists() { + return Ok(Vec::new()); + } + + // List the folders in the asset packs dir. + let dirs = std::fs::read_dir(&self.packs_dir)? + .map(|entry| { + let entry = entry?; + let name = entry + .file_name() + .to_str() + .expect("non-unicode filename") + .to_owned(); + Ok::<_, std::io::Error>(name) + }) + .filter(|x| { + x.as_ref() + .map(|name| self.packs_dir.join(name).is_dir()) + .unwrap_or(true) + }) + .collect::, _>>()?; + + Ok(dirs) + } + + fn load_file(&self, loc: AssetLocRef) -> anyhow::Result> { + let base_dir = match loc.pack { + Some(folder) => self.packs_dir.join(folder), + None => self.core_dir.clone(), + }; + let path = base_dir.join(loc.path); + Ok(std::fs::read(path)?) + } + + fn watch(&self) -> Option> { + self.change_events.clone() + } +} + +/// Dummy [`AssetIo`] implementation used for debugging or as a placeholder. +pub struct DummyIo { + core: HashMap>, + packs: HashMap>>, +} + +impl DummyIo { + /// Initialize a new [`DummyIo`] from an iterator of `(string_path, byte_data)` items. + pub fn new<'a, I: IntoIterator)>>(core: I) -> Self { + Self { + core: core + .into_iter() + .map(|(p, d)| (PathBuf::from(p), d)) + .collect(), + packs: Default::default(), + } + } +} + +impl AssetIo for DummyIo { + fn enumerate_packs(&self) -> anyhow::Result> { + Ok(self.packs.keys().cloned().collect()) + } + + fn load_file(&self, loc: AssetLocRef) -> anyhow::Result> { + let err = || { + anyhow::format_err!( + "File not found: `{:?}` in pack `{:?}`", + loc.path, + loc.pack.unwrap_or("[core]") + ) + }; + if let Some(pack_folder) = loc.pack { + self.packs + .get(pack_folder) + .ok_or_else(err)? + .get(loc.path) + .cloned() + .ok_or_else(err) + } else { + self.core.get(loc.path).cloned().ok_or_else(err) + } + } + + fn watch(&self) -> Option> { + None + } +} diff --git a/framework_crates/bones_asset/src/lib.rs b/framework_crates/bones_asset/src/lib.rs new file mode 100644 index 0000000000..6a76929fd2 --- /dev/null +++ b/framework_crates/bones_asset/src/lib.rs @@ -0,0 +1,30 @@ +//! An asset interface for Bones. + +#![warn(missing_docs)] +// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable +#![cfg_attr(doc, allow(unknown_lints))] +#![deny(rustdoc::all)] + +/// Helper to export the same types in the crate root and in the prelude. +macro_rules! pub_use { + () => { + pub use crate::{asset::*, cid::*, handle::*, io::*, path::*, server::*}; + pub use anyhow; + pub use bones_schema::prelude::*; + pub use semver::Version; + }; +} +pub_use!(); + +/// The prelude. +pub mod prelude { + pub_use!(); +} + +mod asset; +mod cid; +mod handle; +mod io; +mod parse; +mod path; +mod server; diff --git a/framework_crates/bones_asset/src/parse.rs b/framework_crates/bones_asset/src/parse.rs new file mode 100644 index 0000000000..4a9bd1c4ef --- /dev/null +++ b/framework_crates/bones_asset/src/parse.rs @@ -0,0 +1,71 @@ +use std::str::FromStr; + +use bones_utils::LabeledId; +use semver::VersionReq; +use serde::Deserialize; + +use crate::prelude::*; + +/// Deserializeable struct for schema files. +/// +/// This struct is required because you can't use `serde(with = "..")` directly on the [`Schema`] +/// enum to make it use nested struct syntax instead of YAML tags for enum representation. Avoiding +/// tags is necessary because we use nested enums such as `vec: primitive: string`. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct SchemaFile { + /// The schema defined in the file + #[cfg_attr( + feature = "serde", + serde(with = "serde_yaml::with::singleton_map_recursive") + )] + #[cfg_attr(feature = "serde", serde(flatten))] + pub schema: Schema, +} + +impl FromStr for AssetPackReq { + type Err = String; + fn from_str(s: &str) -> Result { + if let Some((id, version)) = s.split_once('@') { + let id = id.parse::().map_err(|e| e.to_string())?; + let version = version.parse::().map_err(|e| e.to_string())?; + Ok(Self { id, version }) + } else { + let id = s.parse::().map_err(|e| e.to_string())?; + Ok(Self { + id, + version: VersionReq::STAR, + }) + } + } +} + +impl FromStr for SchemaPath { + type Err = String; + fn from_str(s: &str) -> Result { + if let Some((pack_id, schema)) = s.split_once('/') { + let pack = AssetPackReq::from_str(pack_id)?; + + Ok(SchemaPath { + pack: Some(pack), + name: schema.into(), + }) + } else { + Ok(SchemaPath { + pack: None, + name: s.into(), + }) + } + } +} + +impl<'de> Deserialize<'de> for SchemaPath { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let s = String::deserialize(deserializer)?; + s.parse::().map_err(D::Error::custom) + } +} diff --git a/framework_crates/bones_asset/src/path.rs b/framework_crates/bones_asset/src/path.rs new file mode 100644 index 0000000000..e36215df88 --- /dev/null +++ b/framework_crates/bones_asset/src/path.rs @@ -0,0 +1,46 @@ +use std::path::{Path, PathBuf}; + +/// Take `path`, treat it as a path relative to `base_path`, normalize it, and return a new path +/// with the result. +pub fn normalize_path_relative_to(path: &Path, base_path: &Path) -> PathBuf { + let is_relative = !path.starts_with(Path::new("/")); + + let path = if is_relative { + let base = base_path.parent().unwrap_or_else(|| Path::new("")); + base.join(path) + } else { + path.to_path_buf() + }; + + normalize_path(&path) +} + +/// Normalize a path +pub fn normalize_path(path: &std::path::Path) -> std::path::PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ std::path::Component::Prefix(..)) = components.peek() { + let buf = std::path::PathBuf::from(c.as_os_str()); + components.next(); + buf + } else { + std::path::PathBuf::new() + }; + + for component in components { + match component { + std::path::Component::Prefix(..) => unreachable!(), + std::path::Component::RootDir => { + ret.push(component.as_os_str()); + } + std::path::Component::CurDir => {} + std::path::Component::ParentDir => { + ret.pop(); + } + std::path::Component::Normal(c) => { + ret.push(c); + } + } + } + + ret +} diff --git a/framework_crates/bones_asset/src/server.rs b/framework_crates/bones_asset/src/server.rs new file mode 100644 index 0000000000..26e8bdf9fe --- /dev/null +++ b/framework_crates/bones_asset/src/server.rs @@ -0,0 +1,898 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use once_cell::sync::Lazy; +use semver::VersionReq; +use serde::{de::DeserializeSeed, Deserialize}; +use ulid::Ulid; + +use crate::prelude::*; +use bones_utils::*; + +/// YAML format for the core asset pack's `pack.yaml` file. +#[derive(Debug, Clone, Deserialize)] +pub struct CorePackfileMeta { + /// The path to the root asset for the pack. + pub root: PathBuf, +} + +/// YAML format for asset packs' `pack.yaml` file. +#[derive(Debug, Clone, Deserialize)] +pub struct PackfileMeta { + /// The path to the root asset for the pack. + pub root: PathBuf, + /// The unique ID of the asset pack. + pub id: AssetPackId, + /// The version of the asset pack. + pub version: Version, + /// The required game version to be compatible with this asset pack. + pub game_version: VersionReq, +} + +/// The [`AssetPackId`] of the core pack. +pub static CORE_PACK_ID: Lazy = + Lazy::new(|| AssetPackId::new_with_ulid("core", Ulid(0)).unwrap()); + +/// An error returned when an asset pack does not support the game version. +#[derive(Debug, Clone)] +pub struct IncompatibleGameVersionError { + /// The version of the game that the pack is not compatible with. + pub game_version: Version, + /// The directory of the pack that + pub pack_dir: String, + /// The metadata of the pack that could not be loaded. + pub pack_meta: PackfileMeta, +} + +impl std::error::Error for IncompatibleGameVersionError {} +impl std::fmt::Display for IncompatibleGameVersionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Asset pack `{}` v{} from folder `{}` is only compatible with game versions matching {}, not {}", + self.pack_meta.id, + self.pack_meta.version, + self.pack_dir, + self.pack_meta.game_version, + self.game_version + ) + } +} + +#[derive(Debug)] +struct LoaderNotFound { + name: String, +} +impl std::fmt::Display for LoaderNotFound { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Schema/loader not found for schema/extension: {}\n\ + You may need to register the asset with asset_server.register_asset::()", + self.name + ) + } +} +impl std::error::Error for LoaderNotFound {} + +impl AssetServer { + /// Initialize a new [`AssetServer`]. + pub fn new(io: Io, version: Version) -> Self { + Self { + io: Box::new(io), + game_version: version, + store: default(), + asset_types: default(), + incompabile_packs: default(), + asset_changes: default(), + } + } + + /// Register an asset type. + pub fn register_asset(&mut self) -> &mut Self { + if T::schema().type_data.get::().is_none() { + panic!( + "Type `{}` must have AssetType type data", + std::any::type_name::() + ); + } + self.asset_types.push(T::schema()); + + self + } + + /// Set the [`AssetIo`] implementation. + /// + /// This should almost always be called before calling [`load_assets()`][Self::load_assets]. + pub fn set_io(&mut self, io: Io) { + self.io = Box::new(io); + } + + /// Load the assets. + /// + /// All of the assets are immediately loaded synchronously, blocking until load is complete. + pub fn load_assets(&mut self) -> anyhow::Result<()> { + let core_pack = self.load_pack(None)?; + let mut packs = HashMap::default(); + + // For every asset pack + for pack_dir in self.io.enumerate_packs()? { + // Load the asset pack + let pack_result = self.load_pack(Some(&pack_dir)); + match pack_result { + // If the load was successful + Ok(pack) => { + // Add it to our pack list. + let spec = AssetPackSpec { + id: pack.id, + version: pack.version.clone(), + }; + packs.insert(spec, pack); + } + // If there was an error. + Err(e) => match e.downcast::() { + // Check for a compatibility error + Ok(e) => { + // Add it to the list of incompatible packs. + self.incompabile_packs.insert(e.pack_dir, e.pack_meta); + } + // If this is another kind of error, return the error + Err(e) => { + return Err(e).context(format!("Error loading asset pack: {pack_dir}")) + } + }, + } + } + + self.store.packs = packs; + self.store.core_pack = Some(core_pack); + + Ok(()) + } + + /// Load the asset pack with the given folder name, or else the default pack if [`None`]. + pub fn load_pack(&mut self, pack: Option<&str>) -> anyhow::Result { + // Load the core pack differently + if pack.is_none() { + return self + .load_core_pack() + .context("Error loading core asset pack"); + } + + // Load the asset packfile + let packfile_contents = self.io.load_file((Path::new("pack.yaml"), pack).into())?; + let meta: PackfileMeta = serde_yaml::from_slice(&packfile_contents)?; + + // If the game version doesn't match, then don't continue loading this pack. + if !meta.game_version.matches(&self.game_version) { + return Err(IncompatibleGameVersionError { + game_version: self.game_version.clone(), + pack_dir: pack.unwrap().to_owned(), + pack_meta: meta, + } + .into()); + } + + // Store the asset pack spec associated to the pack dir name. + if let Some(pack_dir) = pack { + self.store.pack_dirs.insert( + pack_dir.into(), + AssetPackSpec { + id: meta.id, + version: meta.version.clone(), + }, + ); + } + + if !path_is_metadata(&meta.root) { + anyhow::bail!( + "Root asset must be a JSON or YAML file with a name in the form: \ + [filename].[asset_kind].[yaml|json]" + ); + } + + // Load the asset and produce a handle + let root_loc = AssetLocRef { + path: &meta.root, + pack, + }; + let root_handle = self.load_asset(root_loc).map_err(|e| { + e.context(format!( + "Error loading asset from pack `{}`: {:?}", + pack.unwrap(), + meta.root + )) + })?; + + // Return the loaded asset pack. + Ok(AssetPack { + name: "Core".into(), + id: meta.id, + version: meta.version, + game_version: meta.game_version, + // TODO: load & import schemas that are defined in asset packs. + schemas: default(), + import_schemas: default(), + root: root_handle, + }) + } + + /// Responds to any asset changes reported by the [`AssetIo`] implementation. + /// + /// This must be called or asset changes will be ignored. Additionally, the [`AssetIo`] + /// implementation must be able to detect asset changes or this will do nothing. + pub fn handle_asset_changes( + &mut self, + mut handle_change: F, + ) { + if let Some(receiver) = self.asset_changes.get_or_init(|| self.io.watch()).clone() { + while let Ok(loc) = receiver.try_recv() { + match self.load_asset_forced(loc.as_ref()) { + Ok(handle) => handle_change(self, handle), + Err(_) => { + // TODO: Handle/log asset error. + continue; + } + }; + } + } + } + + /// Load the core asset pack. + pub fn load_core_pack(&mut self) -> anyhow::Result { + // Load the core asset packfile + let packfile_contents = self.io.load_file(AssetLocRef { + path: Path::new("pack.yaml"), + pack: None, + })?; + let meta: CorePackfileMeta = serde_yaml::from_slice(&packfile_contents)?; + + if !path_is_metadata(&meta.root) { + anyhow::bail!( + "Root asset must be a JSON or YAML file with a name in the form: \ + [filename].[asset_kind].[yaml|json]" + ); + } + + // Load the asset and produce a handle + let root_loc = AssetLocRef { + path: &meta.root, + pack: None, + }; + let handle = self + .load_asset(root_loc) + .map_err(|e| e.context(format!("Error loading core asset: {:?}", meta.root)))?; + + // Return the loaded asset pack. + Ok(AssetPack { + name: "Core".into(), + id: *CORE_PACK_ID, + version: self.game_version.clone(), + game_version: VersionReq { + comparators: [semver::Comparator { + op: semver::Op::Exact, + major: self.game_version.major, + minor: Some(self.game_version.minor), + patch: Some(self.game_version.patch), + pre: self.game_version.pre.clone(), + }] + .to_vec(), + }, + schemas: default(), + import_schemas: default(), + root: handle, + }) + } + + /// Load an asset. + pub fn load_asset(&mut self, loc: AssetLocRef) -> anyhow::Result { + self.impl_load_asset(loc, false) + } + + /// Like [`load_asset()`][Self::load_asset] but forces the asset to reload, even it if has + /// already been loaded. + pub fn load_asset_forced(&mut self, loc: AssetLocRef) -> anyhow::Result { + self.impl_load_asset(loc, true) + } + + fn impl_load_asset(&mut self, loc: AssetLocRef, force: bool) -> anyhow::Result { + let contents = self.io.load_file(loc).context(format!( + "Could not load asset file: {:?} from path {:?}", + loc.path, + loc.pack.unwrap_or("[core]") + ))?; + + let loc = AssetLoc { + path: normalize_path(loc.path), + pack: loc.pack.map(|x| x.to_owned()), + }; + + if !force { + if let Some(handle) = self.store.path_handles.get(&loc) { + return Ok(*handle); + } + } + + let loc = loc.as_ref(); + + // Try to load a metadata asset if it has a YAML/JSON extension, if that doesn't work, and + // it has a schema not found error, try to load a data asset for the same path, if that + // doesn't work and it is an extension not found error, return the metadata error message. + let partial = if path_is_metadata(loc.path) { + match self.load_metadata_asset(loc, &contents) { + Err(meta_err) => { + if meta_err.downcast_ref::().is_some() { + match self.load_data_asset(loc, &contents) { + Err(data_err) => { + if data_err.downcast_ref::().is_some() { + Err(meta_err) + } else { + Err(data_err) + } + } + ok => ok, + } + } else { + Err(meta_err) + } + } + ok => ok, + } + } else { + self.load_data_asset(loc, &contents) + }?; + + let loaded_asset = LoadedAsset { + cid: partial.cid, + pack_spec: loc.pack.map(|x| { + self.store + .pack_dirs + .get(x) + .expect("Pack dir not loaded properly") + .clone() + }), + loc: loc.to_owned(), + dependencies: partial.dependencies, + data: partial.data, + }; + + let mut reverse_deps = SmallVec::<[_; 16]>::new(); + let handle = *self + .store + .path_handles + .entry(loc.to_owned()) + // If we've already loaded this asset before + .and_modify(|handle| { + // Remove the old asset data + let cid = self.store.asset_ids.remove(handle).unwrap(); + let previous_asset = self.store.assets.remove(&cid).unwrap(); + + // Remove the previous asset's reverse dependencies + for dep in previous_asset.dependencies { + self.store + .reverse_dependencies + .get_mut(&dep) + .unwrap() + .remove(&cid); + } + + // Reload the assets that depended on the previous asset. + if let Some(rev_deps) = self.store.reverse_dependencies.get(&cid) { + for dep in rev_deps { + reverse_deps.push(self.store.assets.get(dep).unwrap().loc.clone()); + } + } + }) + // Otherwise, create a new handle + .or_insert(UntypedHandle { rid: Ulid::new() }); + + // Update reverse dependencies + for dep in &loaded_asset.dependencies { + self.store + .reverse_dependencies + .entry(*dep) + .or_default() + .insert(partial.cid); + } + + self.store.asset_ids.insert(handle, partial.cid); + self.store.assets.insert(partial.cid, loaded_asset); + + // Reload any assets that depended on this asset before it was reloaded + for loc in reverse_deps { + self.load_asset_forced(loc.as_ref())?; + } + + Ok(handle) + } + + fn load_metadata_asset( + &mut self, + loc: AssetLocRef, + contents: &[u8], + ) -> anyhow::Result { + // Get the schema for the asset + let filename = loc + .path + .file_name() + .ok_or_else(|| anyhow::format_err!("Invalid asset filename"))? + .to_str() + .ok_or_else(|| anyhow::format_err!("Invalid unicode in filename"))?; + let before_extension = filename.rsplit_once('.').unwrap().0; + let schema_name = before_extension + .rsplit_once('.') + .map(|x| x.1) + .unwrap_or(before_extension); + let schema = *self + .asset_types + .iter() + .find(|schema| { + let asset_kind = schema.type_data.get::().unwrap(); + match asset_kind { + AssetKind::Metadata { extension } => extension == schema_name, + _ => false, + } + }) + .ok_or_else(|| LoaderNotFound { + name: schema_name.into(), + })?; + let mut dependencies = Vec::new(); + + let mut cid = Cid::default(); + cid.update(contents); + let loader = MetaAssetLoadCtx { + server: self, + loc, + schema, + dependencies: &mut dependencies, + }; + let data = if loc.path.extension().unwrap().to_str().unwrap() == "json" { + let mut deserializer = serde_json::Deserializer::from_slice(contents); + loader.deserialize(&mut deserializer)? + } else { + let deserializer = serde_yaml::Deserializer::from_slice(contents); + loader.deserialize(deserializer)? + }; + + // Update the Cid + dependencies.sort(); + for dep in &dependencies { + cid.update(&dep.0); + } + + Ok(PartialAsset { + cid, + dependencies, + data, + }) + } + + fn load_data_asset( + &mut self, + loc: AssetLocRef, + contents: &[u8], + ) -> anyhow::Result { + // Get the schema for the asset + let filename = loc + .path + .file_name() + .ok_or_else(|| anyhow::format_err!("Invalid asset filename"))? + .to_str() + .ok_or_else(|| anyhow::format_err!("Invalid unicode in filename"))?; + let (_name, extension) = filename.split_once('.').unwrap(); + let loader = self + .asset_types + .iter() + .find_map(|schema| { + let asset_kind = schema.type_data.get::().unwrap(); + match &asset_kind { + AssetKind::Custom { extensions, loader } => { + if extensions + .iter() + .any(|ext| ext == extension || ext == filename) + { + Some(loader) + } else { + None + } + } + _ => None, + } + }) + .ok_or_else(|| LoaderNotFound { + name: extension.into(), + })?; + + let mut dependencies = Vec::new(); + let mut cid = Cid::default(); + cid.update(contents); + + let ctx = AssetLoadCtx { + asset_server: self, + loc, + dependencies: &mut dependencies, + }; + let sbox = loader.load(ctx, contents)?; + + Ok(PartialAsset { + cid, + data: sbox, + dependencies, + }) + } + + /// Borrow a [`LoadedAsset`] associated to the given handle. + pub fn get_untyped(&self, handle: UntypedHandle) -> Option<&LoadedAsset> { + let cid = self.store.asset_ids.get(&handle)?; + self.store.assets.get(cid) + } + + /// Borrow a [`LoadedAsset`] associated to the given handle. + pub fn get_untyped_mut(&mut self, handle: UntypedHandle) -> Option<&mut LoadedAsset> { + let cid = self.store.asset_ids.get(&handle)?; + self.store.assets.get_mut(cid) + } + + /// Read the core asset pack. + /// + /// # Panics + /// + /// Panics if the assets have not be loaded yet with [`AssetServer::load_assets`]. + #[track_caller] + pub fn core(&self) -> &AssetPack { + self.store.core_pack.as_ref().unwrap() + } + + /// Get the core asset pack's root asset. + pub fn root(&self) -> &T { + self.get(self.core().root.typed()) + } + + /// Read the loaded asset packs. + pub fn packs(&self) -> &HashMap { + &self.store.packs + } + + /// Borrow a loaded asset. + /// + /// # Panics + /// + /// Panics if the asset is not loaded or if the asset asset with the given handle doesn't have a + /// schema matching `T`. + #[track_caller] + pub fn get(&self, handle: Handle) -> &T { + let cid = self + .store + .asset_ids + .get(&handle.untyped()) + .expect(NO_ASSET_MSG); + self.store.assets.get(cid).expect(NO_ASSET_MSG).cast_ref() + } + + /// Mutably borrow a loaded asset. + /// + /// # Panics + /// + /// Panics if the asset is not loaded or if the asset asset with the given handle doesn't have a + /// schema matching `T`. + #[track_caller] + pub fn get_mut(&mut self, handle: &Handle) -> &mut T { + let cid = self + .store + .asset_ids + .get(&handle.untyped()) + .expect(NO_ASSET_MSG); + self.store + .assets + .get_mut(cid) + .expect(NO_ASSET_MSG) + .cast_mut() + } +} + +/// Partial of a [`LoadedAsset`] used internally while loading is in progress. +struct PartialAsset { + pub cid: Cid, + pub data: SchemaBox, + pub dependencies: Vec, +} + +const NO_ASSET_MSG: &str = "Asset not loaded"; +fn path_is_metadata(path: &Path) -> bool { + let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else { + return false; + }; + ext == "yaml" || ext == "yml" || ext == "json" +} + +use metadata::*; +mod metadata { + use serde::de::{DeserializeSeed, Error, Visitor}; + + use super::*; + + pub struct MetaAssetLoadCtx<'srv> { + pub server: &'srv mut AssetServer, + pub dependencies: &'srv mut Vec, + pub loc: AssetLocRef<'srv>, + pub schema: &'static Schema, + } + + impl<'asset, 'de> DeserializeSeed<'de> for MetaAssetLoadCtx<'asset> { + type Value = SchemaBox; + + fn deserialize(mut self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if self.schema.has_opaque() { + return Err(D::Error::custom( + "Cannot deserialize schemas containing opaque types.", + )); + } + + // Allocate the object. + let mut ptr = SchemaBox::default(self.schema); + + SchemaPtrLoadCtx { + ctx: &mut self, + ptr: ptr.as_mut(), + } + .deserialize(deserializer)?; + + Ok(ptr) + } + } + + struct SchemaPtrLoadCtx<'a, 'srv, 'ptr, 'prnt> { + ctx: &'a mut MetaAssetLoadCtx<'srv>, + ptr: SchemaRefMut<'ptr, 'prnt>, + } + + impl<'a, 'srv, 'ptr, 'prnt, 'de> DeserializeSeed<'de> for SchemaPtrLoadCtx<'a, 'srv, 'ptr, 'prnt> { + type Value = (); + + fn deserialize(mut self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Load asset handles. + if self + .ptr + .schema() + .type_data + .get::() + .is_some() + { + let relative_path = PathBuf::from(String::deserialize(deserializer)?); + let path = normalize_path_relative_to(&relative_path, self.ctx.loc.path); + let handle = self + .ctx + .server + .load_asset((path.as_path(), self.ctx.loc.pack).into()) + .map_err(|e| D::Error::custom(e.to_string()))?; + self.ctx + .dependencies + .push(*self.ctx.server.store.asset_ids.get(&handle).unwrap()); + *self + .ptr + .try_cast_mut() + .map_err(|e| D::Error::custom(e.to_string()))? = handle; + return Ok(()); + } + + match &self.ptr.schema().kind { + SchemaKind::Struct(_) => deserializer.deserialize_any(StructVisitor { + ptr: self.ptr, + ctx: self.ctx, + })?, + SchemaKind::Vec(_) => deserializer.deserialize_seq(VecVisitor { + ptr: self.ptr, + ctx: self.ctx, + })?, + SchemaKind::Map { .. } => deserializer.deserialize_map(MapVisitor { + ptr: self.ptr, + ctx: self.ctx, + })?, + SchemaKind::Box(_) => { + // SOUND: schema asserts pointer is a SchemaBox. + let b = unsafe { self.ptr.deref_mut::() }; + SchemaPtrLoadCtx { + ctx: self.ctx, + ptr: b.as_mut(), + } + .deserialize(deserializer)? + } + SchemaKind::Primitive(p) => { + match p { + Primitive::Bool => *self.ptr.cast_mut() = bool::deserialize(deserializer)?, + Primitive::U8 => *self.ptr.cast_mut() = u8::deserialize(deserializer)?, + Primitive::U16 => *self.ptr.cast_mut() = u16::deserialize(deserializer)?, + Primitive::U32 => *self.ptr.cast_mut() = u32::deserialize(deserializer)?, + Primitive::U64 => *self.ptr.cast_mut() = u64::deserialize(deserializer)?, + Primitive::U128 => *self.ptr.cast_mut() = u128::deserialize(deserializer)?, + Primitive::I8 => *self.ptr.cast_mut() = i8::deserialize(deserializer)?, + Primitive::I16 => *self.ptr.cast_mut() = i16::deserialize(deserializer)?, + Primitive::I32 => *self.ptr.cast_mut() = i32::deserialize(deserializer)?, + Primitive::I64 => *self.ptr.cast_mut() = i64::deserialize(deserializer)?, + Primitive::I128 => *self.ptr.cast_mut() = i128::deserialize(deserializer)?, + Primitive::F32 => *self.ptr.cast_mut() = f32::deserialize(deserializer)?, + Primitive::F64 => *self.ptr.cast_mut() = f64::deserialize(deserializer)?, + Primitive::String => { + *self.ptr.cast_mut() = String::deserialize(deserializer)? + } + Primitive::Opaque { .. } => panic!( + "Cannot deserialize opaque types from metadata files.\ + This error should have been handled above" + ), + }; + } + }; + + Ok(()) + } + } + + struct StructVisitor<'a, 'srv, 'ptr, 'prnt> { + ctx: &'a mut MetaAssetLoadCtx<'srv>, + ptr: SchemaRefMut<'ptr, 'prnt>, + } + + impl<'a, 'srv, 'ptr, 'prnt, 'de> Visitor<'de> for StructVisitor<'a, 'srv, 'ptr, 'prnt> { + type Value = (); + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + // TODO: write a really nice error message for this. + write!( + formatter, + "asset metadata matching the schema: {:#?}", + self.ptr.schema() + ) + } + + fn visit_seq(mut self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let field_count = self.ptr.schema().kind.as_struct().unwrap().fields.len(); + + for i in 0..field_count { + let field = self.ptr.get_field(i).unwrap(); + if seq + .next_element_seed(SchemaPtrLoadCtx { + ctx: self.ctx, + ptr: field, + })? + .is_none() + { + break; + } + } + + Ok(()) + } + + fn visit_map(mut self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + while let Some(key) = map.next_key::()? { + match self.ptr.get_field(&key) { + Ok(field) => { + map.next_value_seed(SchemaPtrLoadCtx { + ctx: self.ctx, + ptr: field, + })?; + } + Err(_) => { + let fields = &self.ptr.schema().kind.as_struct().unwrap().fields; + let mut msg = format!("unknown field `{key}`, "); + if !fields.is_empty() { + msg += "expected one of "; + for (i, field) in fields.iter().enumerate() { + msg += &field + .name + .as_ref() + .map(|x| format!("`{x}`")) + .unwrap_or_else(|| format!("`{i}`")); + if i < fields.len() - 1 { + msg += ", " + } + } + } else { + msg += "there are no fields" + } + return Err(A::Error::custom(msg)); + } + } + } + + Ok(()) + } + } + + struct VecVisitor<'a, 'srv, 'ptr, 'prnt> { + ctx: &'a mut MetaAssetLoadCtx<'srv>, + ptr: SchemaRefMut<'ptr, 'prnt>, + } + + impl<'a, 'srv, 'ptr, 'prnt, 'de> Visitor<'de> for VecVisitor<'a, 'srv, 'ptr, 'prnt> { + type Value = (); + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + // TODO: write a really nice error message for this. + write!( + formatter, + "asset metadata matching the schema: {:#?}", + self.ptr.schema() + ) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + // SOUND: schema asserts this is a SchemaVec. + let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaVec) }; + loop { + let item_schema = v.schema(); + let mut item = SchemaBox::default(item_schema); + let item_ref = item.as_mut(); + if seq + .next_element_seed(SchemaPtrLoadCtx { + ctx: self.ctx, + ptr: item_ref, + })? + .is_none() + { + break; + } + v.push_box(item); + } + + Ok(()) + } + } + + struct MapVisitor<'a, 'srv, 'ptr, 'prnt> { + ctx: &'a mut MetaAssetLoadCtx<'srv>, + ptr: SchemaRefMut<'ptr, 'prnt>, + } + + impl<'a, 'srv, 'ptr, 'prnt, 'de> Visitor<'de> for MapVisitor<'a, 'srv, 'ptr, 'prnt> { + type Value = (); + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + // TODO: write a really nice error message for this. + write!( + formatter, + "asset metadata matching the schema: {:#?}", + self.ptr.schema() + ) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + // SOUND: schema asserts this is a SchemaMap. + let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaMap) }; + if v.key_schema() != String::schema() { + return Err(A::Error::custom( + "Can only deserialize maps with string keys.", + )); + } + while let Some(key) = map.next_key::()? { + let key = SchemaBox::new(key); + let mut value = SchemaBox::default(v.value_schema()); + map.next_value_seed(SchemaPtrLoadCtx { + ctx: self.ctx, + ptr: value.as_mut(), + })?; + + v.insert_box(key, value); + } + Ok(()) + } + } +} diff --git a/crates/bones_bevy_renderer/CHANGELOG.md b/framework_crates/bones_bevy_renderer/CHANGELOG.md similarity index 100% rename from crates/bones_bevy_renderer/CHANGELOG.md rename to framework_crates/bones_bevy_renderer/CHANGELOG.md diff --git a/framework_crates/bones_bevy_renderer/Cargo.toml b/framework_crates/bones_bevy_renderer/Cargo.toml new file mode 100644 index 0000000000..a50bfa9143 --- /dev/null +++ b/framework_crates/bones_bevy_renderer/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "bones_bevy_renderer" +description = "Bevy rendering implementation for the bones_framework." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[features] +default = ["webgl2"] +webgl2 = ["bevy/webgl2"] + +[dependencies] +bones_framework = { version = "0.3", path = "../bones_framework" } + +bevy_egui = "0.21" +glam = { version = "0.24", features = ["serde"] } +bevy_prototype_lyon = "0.9" + +[dependencies.bevy] +default-features = false +features = ["bevy_render", "bevy_core_pipeline", "bevy_sprite", "x11"] +version = "0.11" diff --git a/framework_crates/bones_bevy_renderer/src/convert.rs b/framework_crates/bones_bevy_renderer/src/convert.rs new file mode 100644 index 0000000000..8d7b3bce8a --- /dev/null +++ b/framework_crates/bones_bevy_renderer/src/convert.rs @@ -0,0 +1,243 @@ +use bevy::{ + input::{mouse::MouseScrollUnit, ButtonState}, + prelude::*, + render::camera::Viewport, +}; +use bones_framework::prelude as bones; + +pub trait IntoBevy { + fn into_bevy(self) -> T; +} +pub trait IntoBones { + fn into_bones(self) -> T; +} + +impl IntoBevy for bones::Viewport { + fn into_bevy(self) -> Viewport { + Viewport { + physical_position: self.position, + physical_size: self.size, + depth: self.depth_min..self.depth_max, + } + } +} + +impl IntoBevy for bones::Color { + fn into_bevy(self) -> Color { + Color::Rgba { + red: self.r(), + green: self.g(), + blue: self.b(), + alpha: self.a(), + } + } +} + +impl IntoBevy for bones::Transform { + fn into_bevy(self) -> Transform { + Transform { + translation: self.translation, + rotation: self.rotation, + scale: self.scale, + } + } +} + +impl IntoBones for MouseScrollUnit { + fn into_bones(self) -> bones::MouseScrollUnit { + match self { + MouseScrollUnit::Line => bones::MouseScrollUnit::Lines, + MouseScrollUnit::Pixel => bones::MouseScrollUnit::Pixels, + } + } +} + +impl IntoBones for ButtonState { + fn into_bones(self) -> bones::ButtonState { + match self { + ButtonState::Pressed => bones::ButtonState::Pressed, + ButtonState::Released => bones::ButtonState::Released, + } + } +} + +impl IntoBones for MouseButton { + fn into_bones(self) -> bones::MouseButton { + match self { + MouseButton::Left => bones::MouseButton::Left, + MouseButton::Right => bones::MouseButton::Right, + MouseButton::Middle => bones::MouseButton::Middle, + MouseButton::Other(i) => bones::MouseButton::Other(i), + } + } +} + +impl IntoBones for KeyCode { + fn into_bones(self) -> bones::KeyCode { + match self { + KeyCode::Key1 => bones::KeyCode::Key1, + KeyCode::Key2 => bones::KeyCode::Key2, + KeyCode::Key3 => bones::KeyCode::Key3, + KeyCode::Key4 => bones::KeyCode::Key4, + KeyCode::Key5 => bones::KeyCode::Key5, + KeyCode::Key6 => bones::KeyCode::Key6, + KeyCode::Key7 => bones::KeyCode::Key7, + KeyCode::Key8 => bones::KeyCode::Key8, + KeyCode::Key9 => bones::KeyCode::Key9, + KeyCode::Key0 => bones::KeyCode::Key0, + KeyCode::A => bones::KeyCode::A, + KeyCode::B => bones::KeyCode::B, + KeyCode::C => bones::KeyCode::C, + KeyCode::D => bones::KeyCode::D, + KeyCode::E => bones::KeyCode::E, + KeyCode::F => bones::KeyCode::F, + KeyCode::G => bones::KeyCode::G, + KeyCode::H => bones::KeyCode::H, + KeyCode::I => bones::KeyCode::I, + KeyCode::J => bones::KeyCode::J, + KeyCode::K => bones::KeyCode::K, + KeyCode::L => bones::KeyCode::L, + KeyCode::M => bones::KeyCode::M, + KeyCode::N => bones::KeyCode::N, + KeyCode::O => bones::KeyCode::O, + KeyCode::P => bones::KeyCode::P, + KeyCode::Q => bones::KeyCode::Q, + KeyCode::R => bones::KeyCode::R, + KeyCode::S => bones::KeyCode::S, + KeyCode::T => bones::KeyCode::T, + KeyCode::U => bones::KeyCode::U, + KeyCode::V => bones::KeyCode::V, + KeyCode::W => bones::KeyCode::W, + KeyCode::X => bones::KeyCode::X, + KeyCode::Y => bones::KeyCode::Y, + KeyCode::Z => bones::KeyCode::Z, + KeyCode::Escape => bones::KeyCode::Escape, + KeyCode::F1 => bones::KeyCode::F1, + KeyCode::F2 => bones::KeyCode::F2, + KeyCode::F3 => bones::KeyCode::F3, + KeyCode::F4 => bones::KeyCode::F4, + KeyCode::F5 => bones::KeyCode::F5, + KeyCode::F6 => bones::KeyCode::F6, + KeyCode::F7 => bones::KeyCode::F7, + KeyCode::F8 => bones::KeyCode::F8, + KeyCode::F9 => bones::KeyCode::F9, + KeyCode::F10 => bones::KeyCode::F10, + KeyCode::F11 => bones::KeyCode::F11, + KeyCode::F12 => bones::KeyCode::F12, + KeyCode::F13 => bones::KeyCode::F13, + KeyCode::F14 => bones::KeyCode::F14, + KeyCode::F15 => bones::KeyCode::F15, + KeyCode::F16 => bones::KeyCode::F16, + KeyCode::F17 => bones::KeyCode::F17, + KeyCode::F18 => bones::KeyCode::F18, + KeyCode::F19 => bones::KeyCode::F19, + KeyCode::F20 => bones::KeyCode::F20, + KeyCode::F21 => bones::KeyCode::F21, + KeyCode::F22 => bones::KeyCode::F22, + KeyCode::F23 => bones::KeyCode::F23, + KeyCode::F24 => bones::KeyCode::F24, + KeyCode::Snapshot => bones::KeyCode::Snapshot, + KeyCode::Scroll => bones::KeyCode::Scroll, + KeyCode::Pause => bones::KeyCode::Pause, + KeyCode::Insert => bones::KeyCode::Insert, + KeyCode::Home => bones::KeyCode::Home, + KeyCode::Delete => bones::KeyCode::Delete, + KeyCode::End => bones::KeyCode::End, + KeyCode::PageDown => bones::KeyCode::PageDown, + KeyCode::PageUp => bones::KeyCode::PageUp, + KeyCode::Left => bones::KeyCode::Left, + KeyCode::Up => bones::KeyCode::Up, + KeyCode::Right => bones::KeyCode::Right, + KeyCode::Down => bones::KeyCode::Down, + KeyCode::Back => bones::KeyCode::Back, + KeyCode::Return => bones::KeyCode::Return, + KeyCode::Space => bones::KeyCode::Space, + KeyCode::Compose => bones::KeyCode::Compose, + KeyCode::Caret => bones::KeyCode::Caret, + KeyCode::Numlock => bones::KeyCode::Numlock, + KeyCode::Numpad0 => bones::KeyCode::Numpad0, + KeyCode::Numpad1 => bones::KeyCode::Numpad1, + KeyCode::Numpad2 => bones::KeyCode::Numpad2, + KeyCode::Numpad3 => bones::KeyCode::Numpad3, + KeyCode::Numpad4 => bones::KeyCode::Numpad4, + KeyCode::Numpad5 => bones::KeyCode::Numpad5, + KeyCode::Numpad6 => bones::KeyCode::Numpad6, + KeyCode::Numpad7 => bones::KeyCode::Numpad7, + KeyCode::Numpad8 => bones::KeyCode::Numpad8, + KeyCode::Numpad9 => bones::KeyCode::Numpad9, + KeyCode::AbntC1 => bones::KeyCode::AbntC1, + KeyCode::AbntC2 => bones::KeyCode::AbntC2, + KeyCode::NumpadAdd => bones::KeyCode::NumpadAdd, + KeyCode::Apostrophe => bones::KeyCode::Apostrophe, + KeyCode::Apps => bones::KeyCode::Apps, + KeyCode::Asterisk => bones::KeyCode::Asterisk, + KeyCode::Plus => bones::KeyCode::Plus, + KeyCode::At => bones::KeyCode::At, + KeyCode::Ax => bones::KeyCode::Ax, + KeyCode::Backslash => bones::KeyCode::Backslash, + KeyCode::Calculator => bones::KeyCode::Calculator, + KeyCode::Capital => bones::KeyCode::Capital, + KeyCode::Colon => bones::KeyCode::Colon, + KeyCode::Comma => bones::KeyCode::Comma, + KeyCode::Convert => bones::KeyCode::Convert, + KeyCode::NumpadDecimal => bones::KeyCode::NumpadDecimal, + KeyCode::NumpadDivide => bones::KeyCode::NumpadDivide, + KeyCode::Equals => bones::KeyCode::Equals, + KeyCode::Grave => bones::KeyCode::Grave, + KeyCode::Kana => bones::KeyCode::Kana, + KeyCode::Kanji => bones::KeyCode::Kanji, + KeyCode::AltLeft => bones::KeyCode::AltLeft, + KeyCode::BracketLeft => bones::KeyCode::BracketLeft, + KeyCode::ControlLeft => bones::KeyCode::ControlLeft, + KeyCode::ShiftLeft => bones::KeyCode::ShiftLeft, + KeyCode::SuperLeft => bones::KeyCode::SuperLeft, + KeyCode::Mail => bones::KeyCode::Mail, + KeyCode::MediaSelect => bones::KeyCode::MediaSelect, + KeyCode::MediaStop => bones::KeyCode::MediaStop, + KeyCode::Minus => bones::KeyCode::Minus, + KeyCode::NumpadMultiply => bones::KeyCode::NumpadMultiply, + KeyCode::Mute => bones::KeyCode::Mute, + KeyCode::MyComputer => bones::KeyCode::MyComputer, + KeyCode::NavigateForward => bones::KeyCode::NavigateForward, + KeyCode::NavigateBackward => bones::KeyCode::NavigateBackward, + KeyCode::NextTrack => bones::KeyCode::NextTrack, + KeyCode::NoConvert => bones::KeyCode::NoConvert, + KeyCode::NumpadComma => bones::KeyCode::NumpadComma, + KeyCode::NumpadEnter => bones::KeyCode::NumpadEnter, + KeyCode::NumpadEquals => bones::KeyCode::NumpadEquals, + KeyCode::Oem102 => bones::KeyCode::Oem102, + KeyCode::Period => bones::KeyCode::Period, + KeyCode::PlayPause => bones::KeyCode::PlayPause, + KeyCode::Power => bones::KeyCode::Power, + KeyCode::PrevTrack => bones::KeyCode::PrevTrack, + KeyCode::AltRight => bones::KeyCode::AltRight, + KeyCode::BracketRight => bones::KeyCode::BracketRight, + KeyCode::ControlRight => bones::KeyCode::ControlRight, + KeyCode::ShiftRight => bones::KeyCode::ShiftRight, + KeyCode::SuperRight => bones::KeyCode::SuperRight, + KeyCode::Semicolon => bones::KeyCode::Semicolon, + KeyCode::Slash => bones::KeyCode::Slash, + KeyCode::Sleep => bones::KeyCode::Sleep, + KeyCode::Stop => bones::KeyCode::Stop, + KeyCode::NumpadSubtract => bones::KeyCode::NumpadSubtract, + KeyCode::Sysrq => bones::KeyCode::Sysrq, + KeyCode::Tab => bones::KeyCode::Tab, + KeyCode::Underline => bones::KeyCode::Underline, + KeyCode::Unlabeled => bones::KeyCode::Unlabeled, + KeyCode::VolumeDown => bones::KeyCode::VolumeDown, + KeyCode::VolumeUp => bones::KeyCode::VolumeUp, + KeyCode::Wake => bones::KeyCode::Wake, + KeyCode::WebBack => bones::KeyCode::WebBack, + KeyCode::WebFavorites => bones::KeyCode::WebFavorites, + KeyCode::WebForward => bones::KeyCode::WebForward, + KeyCode::WebHome => bones::KeyCode::WebHome, + KeyCode::WebRefresh => bones::KeyCode::WebRefresh, + KeyCode::WebSearch => bones::KeyCode::WebSearch, + KeyCode::WebStop => bones::KeyCode::WebStop, + KeyCode::Yen => bones::KeyCode::Yen, + KeyCode::Copy => bones::KeyCode::Copy, + KeyCode::Paste => bones::KeyCode::Paste, + KeyCode::Cut => bones::KeyCode::Cut, + } + } +} diff --git a/framework_crates/bones_bevy_renderer/src/lib.rs b/framework_crates/bones_bevy_renderer/src/lib.rs new file mode 100644 index 0000000000..e29c2eaca5 --- /dev/null +++ b/framework_crates/bones_bevy_renderer/src/lib.rs @@ -0,0 +1,708 @@ +//! Bevy plugin for rendering Bones framework games. + +#![warn(missing_docs)] +// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable +#![cfg_attr(doc, allow(unknown_lints))] +#![deny(rustdoc::all)] + +use std::path::PathBuf; + +pub use bevy; + +use bevy::{ + input::{ + keyboard::KeyboardInput, + mouse::{MouseButtonInput, MouseMotion, MouseWheel}, + }, + prelude::*, + render::{camera::ScalingMode, Extract, RenderApp}, + sprite::{extract_sprites, ExtractedSprite, ExtractedSprites, SpriteSystem}, + utils::HashMap, +}; +use bevy_egui::EguiContext; +use glam::*; + +use bevy_prototype_lyon::prelude as lyon; +use bones_framework::prelude as bones; +use prelude::convert::{IntoBevy, IntoBones}; + +/// The prelude +pub mod prelude { + pub use crate::*; +} + +mod convert; + +/// Marker component for entities that are rendered in Bevy for bones. +#[derive(Component)] +pub struct BevyBonesEntity; + +/// Renderer for [`bones_framework`] [`Game`][bones::Game]s using Bevy. +#[derive(Resource)] +pub struct BonesBevyRenderer { + /// Whether or not to use nearest-neighbor sampling for textures. + pub pixel_art: bool, + /// The bones game to run. + pub game: bones::Game, + /// The version of the game, used for the asset loader. + pub game_version: bones::Version, + /// The path to load assets from. + pub asset_dir: PathBuf, + /// The path to load asset packs from. + pub packs_dir: PathBuf, +} + +/// Resource containing the entity spawned for all of the bones game renderables. +#[derive(Resource)] +pub struct BonesGameEntity(pub Entity); +impl FromWorld for BonesGameEntity { + fn from_world(world: &mut World) -> Self { + Self(world.spawn(VisibilityBundle::default()).id()) + } +} + +/// Resource mapping bones image IDs to their bevy handles. +#[derive(Resource, Default, Debug, Deref, DerefMut)] +pub struct BonesImageIds { + #[deref] + map: HashMap>, + next_id: u32, +} + +impl BonesImageIds { + /// Load all bones images into bevy. + pub fn load_bones_images( + &mut self, + bones_assets: &mut bones::AssetServer, + bevy_images: &mut Assets, + ) { + for asset in bones_assets.store.assets.values_mut() { + if let Ok(image) = asset.data.try_cast_mut::() { + self.load_bones_image(image, bevy_images) + } + } + } + + /// Load a bones image into bevy. + pub fn load_bones_image(&mut self, image: &mut bones::Image, bevy_images: &mut Assets) { + let Self { map, next_id } = self; + let mut taken_image = bones::Image::External(0); // Dummy value temporarily + std::mem::swap(image, &mut taken_image); + if let bones::Image::Data(data) = taken_image { + let handle = bevy_images.add(Image::from_dynamic(data, true)); + map.insert(*next_id, handle); + *image = bones::Image::External(*next_id); + *next_id += 1; + } + } +} + +/// Bevy resource that contains the info for the bones game that is being rendered. +#[derive(Resource)] +pub struct BonesData { + /// The bones game. + pub game: bones::Game, + /// The bones asset server cell. + pub asset_server: bones::AtomicResource, +} + +impl BonesBevyRenderer { + // TODO: Create a better builder pattern struct for `BonesBevyRenderer`. + /// Create a new [`BonesBevyRenderer`] for the provided game. + pub fn new(game: bones::Game) -> Self { + BonesBevyRenderer { + pixel_art: true, + game, + game_version: bones::Version::new(0, 1, 0), + asset_dir: PathBuf::from("assets"), + packs_dir: PathBuf::from("packs"), + } + } + + /// Return a bevy [`App`] configured to run the bones game. + pub fn app(self) -> App { + let mut app = App::new(); + + // Initialize Bevy plugins we use + let mut plugins = DefaultPlugins.build(); + if self.pixel_art { + plugins = plugins.set(ImagePlugin::default_nearest()); + } + + app.add_plugins(plugins) + .add_plugins((bevy_egui::EguiPlugin, lyon::ShapePlugin)) + .insert_resource({ + let mut egui_settings = bevy_egui::EguiSettings::default(); + if self.pixel_art { + egui_settings.use_nearest_descriptor(); + } + egui_settings + }) + .init_resource::(); + + { + let mut bones_image_ids = BonesImageIds::default(); + let mut asset_server = self.game.asset_server(); + let mut bevy_images = app.world.resource_mut::>(); + + if !asset_server.asset_types.is_empty() { + #[cfg(not(target_arch = "wasm32"))] + { + // Configure the AssetIO + let io = bones::FileAssetIo::new(&self.asset_dir, &self.packs_dir, true); + asset_server.set_io(io); + + // Load the game assets + asset_server + .load_assets() + .expect("Could not load game assets"); + + // Take all loaded image assets and conver them to external images that reference bevy handles + bones_image_ids.load_bones_images(&mut asset_server, &mut bevy_images); + } + #[cfg(target_arch = "wasm32")] + { + bones_image_ids.load_bones_images(&mut asset_server, &mut bevy_images); + // TODO: Implement WASM asset loader. + unimplemented!("WASM asset loading is not implemented yet."); + } + } + + app.insert_resource(bones_image_ids); + } + + // Insert the bones data + app.insert_resource(BonesData { + asset_server: self.game.asset_server.clone_cell(), + game: self.game, + }) + .init_resource::(); + + // Add the world sync systems + app.add_systems( + Update, + ( + // Collect input and run world simulation + get_bones_input.pipe(step_bones_game), + // Synchronize bones render components with the Bevy world. + ( + sync_egui_settings, + sync_clear_color, + sync_cameras, + sync_bones_path2ds, + ), + ) + .chain(), + ); + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems( + ExtractSchedule, + (extract_bones_sprites, extract_bones_tilemaps) + .in_set(SpriteSystem::ExtractSprites) + .after(extract_sprites), + ); + } + + app + } +} + +fn get_bones_input( + mut mouse_button_input_events: EventReader, + mut mouse_motion_events: EventReader, + mut mouse_wheel_events: EventReader, + mut keyboard_events: EventReader, +) -> (bones::MouseInputs, bones::KeyboardInputs) { + // TODO: investigate possible ways to avoid allocating vectors every frame for event lists. + ( + bones::MouseInputs { + movement: mouse_motion_events + .iter() + .last() + .map(|x| x.delta) + .unwrap_or_default(), + wheel_events: mouse_wheel_events + .iter() + .map(|event| bones::MouseScrollInput { + unit: event.unit.into_bones(), + movement: Vec2::new(event.x, event.y), + }) + .collect(), + button_events: mouse_button_input_events + .iter() + .map(|event| bones::MouseButtonInput { + button: event.button.into_bones(), + state: event.state.into_bones(), + }) + .collect(), + }, + bones::KeyboardInputs { + keys: keyboard_events + .iter() + .map(|event| bones::KeyboardInput { + scan_code: event.scan_code, + key_code: event.key_code.map(|x| x.into_bones()), + button_state: event.state.into_bones(), + }) + .collect(), + }, + ) +} + +/// System to step the bones simulation. +fn step_bones_game( + In((mouse_inputs, keyboard_inputs)): In<(bones::MouseInputs, bones::KeyboardInputs)>, + world: &mut World, +) { + world.resource_scope(|world: &mut World, mut data: Mut| { + world.resource_scope( + |world: &mut World, mut bones_image_ids: Mut| { + world.resource_scope(|world: &mut World, mut bevy_images: Mut>| { + let egui_ctx = { + let mut egui_query = + world.query_filtered::<&mut EguiContext, With>(); + let mut egui_ctx = egui_query.get_single_mut(world).unwrap(); + egui_ctx.get_mut().clone() + }; + let BonesData { game, asset_server } = &mut *data; + let bevy_time = world.resource::::default(); + storage.insert(e1, A("hello".into())); + storage.insert(e2, A("world".into())); + assert!(storage.get(e1).is_some()); + storage.remove(e1); + assert!(storage.get(e1).is_none()); + assert_eq!( + storage.iter().cloned().collect::>(), + vec![A("world".into())] + ) + } +} diff --git a/framework_crates/bones_ecs/src/components/untyped.rs b/framework_crates/bones_ecs/src/components/untyped.rs new file mode 100644 index 0000000000..6bd3bac945 --- /dev/null +++ b/framework_crates/bones_ecs/src/components/untyped.rs @@ -0,0 +1,593 @@ +use crate::prelude::*; + +use bones_schema::alloc::ResizableAlloc; +use std::{ + mem::MaybeUninit, + ptr::{self}, + rc::Rc, +}; + +/// Holds components of a given type indexed by `Entity`. +/// +/// We do not check if the given entity is alive here, this should be done using `Entities`. +pub struct UntypedComponentStore { + pub(crate) bitset: BitSetVec, + pub(crate) storage: ResizableAlloc, + pub(crate) max_id: usize, + pub(crate) schema: &'static Schema, +} + +unsafe impl Sync for UntypedComponentStore {} +unsafe impl Send for UntypedComponentStore {} + +impl Clone for UntypedComponentStore { + fn clone(&self) -> Self { + let size = self.schema.layout().size(); + let mut new_storage = self.storage.clone(); + + for i in 0..self.max_id { + if self.bitset.bit_test(i) { + // SAFE: constructing an UntypedComponent store is unsafe, and the user affirms that + // clone_fn will not do anything unsound. + // + // - And our previous pointer is a valid pointer to component data + // - And our new pointer is a writable pointer with the same layout + unsafe { + let prev_ptr = self.storage.ptr().byte_add(i * size); + let new_ptr = new_storage.ptr_mut().byte_add(i * size); + (self.schema.clone_fn.expect("Cannot clone component"))( + prev_ptr.as_ptr(), + new_ptr.as_ptr(), + ); + } + } + } + + Self { + bitset: self.bitset.clone(), + storage: new_storage, + max_id: self.max_id, + schema: self.schema, + } + } +} + +impl Drop for UntypedComponentStore { + fn drop(&mut self) { + if let Some(drop_fn) = self.schema.drop_fn { + for i in 0..self.storage.capacity() { + if self.bitset.bit_test(i) { + // SAFE: constructing an UntypedComponent store is unsafe, and the user affirms + // that drop_fn will not do anything unsound. + // + // And our pointer is valid. + unsafe { + let ptr = self.storage.unchecked_idx_mut(i); + drop_fn(ptr.as_ptr()); + } + } + } + } + } +} + +impl UntypedComponentStore { + /// Create a arbitrary [`UntypedComponentStore`]. + /// + /// In Rust, you will usually not use [`UntypedComponentStore`] and will use the statically + /// typed [`ComponentStore`] instead. + /// + /// # Safety + /// + /// The `clone_fn` and `drop_fn`, if specified, must not do anything unsound, when given valid + /// pointers to clone or drop. + pub unsafe fn new(schema: &'static Schema) -> Self { + Self { + bitset: create_bitset(), + storage: ResizableAlloc::new(schema.layout()), + max_id: 0, + schema, + } + } + + /// Create an [`UntypedComponentStore`] that is valid for the given type `T`. + pub fn for_type() -> Self { + Self { + bitset: create_bitset(), + storage: ResizableAlloc::new(T::schema().layout()), + max_id: 0, + schema: T::schema(), + } + } + + /// Get the schema of the components stored. + pub fn schema(&self) -> &'static Schema { + self.schema + } + + /// Insert component data for the given entity and get the previous component data if present. + /// # Panics + /// Panics if the schema of `T` doesn't match the store. + #[inline] + #[track_caller] + pub fn insert_box(&mut self, entity: Entity, data: SchemaBox) -> Option { + self.try_insert_box(entity, data).unwrap() + } + + /// Insert component data for the given entity and get the previous component data if present. + /// # Errors + /// Errors if the schema of `T` doesn't match the store. + pub fn try_insert_box( + &mut self, + entity: Entity, + mut data: SchemaBox, + ) -> Result, SchemaMismatchError> { + if self.schema != data.schema() { + Err(SchemaMismatchError) + } else { + let ptr = data.as_mut().as_ptr(); + // SOUND: we validated schema matches + let already_had_component = unsafe { self.insert_raw(entity, ptr) }; + if already_had_component { + // Previous component data will be written to data pointer + Ok(Some(data)) + } else { + // Don't run the data's destructor, it has been moved into the storage. + std::mem::forget(data); + Ok(None) + } + } + } + + /// Insert component data for the given entity and get the previous component data if present. + /// # Panics + /// Panics if the schema of `T` doesn't match the store. + #[inline] + #[track_caller] + pub fn insert(&mut self, entity: Entity, data: T) -> Option { + self.try_insert(entity, data).unwrap() + } + + /// Insert component data for the given entity and get the previous component data if present. + /// # Errors + /// Errors if the schema of `T` doesn't match the store. + pub fn try_insert( + &mut self, + entity: Entity, + mut data: T, + ) -> Result, SchemaMismatchError> { + if self.schema != T::schema() { + Err(SchemaMismatchError) + } else { + let ptr = &mut data as *mut T as *mut u8; + // SOUND: we validated schema matches + let already_had_component = unsafe { self.insert_raw(entity, ptr) }; + if already_had_component { + // Previous component data will be written to data pointer + Ok(Some(data)) + } else { + // Don't run the data's destructor, it has been moved into the storage. + std::mem::forget(data); + Ok(None) + } + } + } + + /// Returns true if the entity already had a component of this type. + /// + /// If true is returned, the previous value of the pointer will be written to `data`. + /// + /// # Safety + /// - The data must be a pointer to memory with the same schema. + /// - If `false` is returned you must ensure the `data` pointer is not used after pushing. + pub unsafe fn insert_raw(&mut self, entity: Entity, data: *mut u8) -> bool { + let index = entity.index() as usize; + let size = self.schema.layout().size(); + + // If the component already exists on the entity + if self.bitset.bit_test(entity.index() as usize) { + let ptr = self.storage.unchecked_idx_mut(index); + + // Swap the data with the data already there + ptr::swap_nonoverlapping(ptr.as_ptr(), data, size); + + // There was already a component of this type + true + + // If the component does not already exist for this entity. + } else { + // Update our maximum enitity id. + self.max_id = self.max_id.max(index + 1); + + // Make sure we have enough memory allocated for storage. + self.allocate_enough(index); + + // Set the bit indicating that this entity has this component data stored. + self.bitset.bit_set(index); + + // Copy the data from the data pointer into our storage + self.storage + .unchecked_idx_mut(index) + .as_ptr() + .copy_from_nonoverlapping(data, size); + + // There was not already a component of this type + false + } + } + + /// Ensures that we have the storage filled at least until the `until` variable. + /// + /// Usually, set this to `entity.index`. + fn allocate_enough(&mut self, until: usize) { + if self.storage.capacity() <= until { + self.storage + // TODO: Determine a better policy for resizing component storage. + .resize((until + 1) * 2) + .unwrap(); + } + } + + /// Get a reference to the component storage for the given [`Entity`]. + /// # Panics + /// Panics if the schema of `T` doesn't match. + #[track_caller] + #[inline] + pub fn get(&self, entity: Entity) -> Option<&T> { + self.try_get(entity).unwrap() + } + + /// Get a reference to the component storage for the given [`Entity`]. + /// # Errors + /// Errors if the schema of `T` doesn't match. + pub fn try_get(&self, entity: Entity) -> Result, SchemaMismatchError> { + self.get_ref(entity).map(|x| x.try_cast()).transpose() + } + + /// Get a [`SchemaRef`] to the component for the given [`Entity`] if the entity has this + /// component. + #[inline] + pub fn get_ref(&self, entity: Entity) -> Option { + let idx = entity.index() as usize; + self.get_idx(idx) + } + + fn get_idx(&self, idx: usize) -> Option { + if self.bitset.bit_test(idx) { + // SOUND: we ensure that there is allocated storge for entities that have their bit set. + let ptr = unsafe { self.storage.unchecked_idx(idx) }; + // SOUND: we know that the pointer has our schema. + Some(unsafe { SchemaRef::from_ptr_schema(ptr.as_ptr(), self.schema) }) + } else { + None + } + } + + /// Get a mutable reference to the component storage for the given [`Entity`]. + /// # Panics + /// Panics if the schema of `T` doesn't match. + #[track_caller] + #[inline] + pub fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { + self.try_get_mut(entity).unwrap() + } + + /// Get a mutable reference to the component storage for the given [`Entity`]. + /// # Errors + /// Errors if the schema of `T` doesn't match. + pub fn try_get_mut( + &mut self, + entity: Entity, + ) -> Result, SchemaMismatchError> { + self.get_ref_mut(entity) + .map(|x| x.try_cast_into_mut()) + .transpose() + } + + /// Get a [`SchemaRefMut`] to the component for the given [`Entity`] + #[inline] + pub fn get_ref_mut<'a>(&mut self, entity: Entity) -> Option> { + let idx = entity.index() as usize; + self.get_idx_mut(idx) + } + + fn get_idx_mut<'a>(&mut self, idx: usize) -> Option> { + if self.bitset.bit_test(idx) { + // SOUND: we ensure that there is allocated storage for entities that have their bit + // set. + let ptr = unsafe { self.storage.unchecked_idx_mut(idx) }; + // SOUND: we know that the pointer has our schema. + Some(unsafe { SchemaRefMut::from_ptr_schema(ptr.as_ptr(), self.schema) }) + } else { + None + } + } + + /// Get mutable references s to the component data for multiple entities at the same time. + /// + /// # Panics + /// + /// This will panic if the same entity is specified multiple times. This is invalid because it + /// would mean you would have two mutable references to the same component data at the same + /// time. + /// + /// This will also panic if there is a schema mismatch. + #[inline] + #[track_caller] + pub fn get_many_mut( + &mut self, + entities: [Entity; N], + ) -> [Option<&mut T>; N] { + self.try_get_many_mut(entities).unwrap() + } + + /// Get mutable references s to the component data for multiple entities at the same time. + /// + /// # Panics + /// + /// This will panic if the same entity is specified multiple times. This is invalid because it + /// would mean you would have two mutable references to the same component data at the same + /// time. + /// + /// # Errors + /// + /// This will error if there is a schema mismatch. + pub fn try_get_many_mut( + &mut self, + entities: [Entity; N], + ) -> Result<[Option<&mut T>; N], SchemaMismatchError> { + if self.schema != T::schema() { + Err(SchemaMismatchError) + } else { + let mut refs = self.get_many_ref_mut(entities); + let refs = std::array::from_fn(|i| { + let r = refs[i].take(); + // SOUND: we've validated the schema matches. + r.map(|r| unsafe { r.deref_mut() }) + }); + + Ok(refs) + } + } + + /// Get [`SchemaRefMut`]s to the component data for multiple entities at the same time. + /// + /// # Panics + /// + /// This will panic if the same entity is specified multiple times. This is invalid because it + /// would mean you would have two mutable references to the same component data at the same + /// time. + pub fn get_many_ref_mut( + &mut self, + entities: [Entity; N], + ) -> [Option; N] { + // Sort a copy of the passed in entities list. + let mut sorted = entities; + sorted.sort_unstable(); + // Detect duplicates. + // + // Since we have sorted the slice, any duplicates will be adjacent to each-other, and we + // only have to make sure that for every item in the slice, the one after it is not the same + // as it. + for i in 0..(N - 1) { + if sorted[i] == sorted[i + 1] { + panic!("All entities passed to `get_multiple_mut()` must be unique."); + } + } + + std::array::from_fn(|i| { + let index = entities[i].index() as usize; + + if self.bitset.bit_test(index) { + // SOUND: we've already validated that the contents of storage is valid for type T. + // The new lifetime is sound because we validate that all of these borrows don't + // overlap and their lifetimes are that of the &mut self borrow. + unsafe { + let ptr = self.storage.unchecked_idx_mut(index); + Some(SchemaRefMut::from_ptr_schema(ptr.as_ptr(), self.schema)) + } + } else { + None + } + }) + } + + /// Remove the component data for the entity if it exists. + /// # Errors + /// Errors if the schema doesn't match. + #[inline] + #[track_caller] + pub fn remove(&mut self, entity: Entity) -> Option { + self.try_remove(entity).unwrap() + } + + /// Remove the component data for the entity if it exists. + /// # Errors + /// Errors if the schema doesn't match. + pub fn try_remove( + &mut self, + entity: Entity, + ) -> Result, SchemaMismatchError> { + if self.schema != T::schema() { + Err(SchemaMismatchError) + } else if self.bitset.contains(entity) { + let mut data = MaybeUninit::::uninit(); + // SOUND: the data doesn't overlap the storage. + unsafe { self.remove_raw(entity, Some(data.as_mut_ptr() as *mut u8)) }; + + // SOUND: we've initialized the data. + Ok(Some(unsafe { data.assume_init() })) + } else { + // SOUND: we don't use the out pointer. + unsafe { self.remove_raw(entity, None) }; + Ok(None) + } + } + + /// Remove the component data for the entity if it exists. + pub fn remove_box(&mut self, entity: Entity) -> Option { + if self.bitset.contains(entity) { + // SOUND: we will immediately initialize the schema box with data matching the schema. + let mut b = unsafe { SchemaBox::uninitialized(self.schema) }; + // SOUND: the box data doesn't overlap the storage. + unsafe { self.remove_raw(entity, Some(b.as_mut().as_ptr())) }; + Some(b) + } else { + // SOUND: we don't use the out pointer. + unsafe { self.remove_raw(entity, None) }; + None + } + } + + /// If there is a previous value, `true` will be returned. + /// + /// If `out` is set and true is returned, the previous value will be written to it. + /// + /// # Safety + /// + /// If set, the `out` pointer, must not overlap the internal component storage. + pub unsafe fn remove_raw(&mut self, entity: Entity, out: Option<*mut u8>) -> bool { + let index = entity.index() as usize; + let size = self.schema.layout().size(); + + if self.bitset.bit_test(index) { + self.bitset.bit_reset(index); + + let ptr = self.storage.unchecked_idx_mut(index).as_ptr(); + + if let Some(out) = out { + // SAFE: user asserts `out` is non-overlapping + out.copy_from_nonoverlapping(ptr, size); + } else if let Some(drop_fn) = self.schema.drop_fn { + // SAFE: construcing `UntypedComponentStore` asserts the soundess of the drop_fn + // + // And ptr is a valid pointer to the component type. + drop_fn(ptr); + } + + // Found previous component + true + } else { + // No previous component + false + } + } + + /// Iterates immutably over all components of this type. + /// + /// Very fast but doesn't allow joining with other component types. + pub fn iter(&self) -> UntypedComponentStoreIter<'_> { + UntypedComponentStoreIter { + store: self, + idx: 0, + } + } + + /// Iterates mutably over all components of this type. + /// + /// Very fast but doesn't allow joining with other component types. + pub fn iter_mut(&mut self) -> UntypedComponentStoreIterMut<'_> { + UntypedComponentStoreIterMut { + store: self, + idx: 0, + } + } + + /// Iterates immutably over the components of this type where `bitset` indicates the indices of + /// entities. + /// + /// Slower than `iter()` but allows joining between multiple component types. + pub fn iter_with_bitset(&self, bitset: Rc) -> UntypedComponentBitsetIterator { + UntypedComponentBitsetIterator { + current_id: 0, + components: self, + bitset, + } + } + + /// Iterates mutable over the components of this type where `bitset` indicates the indices of + /// entities. + /// + /// Slower than `iter()` but allows joining between multiple component types. + pub fn iter_mut_with_bitset( + &mut self, + bitset: Rc, + ) -> UntypedComponentBitsetIteratorMut { + UntypedComponentBitsetIteratorMut { + current_id: 0, + components: self, + bitset, + } + } + + /// Returns the bitset indicating which entity indices have a component associated to them. + /// + /// Useful to build conditions between multiple `Components`' bitsets. + /// + /// For example, take two bitsets from two different `Components` types. Then, + /// bitset1.clone().bit_and(bitset2); And finally, you can use bitset1 in `iter_with_bitset` and + /// `iter_mut_with_bitset`. This will iterate over the components of the entity only for + /// entities that have both components. + pub fn bitset(&self) -> &BitSetVec { + &self.bitset + } + + /// Convert into a typed [`ComponentStore`]. + /// # Panics + /// Panics if the schema doesn't match. + #[inline] + #[track_caller] + pub fn into_typed(self) -> ComponentStore { + self.try_into().unwrap() + } +} + +/// Mutable iterator over pointers in an untyped component store. +pub struct UntypedComponentStoreIter<'a> { + store: &'a UntypedComponentStore, + idx: usize, +} +impl<'a> Iterator for UntypedComponentStoreIter<'a> { + type Item = SchemaRef<'a>; + fn next(&mut self) -> Option { + loop { + if self.idx < self.store.max_id { + if let Some(ptr) = self.store.get_idx(self.idx) { + self.idx += 1; + break Some(ptr); + } else { + self.idx += 1; + } + } else { + break None; + } + } + } +} + +/// Mutable iterator over pointers in an untyped component store. +pub struct UntypedComponentStoreIterMut<'a> { + store: &'a mut UntypedComponentStore, + idx: usize, +} +impl<'a> Iterator for UntypedComponentStoreIterMut<'a> { + type Item = SchemaRefMut<'a, 'a>; + fn next(&mut self) -> Option { + loop { + if self.idx < self.store.max_id { + if let Some(ptr) = self.store.get_idx_mut(self.idx) { + self.idx += 1; + // Re-create the ref to extend the lifetime. + // SOUND: We know the pointer will be valid for the lifetime of the store. + break Some(unsafe { + SchemaRefMut::from_ptr_schema(ptr.as_ptr(), ptr.schema()) + }); + } else { + self.idx += 1; + } + } else { + break None; + } + } + } +} diff --git a/crates/bones_ecs/src/entities.rs b/framework_crates/bones_ecs/src/entities.rs similarity index 94% rename from crates/bones_ecs/src/entities.rs rename to framework_crates/bones_ecs/src/entities.rs index b317a0363b..9103c0cfee 100644 --- a/crates/bones_ecs/src/entities.rs +++ b/framework_crates/bones_ecs/src/entities.rs @@ -46,8 +46,8 @@ impl Entity { /// /// It also holds a list of entities that were recently killed, which allows to remove components of /// deleted entities at the end of a game frame. -#[derive(TypeUlid, Clone)] -#[ulid = "01GNDN1CYXP2XVQKQFK3RNSGGD"] +#[derive(Clone, HasSchema)] +#[schema(opaque)] pub struct Entities { /// Bitset containing all living entities alive: BitSetVec, @@ -58,6 +58,11 @@ pub struct Entities { /// bitset. has_deleted: bool, } +impl std::fmt::Debug for Entities { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Entities").finish_non_exhaustive() + } +} impl Default for Entities { fn default() -> Self { @@ -83,34 +88,34 @@ pub trait QueryItem { // TODO: Implement optional component query iterators. -impl<'a, 'q, T: TypedEcsData> QueryItem for &'a Comp<'q, T> { +impl<'a, 'q, T: HasSchema> QueryItem for &'a Comp<'q, T> { type Iter = ComponentBitsetIterator<'a, T>; fn apply_bitset(&self, bitset: &mut BitSetVec) { bitset.bit_and(self.bitset()); } fn iter_with_bitset(self, bitset: Rc) -> Self::Iter { - Comp::iter_with_bitset(self, bitset) + ComponentStore::iter_with_bitset(&**self, bitset) } } -impl<'a, 'q, T: TypedEcsData> QueryItem for &'a CompMut<'q, T> { +impl<'a, 'q, T: HasSchema> QueryItem for &'a CompMut<'q, T> { type Iter = ComponentBitsetIterator<'a, T>; fn apply_bitset(&self, bitset: &mut BitSetVec) { bitset.bit_and(self.bitset()); } fn iter_with_bitset(self, bitset: Rc) -> Self::Iter { - CompMut::iter_with_bitset(self, bitset) + ComponentStore::iter_with_bitset(&**self, bitset) } } -impl<'a, 'q, T: TypedEcsData> QueryItem for &'a mut CompMut<'q, T> { +impl<'a, 'q, T: HasSchema> QueryItem for &'a mut CompMut<'q, T> { type Iter = ComponentBitsetIteratorMut<'a, T>; fn apply_bitset(&self, bitset: &mut BitSetVec) { bitset.bit_and(self.bitset()); } fn iter_with_bitset(self, bitset: Rc) -> Self::Iter { - CompMut::iter_mut_with_bitset(self, bitset) + ComponentStore::iter_mut_with_bitset(self, bitset) } } @@ -266,11 +271,11 @@ impl Entities { /// /// ``` /// # use bones_ecs::prelude::*; - /// # #[derive(Clone, TypeUlid)] - /// # #[ulid = "01GP1SVTTSR91P40B2W0XPQ1SN"] + /// # #[derive(HasSchema, Clone, Default)] + /// # #[repr(C)] /// # struct Pos { x: f32, y: f32 }; - /// # #[derive(Clone, TypeUlid)] - /// # #[ulid = "01GP1SW3HYWEB2TY4S40ARMB1R"] + /// # #[derive(HasSchema, Clone, Default)] + /// # #[repr(C)] /// # struct Vel { x: f32, y: f32 }; /// /// fn my_system(entities: Res, mut pos: CompMut, vel: Comp) { @@ -497,6 +502,7 @@ mod tests { entities.create(); } + #[cfg(not(miri))] // This test is very slow on miri and not critical to test for. #[test] #[should_panic(expected = "Exceeded maximum amount")] fn force_max_entity_panic() { @@ -506,6 +512,7 @@ mod tests { } } + #[cfg(not(miri))] // This test is very slow on miri and not critical to test for. #[test] #[should_panic(expected = "Exceeded maximum amount")] fn force_max_entity_panic2() { diff --git a/crates/bones_ecs/src/error.rs b/framework_crates/bones_ecs/src/error.rs similarity index 80% rename from crates/bones_ecs/src/error.rs rename to framework_crates/bones_ecs/src/error.rs index 6778ef9586..b684d3a2f0 100644 --- a/crates/bones_ecs/src/error.rs +++ b/framework_crates/bones_ecs/src/error.rs @@ -26,13 +26,6 @@ pub enum EcsError { /// To create an error of this type easily, use the `system_error!` macro. #[error("System errored: {0}")] SystemError(Box), - /// This happens when two Rust types have the same [`TypeUlid`][crate::ulid::TypeUlid], which - /// must not happen in the same [`World`][crate::world::World]. - #[error( - "Attempted to initialize resource/component with the same TypeUlid as another type \ - that has already been initialized." - )] - TypeUlidCollision, } /// The result of a `System`'s execution. diff --git a/framework_crates/bones_ecs/src/lib.rs b/framework_crates/bones_ecs/src/lib.rs new file mode 100644 index 0000000000..26912c02f5 --- /dev/null +++ b/framework_crates/bones_ecs/src/lib.rs @@ -0,0 +1,102 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(doc, allow(unknown_lints))] +#![deny(rustdoc::all)] +#![warn(missing_docs)] + +pub mod atomic { + //! Atomic Refcell implmentation. + //! + //! Atomic Refcells are from the [`atomic_refcell`] crate. + //! + //! [`atomic_refcell`]: https://docs.rs/atomic_refcell + pub use atomic_refcell::*; +} +pub mod bitset; +pub mod components; +pub mod entities; +pub mod resources; +pub mod stage; +pub mod system; + +pub use bones_schema as schema; +pub use bones_utils as utils; + +mod error; +pub use error::EcsError; + +mod world; +pub use world::{FromWorld, World}; + +/// The prelude. +pub mod prelude { + pub use { + atomic_refcell::*, bitset_core::BitSet, bones_schema::prelude::*, bones_utils::prelude::*, + }; + + pub use crate::{ + bitset::*, + components::*, + entities::*, + error::*, + resources::*, + stage::{CoreStage::*, *}, + system::*, + FromWorld, UnwrapMany, World, + }; + + // Make bones_schema available for derive macros + pub use bones_schema; +} + +/// Helper trait for unwraping each item in an array. +/// +/// # Example +/// +/// ``` +/// # use bones_ecs::UnwrapMany; +/// let data = [Some(1), Some(2)]; +/// let [data1, data2] = data.unwrap_many(); +/// ``` +pub trait UnwrapMany { + /// Unwrap all the items in an array. + fn unwrap_many(self) -> [T; N]; +} + +impl UnwrapMany for [Option; N] { + fn unwrap_many(self) -> [T; N] { + let mut iter = self.into_iter(); + std::array::from_fn(|_| iter.next().unwrap().unwrap()) + } +} +impl UnwrapMany for [Result; N] { + fn unwrap_many(self) -> [T; N] { + let mut iter = self.into_iter(); + std::array::from_fn(|_| iter.next().unwrap().unwrap()) + } +} + +#[cfg(test)] +mod test { + use crate::prelude::*; + + #[test] + fn insert_comp_with_gap() { + let mut w = World::new(); + + #[derive(HasSchema, Default, Clone)] + #[repr(C)] + struct MyComp(u32, u32, u32); + + w.run_system( + |mut entities: ResMut, mut comps: CompMut| { + for _ in 0..3 { + entities.create(); + } + + let e = entities.create(); + comps.insert(e, default()); + }, + ) + .unwrap(); + } +} diff --git a/framework_crates/bones_ecs/src/resources.rs b/framework_crates/bones_ecs/src/resources.rs new file mode 100644 index 0000000000..b936234d75 --- /dev/null +++ b/framework_crates/bones_ecs/src/resources.rs @@ -0,0 +1,301 @@ +//! World resource storage. + +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; + +use crate::prelude::*; + +/// Storage for un-typed resources. +/// +/// This is the backing data store used by [`Resources`]. +/// +/// Unless you are intending to do modding or otherwise need raw pointers to your resource data, you +/// should use [`Resources`] instead. +#[derive(Clone, Default)] +pub struct UntypedResources { + resources: HashMap, +} + +/// An untyped resource that may be inserted into [`UntypedResources`]. +#[derive(Deref, DerefMut)] +pub struct UntypedAtomicResource { + #[deref] + cell: Arc>, + schema: &'static Schema, +} + +impl UntypedAtomicResource { + /// Creates a new [`UntypedAtomicResource`] storing the given data. + pub fn new(resource: SchemaBox) -> Self { + Self { + schema: resource.schema(), + cell: Arc::new(AtomicRefCell::new(resource)), + } + } + + /// Create a new [`UntypedAtomicResource`] for the given schema, initially populated with the default + /// value for the schema. + pub fn from_schema(schema: &'static Schema) -> Self { + Self { + cell: Arc::new(AtomicRefCell::new(SchemaBox::default(schema))), + schema, + } + } + + /// Get another [`UntypedAtomicResource`] that points to the same data. + pub fn clone_cell(&self) -> UntypedAtomicResource { + Self { + cell: self.cell.clone(), + schema: self.schema, + } + } + + /// Get the schema of the resource. + pub fn schema(&self) -> &'static Schema { + self.schema + } +} + +impl Clone for UntypedAtomicResource { + fn clone(&self) -> Self { + Self { + cell: Arc::new(AtomicRefCell::new(self.cell.borrow().clone())), + schema: self.schema, + } + } +} + +impl UntypedResources { + /// Create an empty [`UntypedResources`]. + pub fn new() -> Self { + Self::default() + } + + /// Insert a resource. + pub fn insert(&mut self, resource: SchemaBox) -> Option { + let id = resource.schema().id(); + self.resources + .insert(id, UntypedAtomicResource::new(resource)) + } + + /// Insert a resource. + pub fn insert_cell( + &mut self, + resource: UntypedAtomicResource, + ) -> Option { + let id = resource.cell.borrow().schema().id(); + self.resources.insert(id, resource) + } + + /// Get a cell containing the resource data pointer for the given ID. + pub fn get_cell(&self, schema_id: SchemaId) -> Option { + self.resources.get(&schema_id).map(|x| x.clone_cell()) + } + + /// Get a reference to an untyped resource. + pub fn get(&self, schema_id: SchemaId) -> Option> { + self.resources + .get(&schema_id) + .map(|x| AtomicRefCell::borrow(&x.cell)) + } + + /// Get a mutable reference to an untyped resource. + pub fn get_mut(&mut self, schema_id: SchemaId) -> Option> { + self.resources + .get(&schema_id) + .map(|x| AtomicRefCell::borrow_mut(&x.cell)) + } + + /// Remove a resource. + pub fn remove(&mut self, id: SchemaId) -> Option { + self.resources.remove(&id) + } +} + +/// A collection of resources. +/// +/// [`Resources`] is essentially a type-map +#[derive(Clone, Default)] +pub struct Resources { + untyped: UntypedResources, +} + +impl Resources { + /// Create an empty [`Resources`]. + pub fn new() -> Self { + Self::default() + } + + /// Insert a resource. + pub fn insert(&mut self, resource: T) -> Option> { + self.untyped + .insert(SchemaBox::new(resource)) + .map(|x| AtomicResource::from_untyped(x)) + } + + /// Insert a resource cell. + pub fn insert_cell(&mut self, resource: AtomicResource) { + self.untyped.insert_cell(resource.untyped); + } + + /// Borrow a resource. + pub fn get(&self) -> Option> { + self.untyped.get(T::schema().id()).map(|sbox| { + AtomicRef::map(sbox, |sbox| { + // SOUND: schema matches as checked by retreiving from the untyped store by the schema + // ID. + unsafe { sbox.as_ref().deref() } + }) + }) + } + + /// Mutably borrow a resource. + pub fn get_mut(&mut self) -> Option> { + self.untyped.get_mut(T::schema().id()).map(|sbox| { + AtomicRefMut::map(sbox, |sbox| { + // SOUND: schema matches as checked by retreiving from the untyped store by the + // schema ID. + unsafe { sbox.as_mut().deref_mut() } + }) + }) + } + + /// Check whether or not a resource is in the store. + /// + /// See [get()][Self::get] + pub fn contains(&self) -> bool { + self.untyped.resources.contains_key(&T::schema().id()) + } + + /// Gets a clone of the resource cell for the resource of the given type. + pub fn get_cell(&self) -> Option> { + let untyped = self.untyped.get_cell(T::schema().id())?; + + Some(AtomicResource { + untyped, + _phantom: PhantomData, + }) + } + + /// Borrow the underlying [`UntypedResources`] store. + pub fn untyped(&self) -> &UntypedResources { + &self.untyped + } + /// Mutably borrow the underlying [`UntypedResources`] store. + pub fn untyped_mut(&mut self) -> &mut UntypedResources { + &mut self.untyped + } + /// Consume [`Resources`] and extract the underlying [`UntypedResources`]. + pub fn into_untyped(self) -> UntypedResources { + self.untyped + } +} + +/// A handle to a resource from a [`Resources`] collection. +/// +/// This is not the resource itself, but a cheaply clonable handle to it. +/// +/// To access the resource you must borrow it with either [`borrow()`][Self::borrow] or +/// [`borrow_mut()`][Self::borrow_mut]. +pub struct AtomicResource { + untyped: UntypedAtomicResource, + _phantom: PhantomData, +} +impl Debug for AtomicResource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("AtomicResource(")?; + T::fmt(self.untyped.cell.borrow().cast_ref(), f)?; + f.write_str(")")?; + Ok(()) + } +} + +impl Default for AtomicResource { + fn default() -> Self { + Self { + untyped: UntypedAtomicResource::new(SchemaBox::new(T::default())), + _phantom: Default::default(), + } + } +} + +impl AtomicResource { + /// Create a new atomic resource. + /// + /// This can be inserted into a world by calling `world.resources.insert_cell`. + pub fn new(data: T) -> Self { + AtomicResource { + untyped: UntypedAtomicResource::new(SchemaBox::new(data)), + _phantom: PhantomData, + } + } + + /// Clone this atomic resource, returning a handle to the same resource data. + pub fn clone_cell(&self) -> Self { + Self { + untyped: self.untyped.clone_cell(), + _phantom: PhantomData, + } + } + + /// Create from an [`UntypedAtomicResource`]. + pub fn from_untyped(untyped: UntypedAtomicResource) -> Self { + assert_eq!(T::schema(), untyped.schema); + AtomicResource { + untyped, + _phantom: PhantomData, + } + } + + /// Lock the resource for reading. + /// + /// This returns a read guard, very similar to an [`RwLock`][std::sync::RwLock]. + pub fn borrow(&self) -> AtomicRef { + let borrow = AtomicRefCell::borrow(&self.untyped); + // SOUND: We know that the data pointer is valid for type T. + AtomicRef::map(borrow, |data| unsafe { data.as_ref().deref() }) + } + + /// Lock the resource for read-writing. + /// + /// This returns a write guard, very similar to an [`RwLock`][std::sync::RwLock]. + pub fn borrow_mut(&self) -> AtomicRefMut { + let borrow = AtomicRefCell::borrow_mut(&self.untyped); + // SOUND: We know that the data pointer is valid for type T. + AtomicRefMut::map(borrow, |data| unsafe { data.as_mut().deref_mut() }) + } +} + +#[cfg(test)] +mod test { + use crate::prelude::*; + + #[test] + fn sanity_check() { + #[derive(HasSchema, Clone, Debug, Default)] + #[repr(C)] + struct A(String); + + #[derive(HasSchema, Clone, Debug, Default)] + #[repr(C)] + struct B(u32); + + let mut resources = Resources::new(); + + resources.insert(A(String::from("hi"))); + assert_eq!(resources.get::().unwrap().0, "hi"); + + let r2 = resources.clone(); + + resources.insert(A(String::from("bye"))); + resources.insert(A(String::from("world"))); + assert_eq!(resources.get::().unwrap().0, "world"); + + assert_eq!(r2.get::().unwrap().0, "hi"); + + resources.insert(B(1)); + assert_eq!(resources.get::().unwrap().0, 1); + resources.insert(B(2)); + assert_eq!(resources.get::().unwrap().0, 2); + assert_eq!(resources.get::().unwrap().0, "world"); + } +} diff --git a/crates/bones_ecs/src/stage.rs b/framework_crates/bones_ecs/src/stage.rs similarity index 84% rename from crates/bones_ecs/src/stage.rs rename to framework_crates/bones_ecs/src/stage.rs index 63d87b1f08..abcfd89e3e 100644 --- a/crates/bones_ecs/src/stage.rs +++ b/framework_crates/bones_ecs/src/stage.rs @@ -8,6 +8,23 @@ use crate::prelude::*; pub struct SystemStages { /// The stages in the collection, in the order that they will be run. pub stages: Vec>, + /// Whether or not the startup systems have been run yet. + pub has_started: bool, + /// The systems that should run at startup. + pub startup_systems: Vec, +} +impl std::fmt::Debug for SystemStages { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SystemStages") + // TODO: add list of stages to the debug render for `SystemStages`. + .finish() + } +} + +impl Default for SystemStages { + fn default() -> Self { + Self::with_core_stages() + } } impl SystemStages { @@ -25,6 +42,14 @@ impl SystemStages { /// > **Note:** You must call [`initialize_systems()`][Self::initialize_systems] once before /// > calling `run()` one or more times. pub fn run(&mut self, world: &mut World) -> SystemResult { + if !self.has_started { + for system in &mut self.startup_systems { + system.initialize(world); + system.run(world).unwrap(); + } + self.has_started = true; + } + for stage in &mut self.stages { stage.run(world)?; } @@ -42,9 +67,17 @@ impl SystemStages { Box::new(SimpleSystemStage::new(CoreStage::PostUpdate)), Box::new(SimpleSystemStage::new(CoreStage::Last)), ], + has_started: false, + startup_systems: default(), } } + /// Add a system that will run only once, before all of the other non-startup systems. + pub fn add_startup_system>(&mut self, system: S) -> &mut Self { + self.startup_systems.push(system.system()); + self + } + /// Add a [`System`] to the stage with the given label. pub fn add_system_to_stage, L: StageLabel>( &mut self, @@ -165,12 +198,13 @@ impl SystemStage for SimpleSystemStage { // Drain the command queue { - let command_queue = world.resource::(); - let mut command_queue = command_queue.borrow_mut(); + if let Some(command_queue) = world.resources.get_cell::() { + let mut command_queue = command_queue.borrow_mut(); - for mut system in command_queue.queue.drain(..) { - system.initialize(world); - system.run(world).unwrap(); + for mut system in command_queue.queue.drain(..) { + system.initialize(world); + system.run(world).unwrap(); + } } } @@ -231,8 +265,8 @@ impl StageLabel for CoreStage { /// A resource containing the [`Commands`] command queue. /// /// You can use [`Commands`] as a [`SystemParam`] as a shortcut to [`ResMut`]. -#[derive(Debug, TypeUlid, Default)] -#[ulid = "01GPY3KPT0CDNCM23HTKAKN0NJ"] +#[derive(Debug, HasSchema, Default)] +#[schema(opaque)] pub struct CommandQueue { /// The system queue that will be run at the end of the stage pub queue: VecDeque, @@ -274,7 +308,7 @@ impl<'a> SystemParam for Commands<'a> { fn initialize(_world: &mut World) {} fn get_state(world: &World) -> Self::State { - world.resource::() + world.resources.get_cell::().unwrap() } fn borrow(state: &mut Self::State) -> Self::Param<'_> { diff --git a/crates/bones_ecs/src/system.rs b/framework_crates/bones_ecs/src/system.rs similarity index 72% rename from crates/bones_ecs/src/system.rs rename to framework_crates/bones_ecs/src/system.rs index 8d0ef0b29c..40d698bbbc 100644 --- a/crates/bones_ecs/src/system.rs +++ b/framework_crates/bones_ecs/src/system.rs @@ -1,7 +1,14 @@ //! Implements the system API for the ECS. +use std::sync::Arc; + use crate::prelude::*; +#[derive(Deref, DerefMut)] +struct Test { + s: String, +} + /// Struct used to run a system function using the world. pub struct System { /// This should be called once to initialize the system, allowing it to intialize any resources @@ -132,8 +139,22 @@ pub trait SystemParam: Sized { } /// [`SystemParam`] for getting read access to a resource. -pub struct Res<'a, T: TypedEcsData + FromWorld>(AtomicRef<'a, T>); -impl<'a, T: TypedEcsData + FromWorld> std::ops::Deref for Res<'a, T> { +/// +/// Use [`Res`] if you want to automatically initialize the resource. +pub struct Res<'a, T: HasSchema>(AtomicRef<'a, T>); +impl<'a, T: HasSchema> std::ops::Deref for Res<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// [`SystemParam`] for getting read access to a resource and initialzing it if it doesn't already +/// exist. +/// +/// Use [`Res`] if you don't want to automatically initialize the resource. +pub struct ResInit<'a, T: HasSchema + FromWorld>(AtomicRef<'a, T>); +impl<'a, T: HasSchema + FromWorld> std::ops::Deref for ResInit<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 @@ -141,60 +162,129 @@ impl<'a, T: TypedEcsData + FromWorld> std::ops::Deref for Res<'a, T> { } /// [`SystemParam`] for getting mutable access to a resource. -pub struct ResMut<'a, T: TypedEcsData + FromWorld>(AtomicRefMut<'a, T>); -impl<'a, T: TypedEcsData + FromWorld> std::ops::Deref for ResMut<'a, T> { +/// +/// Use [`ResMutInit`] if you want to automatically initialize the resource. +pub struct ResMut<'a, T: HasSchema>(AtomicRefMut<'a, T>); +impl<'a, T: HasSchema> std::ops::Deref for ResMut<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'a, T: HasSchema> std::ops::DerefMut for ResMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// [`SystemParam`] for getting mutable access to a resource and initializing it if it doesn't +/// already exist. +/// +/// Use [`ResMut`] if you don't want to automatically initialize the resource. +pub struct ResMutInit<'a, T: HasSchema + FromWorld>(AtomicRefMut<'a, T>); +impl<'a, T: HasSchema + FromWorld> std::ops::Deref for ResMutInit<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a, T: TypedEcsData + FromWorld> std::ops::DerefMut for ResMut<'a, T> { +impl<'a, T: HasSchema + FromWorld> std::ops::DerefMut for ResMutInit<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl<'a, T: TypedEcsData + FromWorld> SystemParam for Res<'a, T> { +impl<'a, T: HasSchema> SystemParam for Res<'a, T> { type State = AtomicResource; type Param<'p> = Res<'p, T>; + fn initialize(_world: &mut World) {} + + fn get_state(world: &World) -> Self::State { + world.resources.get_cell::().unwrap_or_else(|| { + panic!( + "Resource of type `{}` not in world. \ + You may need to insert or initialize the resource or use \ + `ResInit` instead of `Res` to automatically initialize the \ + resource with the default value.", + std::any::type_name::() + ) + }) + } + + fn borrow(state: &mut Self::State) -> Self::Param<'_> { + Res(state.borrow()) + } +} + +impl<'a, T: HasSchema + FromWorld> SystemParam for ResInit<'a, T> { + type State = AtomicResource; + type Param<'p> = ResInit<'p, T>; + fn initialize(world: &mut World) { - world.init_resource::() + if !world.resources.contains::() { + world.init_resource::(); + } } fn get_state(world: &World) -> Self::State { - world.resource::() + world.resources.get_cell::().unwrap() } fn borrow(state: &mut Self::State) -> Self::Param<'_> { - Res(state.borrow()) + ResInit(state.borrow()) } } -impl<'a, T: TypedEcsData + FromWorld> SystemParam for ResMut<'a, T> { +impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> { type State = AtomicResource; type Param<'p> = ResMut<'p, T>; + fn initialize(_world: &mut World) {} + + fn get_state(world: &World) -> Self::State { + world.resources.get_cell::().unwrap_or_else(|| { + panic!( + "Resource of type `{}` not in world. \ + You may need to insert or initialize the resource or use \ + `ResInit` instead of `Res` to automatically initialize the \ + resource with the default value.", + std::any::type_name::() + ) + }) + } + + fn borrow(state: &mut Self::State) -> Self::Param<'_> { + ResMut(state.borrow_mut()) + } +} + +impl<'a, T: HasSchema + FromWorld> SystemParam for ResMutInit<'a, T> { + type State = AtomicResource; + type Param<'p> = ResMutInit<'p, T>; + fn initialize(world: &mut World) { - world.init_resource::(); + if !world.resources.contains::() { + world.init_resource::(); + } } fn get_state(world: &World) -> Self::State { - world.resource::() + world.resources.get_cell::().unwrap() } fn borrow(state: &mut Self::State) -> Self::Param<'_> { - ResMut(state.borrow_mut()) + ResMutInit(state.borrow_mut()) } } /// [`SystemParam`] for getting read access to a [`ComponentStore`]. -pub type Comp<'a, T> = AtomicComponentStoreRef<'a, T>; +pub type Comp<'a, T> = AtomicRef<'a, ComponentStore>; /// [`SystemParam`] for getting mutable access to a [`ComponentStore`]. -pub type CompMut<'a, T> = AtomicComponentStoreRefMut<'a, T>; +pub type CompMut<'a, T> = AtomicRefMut<'a, ComponentStore>; -impl<'a, T: TypedEcsData> SystemParam for Comp<'a, T> { - type State = AtomicComponentStore; +impl<'a, T: HasSchema> SystemParam for Comp<'a, T> { + type State = Arc>>; type Param<'p> = Comp<'p, T>; fn initialize(world: &mut World) { @@ -202,7 +292,7 @@ impl<'a, T: TypedEcsData> SystemParam for Comp<'a, T> { } fn get_state(world: &World) -> Self::State { - world.components.get::() + world.components.get_cell::().unwrap() } fn borrow(state: &mut Self::State) -> Self::Param<'_> { @@ -210,8 +300,8 @@ impl<'a, T: TypedEcsData> SystemParam for Comp<'a, T> { } } -impl<'a, T: TypedEcsData> SystemParam for CompMut<'a, T> { - type State = AtomicComponentStore; +impl<'a, T: HasSchema> SystemParam for CompMut<'a, T> { + type State = Arc>>; type Param<'p> = CompMut<'p, T>; fn initialize(world: &mut World) { @@ -219,7 +309,7 @@ impl<'a, T: TypedEcsData> SystemParam for CompMut<'a, T> { } fn get_state(world: &World) -> Self::State { - world.components.get::() + world.components.get_cell::().unwrap() } fn borrow(state: &mut Self::State) -> Self::Param<'_> { @@ -346,8 +436,8 @@ mod tests { #[test] fn convert_system() { fn tmp( - _var1: AtomicComponentStoreRef, - _var2: AtomicComponentStoreRef, + _var1: AtomicRef>, + _var2: AtomicRef>, _var3: Res, _var4: ResMut, ) -> SystemResult { @@ -403,34 +493,36 @@ mod tests { #[test] fn system_replace_resource() { - #[derive(Default, TypeUlid, Clone, PartialEq, Eq, Debug)] - #[ulid = "01GNDP03R29SDA1S009KTQF18Y"] + #[derive(Default, HasSchema, Clone, PartialEq, Eq, Debug)] + #[schema(opaque)] pub struct A; - #[derive(Default, TypeUlid, Clone, Debug)] - #[ulid = "01GNDP0C73TAV0TDKZZB39NQ8C"] + #[derive(Default, HasSchema, Clone, Debug)] + #[schema(opaque)] pub struct B { x: u32, } let mut world = World::default(); - let mut my_system = (|_a: Res, mut b: ResMut| { + let mut my_system = (|_a: ResInit, mut b: ResMutInit| { let b2 = B { x: 45 }; *b = b2; Ok(()) }) .system(); - assert!(world.resources.try_get::().is_none()); + assert!(world.resources.get_cell::().is_none()); my_system.initialize(&mut world); - let res = world.resource::(); - assert_eq!(res.borrow().x, 0); + { + let res = world.resource::(); + assert_eq!(res.x, 0); + } my_system.run(&world).unwrap(); let res = world.resource::(); - assert_eq!(res.borrow().x, 45); + assert_eq!(res.x, 45); let res = world.resource::(); - assert_eq!(*res.borrow(), A); + assert_eq!(*res, A); } } diff --git a/crates/bones_ecs/src/world.rs b/framework_crates/bones_ecs/src/world.rs similarity index 79% rename from crates/bones_ecs/src/world.rs rename to framework_crates/bones_ecs/src/world.rs index bccec8eb73..87dc98a486 100644 --- a/crates/bones_ecs/src/world.rs +++ b/framework_crates/bones_ecs/src/world.rs @@ -12,10 +12,15 @@ use crate::prelude::*; #[derive(Clone)] pub struct World { /// Stores the world resources. - pub(crate) resources: Resources, + pub resources: Resources, /// Stores the world components. pub components: ComponentStores, } +impl std::fmt::Debug for World { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("World").finish() + } +} impl Default for World { fn default() -> Self { @@ -44,8 +49,7 @@ impl World { /// This will remove the component storage for all killed entities, and allow their slots to be /// re-used for any new entities. pub fn maintain(&mut self) { - let entities = self.resources.get::(); - let mut entities = entities.borrow_mut(); + let mut entities = self.resources.get_mut::().unwrap(); for components in &mut self.components.components.values_mut() { let mut components = components.borrow_mut(); @@ -54,7 +58,7 @@ impl World { // Safe: We don't provide an out pointer, so it doesn't overlap the component's // internal storage. unsafe { - components.remove(entity, None); + components.remove_raw(entity, None); } } } @@ -93,47 +97,56 @@ impl World { } /// Initialize a resource of type `T` by inserting it's default value. - pub fn init_resource(&mut self) { + pub fn init_resource(&mut self) { if !self.resources.contains::() { let value = R::from_world(self); - self.resources.insert(value) + self.resources.insert(value); } } /// Insert a resource. - /// - /// # Panics - /// - /// Panics if you try to insert a Rust type with a different [`std::any::TypeId`], but the same - /// [`TypeUlid`] as another resource in the store. - pub fn insert_resource(&mut self, resource: R) { + pub fn insert_resource(&mut self, resource: R) -> Option> { self.resources.insert(resource) } - /// Get a resource handle from the store. - /// - /// This is not the resource itself, but a handle, may be cloned cheaply. - /// - /// In order to access the resource you must call [`borrow()`][AtomicResource::borrow] or - /// [`borrow_mut()`][AtomicResource::borrow_mut] on the returned value. - /// + /// Borrow a resource from the world. /// # Panics - /// /// Panics if the resource does not exist in the store. - pub fn resource(&self) -> AtomicResource { - match self.resources.try_get::() { + #[track_caller] + pub fn resource(&self) -> AtomicRef { + match self.resources.get::() { Some(r) => r, None => panic!( "Requested resource {} does not exist in the `World`. Did you forget to add it using `world.insert_resource` / `world.init_resource`?", - std::any::type_name::() + std::any::type_name::() ), } } - /// Gets a resource handle from the store if it exists. - pub fn get_resource(&self) -> Option> { - self.resources.try_get::() + /// Borrow a resource from the world. + /// # Panics + /// Panics if the resource does not exist in the store. + #[track_caller] + pub fn resource_mut(&mut self) -> AtomicRefMut { + match self.resources.get_mut::() { + Some(r) => r, + None => panic!( + "Requested resource {} does not exist in the `World`. + Did you forget to add it using `world.insert_resource` / `world.init_resource`?", + std::any::type_name::() + ), + } + } + + /// Borrow a resource from the world, if it exists. + pub fn get_resource(&self) -> Option> { + self.resources.get() + } + + /// Borrow a resource from the world, if it exists. + pub fn get_resource_mut(&mut self) -> Option> { + self.resources.get_mut() } } @@ -158,16 +171,16 @@ mod tests { use super::FromWorld; - #[derive(Clone, TypeUlid, Debug, Eq, PartialEq)] - #[ulid = "01GNDN2QYC1TRE763R54HVWZ0W"] + #[derive(Clone, HasSchema, Debug, Eq, PartialEq, Default)] + #[repr(C)] struct Pos(i32, i32); - #[derive(Clone, TypeUlid, Debug, Eq, PartialEq)] - #[ulid = "01GNDN3HCY2F1SGYE8Z0GGDMXB"] + #[derive(Clone, HasSchema, Debug, Eq, PartialEq, Default)] + #[repr(C)] struct Vel(i32, i32); - #[derive(Clone, TypeUlid, Debug, Eq, PartialEq)] - #[ulid = "01GNDN3QJD1SP7ANTZ0TG6Q804"] + #[derive(Clone, HasSchema, Debug, Eq, PartialEq, Default)] + #[schema(opaque)] struct Marker; // Sets up the world with a couple entities. @@ -279,23 +292,6 @@ mod tests { world1.run_system(test_pos_vel_1_run).unwrap(); } - #[test] - #[should_panic(expected = "TypeUlidCollision")] - fn no_duplicate_component_uuids() { - #[derive(Clone, TypeUlid)] - #[ulid = "01GNDN440Q4FYH34TY8MV8CTTB"] - struct A; - - /// This struct has the same UUID as struct [`A`]. Big no no!! - #[derive(Clone, TypeUlid)] - #[ulid = "01GNDN440Q4FYH34TY8MV8CTTB"] - struct B; - - let mut w = World::default(); - w.components.init::(); - w.components.init::(); - } - #[test] fn world_is_send() { send(World::new()) @@ -307,17 +303,16 @@ mod tests { // From World // ============ - #[derive(Clone, TypeUlid)] - #[ulid = "01GRWJV4NRXY9NJBBDMD2D9QK3"] + #[derive(Clone, HasSchema, Default)] + #[schema(opaque)] struct TestResource(u32); - #[derive(Clone, TypeUlid)] - #[ulid = "01GRWJW44YGNSXQ81W395J0D52"] + #[derive(Clone, HasSchema)] + #[schema(opaque, no_default)] struct TestFromWorld(u32); impl FromWorld for TestFromWorld { fn from_world(world: &mut World) -> Self { let b = world.resource::(); - let b = b.borrow(); Self(b.0) } } @@ -330,7 +325,6 @@ mod tests { w.insert_resource(TestResource(1)); let resource = w.resource::(); - let resource = resource.borrow(); assert_eq!(resource.0, 0); } } diff --git a/framework_crates/bones_framework/CHANGELOG.md b/framework_crates/bones_framework/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/framework_crates/bones_framework/Cargo.toml b/framework_crates/bones_framework/Cargo.toml new file mode 100644 index 0000000000..899138c9c8 --- /dev/null +++ b/framework_crates/bones_framework/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "bones_framework" +description = "The Bones game development framework." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[features] +default = ["image_png", "ui", "localization"] +#! Cargo feature supported in `bones_framework`. + +## Enable the `ui` module, powered by [`egui`]. +ui = ["dep:egui"] +## Enable the localization module, powered by [`fluent`](https://github.com/projectfluent/fluent-rs). +localization = ["dep:fluent-bundle", "dep:fluent-langneg", "dep:intl-memoizer", "dep:unic-langid", "dep:sys-locale"] + +#! ### Image Formats +#! These features enable different image formats. +#! +#! If there is an image format that you need that is not in the list, you may check the +#! [supported formats](https://github.com/image-rs/image#supported-image-formats) list. +#! If the format you need is in the list, please open an issue and we can add a feature +#! for it. + +## Enable PNG image loader. +image_png = ["image/png"] +## Enable JPEG image loader. +image_jpeg = ["image/jpeg"] +## Enable WEBP image loader. +image_webp = ["image/webp"] +## Enable TIFF image loader. +image_tiff = ["image/tiff"] +## Enable GIF image loader. +image_gif = ["image/gif"] +## Enable ICO image loader. +image_ico = ["image/ico"] +## Enable BMP image loader. +image_bmp = ["image/bmp"] + +document-features = ["dep:document-features"] + +[dependencies] +# Bones +bones_lib = { version = "0.3", path = "../bones_lib", features = ["glam"] } + +# Other +glam = "0.24" +thiserror = "1.0" +instant = "0.1" +hex = "0.4" +tracing = "0.1" + +# Input +gilrs = "0.10" + +# Assets +serde_yaml = "0.9" +serde = { version = "1.0", features = ["derive"] } + +# Sprite +image = { version = "0.24", default-features = false } + +# Gui +egui = { version = "0.22", optional = true } + +# Localization +fluent-bundle = { version = "0.15", optional = true } +fluent-langneg = { version = "0.13", optional = true } +intl-memoizer = { version = "0.5", optional = true } +unic-langid = { version = "0.9", features = ["serde"], optional = true } +sys-locale = { version = "0.3", optional = true } + +# API docs +document-features = { version = "0.2", optional = true } diff --git a/src/animation.rs b/framework_crates/bones_framework/src/animation.rs similarity index 65% rename from src/animation.rs rename to framework_crates/bones_framework/src/animation.rs index d6a922d8a7..a0066dcfb7 100644 --- a/src/animation.rs +++ b/framework_crates/bones_framework/src/animation.rs @@ -1,44 +1,35 @@ //! Animation utilities and systems. -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use crate::prelude::*; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; pub(crate) mod prelude { pub use super::{AnimatedSprite, AnimationBankSprite}; } /// Install animation utilities into the given [`SystemStages`]. -pub fn install(stages: &mut SystemStages) { - stages +pub fn plugin(core: &mut Session) { + core.stages .add_system_to_stage(CoreStage::Last, update_animation_banks) .add_system_to_stage(CoreStage::Last, animate_sprites); } /// Component that may be added to entities with an [`AtlasSprite`] to animate them. -#[derive(Clone, TypeUlid, Debug)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[ulid = "01GNZRPWKAHKP33V1KKRVAMVS7"] +#[derive(Clone, HasSchema, Debug)] +#[schema(opaque)] pub struct AnimatedSprite { /// The current frame in the animation. - #[cfg_attr(feature = "serde", serde(default))] pub index: usize, /// The frames in the animation. /// /// These are the indexes into the atlas, specified in the order they will be played. - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc_slice"))] - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arc_slice"))] - pub frames: Arc<[usize]>, + pub frames: Arc<[u32]>, /// The frames per second to play the animation at. pub fps: f32, /// The amount of time the current frame has been playing - #[cfg_attr(feature = "serde", serde(default))] pub timer: f32, /// Whether or not to repeat the animation - #[cfg_attr(feature = "serde", serde(default = "default_true"))] pub repeat: bool, } @@ -52,55 +43,18 @@ fn default_true() -> bool { /// /// This is great for players or other sprites that will change through different, named animations /// at different times. -#[derive(Clone, TypeUlid, Debug)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[ulid = "01GP4EM4BGJPX22HYAMGYKKSAV"] +#[derive(Clone, HasSchema, Debug, Default)] +#[schema(opaque)] pub struct AnimationBankSprite { - #[cfg_attr(feature = "serde", serde(default))] /// The current animation. pub current: Key, /// The collection of animations in this animation bank. - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc"))] - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arc"))] pub animations: Arc>, #[cfg_attr(feature = "serde", serde(default))] /// The last animation that was playing. pub last_animation: Key, } -#[cfg(feature = "serde")] -fn deserialize_arc<'de, T: Deserialize<'de>, D: Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - let item = T::deserialize(deserializer)?; - Ok(Arc::new(item)) -} -#[cfg(feature = "serde")] -fn serialize_arc( - data: &Arc, - serializer: S, -) -> Result { - use std::ops::Deref; - let data = data.deref(); - data.serialize(serializer) -} -#[cfg(feature = "serde")] -fn deserialize_arc_slice<'de, T: Deserialize<'de>, D: Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - let item = >::deserialize(deserializer)?; - Ok(Arc::from(item)) -} -#[cfg(feature = "serde")] -fn serialize_arc_slice( - data: &Arc<[T]>, - serializer: S, -) -> Result { - use std::ops::Deref; - let data = data.deref(); - data.serialize(serializer) -} - impl Default for AnimatedSprite { fn default() -> Self { Self { @@ -115,8 +69,8 @@ impl Default for AnimatedSprite { /// System for automatically animating sprites with the [`AnimatedSprite`] component. pub fn animate_sprites( + time: Res(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut struct_schema = StructSchemaInfo { + fields: Vec::with_capacity(seq.size_hint().unwrap_or(0)), + }; + while let Some(schema) = seq.next_element()? { + struct_schema + .fields + .push(StructFieldInfo { name: None, schema }); + } + Ok(struct_schema) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut struct_schema = StructSchemaInfo { + fields: Vec::with_capacity(map.size_hint().unwrap_or(0)), + }; + while let Some((name, schema)) = map.next_entry()? { + struct_schema.fields.push(StructFieldInfo { + name: Some(name), + schema, + }); + } + Ok(struct_schema) + } +} diff --git a/framework_crates/bones_schema/src/std_impls.rs b/framework_crates/bones_schema/src/std_impls.rs new file mode 100644 index 0000000000..9a9aad5562 --- /dev/null +++ b/framework_crates/bones_schema/src/std_impls.rs @@ -0,0 +1,289 @@ +use bones_utils::fxhash::FxHasher; + +use crate::{prelude::*, raw_fns::*}; + +use std::{any::TypeId, hash::Hasher, sync::OnceLock}; + +macro_rules! impl_primitive { + ($t:ty, $prim:ident) => { + unsafe impl HasSchema for $t { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + kind: SchemaKind::Primitive(Primitive::$prim), + type_id: Some(TypeId::of::<$t>()), + clone_fn: Some(<$t as RawClone>::raw_clone), + drop_fn: Some(<$t as RawDrop>::raw_drop), + default_fn: Some(<$t as RawDefault>::raw_default), + hash_fn: Some(<$t as RawHash>::raw_hash), + eq_fn: Some(<$t as RawEq>::raw_eq), + type_data: Default::default(), + }) + }) + } + } + }; +} + +impl_primitive!(String, String); +impl_primitive!(bool, Bool); +impl_primitive!(u8, U8); +impl_primitive!(u16, U16); +impl_primitive!(u32, U32); +impl_primitive!(u64, U64); +impl_primitive!(u128, U128); +impl_primitive!(i8, I8); +impl_primitive!(i16, I16); +impl_primitive!(i32, I32); +impl_primitive!(i64, I64); +impl_primitive!(i128, I128); + +macro_rules! schema_impl_float { + ($t:ty, $prim:ident) => { + unsafe impl HasSchema for $t { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + kind: SchemaKind::Primitive(Primitive::$prim), + type_id: Some(TypeId::of::<$t>()), + clone_fn: Some(<$t as RawClone>::raw_clone), + drop_fn: Some(<$t as RawDrop>::raw_drop), + default_fn: Some(<$t as RawDefault>::raw_default), + hash_fn: Some(<$t as CustomRawFns>::raw_hash), + eq_fn: Some(<$t as CustomRawFns>::raw_eq), + type_data: Default::default(), + }) + }) + } + } + }; +} + +schema_impl_float!(f32, F32); +schema_impl_float!(f64, F64); + +unsafe impl HasSchema for usize { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + kind: SchemaKind::Primitive({ + #[cfg(target_pointer_width = "32")] + let p = Primitive::U32; + #[cfg(target_pointer_width = "64")] + let p = Primitive::U64; + p + }), + type_id: Some(TypeId::of::()), + clone_fn: Some(::raw_clone), + drop_fn: Some(::raw_drop), + default_fn: Some(::raw_default), + hash_fn: Some(::raw_hash), + eq_fn: Some(::raw_eq), + type_data: Default::default(), + }) + }) + } +} +unsafe impl HasSchema for isize { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + kind: SchemaKind::Primitive({ + #[cfg(target_pointer_width = "32")] + let p = Primitive::I32; + #[cfg(target_pointer_width = "64")] + let p = Primitive::I64; + p + }), + type_id: Some(TypeId::of::()), + clone_fn: Some(::raw_clone), + drop_fn: Some(::raw_drop), + default_fn: Some(::raw_default), + hash_fn: Some(::raw_hash), + eq_fn: Some(::raw_eq), + type_data: Default::default(), + }) + }) + } +} + +#[cfg(feature = "glam")] +mod impl_glam { + use super::*; + use glam::*; + + unsafe impl HasSchema for Quat { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + let layout = std::alloc::Layout::new::(); + S.get_or_init(|| { + SCHEMA_REGISTRY.register(SchemaData { + kind: SchemaKind::Primitive(Primitive::Opaque { + size: layout.size(), + align: layout.align(), + }), + type_id: Some(TypeId::of::()), + clone_fn: Some(::raw_clone), + drop_fn: Some(::raw_drop), + default_fn: Some(::raw_default), + // TODO: Implement hash and eq for `Quat`. + hash_fn: None, + eq_fn: None, + type_data: Default::default(), + }) + }) + } + } + + macro_rules! schema_impl_glam { + ($t:ty, $prim:ident, $nprim:ident, $($field:ident),+) => { + unsafe impl HasSchema for $t { + fn schema() -> &'static Schema { + static S: OnceLock<&'static Schema> = OnceLock::new(); + + S.get_or_init(|| { + let type_id = Some(TypeId::of::()); + let kind = SchemaKind::Struct(StructSchemaInfo { + fields: vec![ + $( + StructFieldInfo { + name: Some(stringify!($field).into()), + schema: $nprim::schema(), + } + ),* + ], + }); + SCHEMA_REGISTRY.register(SchemaData { + type_id, + kind, + type_data: Default::default(), + clone_fn: Some(::raw_clone), + drop_fn: Some(::raw_drop), + default_fn: Some(::raw_default), + hash_fn: Some(::raw_hash), + eq_fn: Some(::raw_eq), + }) + }) + } + } + }; + } + + macro_rules! schema_impl_glam_vecs { + ($prim:ident, $nprim:ident, $id:ident) => { + paste::paste! { + schema_impl_glam!( [< $id 2 >], $prim, $nprim, x, y); + schema_impl_glam!( [< $id 3 >], $prim, $nprim, x, y, z); + schema_impl_glam!( [< $id 4 >], $prim, $nprim, x, y, z, w); + } + }; + } + + schema_impl_glam_vecs!(Bool, bool, BVec); + schema_impl_glam_vecs!(U32, u32, UVec); + schema_impl_glam_vecs!(I32, i32, IVec); + schema_impl_glam_vecs!(F32, f32, Vec); + schema_impl_glam_vecs!(F64, f64, DVec); + + // TODO: Implement `HasSchema` for glam matrix types. + + macro_rules! custom_fns_impl_bvec { + ($ty:ident) => { + impl CustomRawFns for glam::$ty { + unsafe extern "C-unwind" fn raw_hash(ptr: *const u8) -> u64 { + ::raw_hash(ptr) + } + unsafe extern "C-unwind" fn raw_eq(a: *const u8, b: *const u8) -> bool { + ::raw_eq(a, b) + } + } + }; + } + custom_fns_impl_bvec!(BVec2); + custom_fns_impl_bvec!(BVec3); + custom_fns_impl_bvec!(BVec4); + + macro_rules! custom_fns_impl_glam { + ($t:ty, $prim:ident, $($field:ident),+) => { + impl CustomRawFns for $t { + unsafe extern "C-unwind" fn raw_hash(ptr: *const u8) -> u64 { + let this = unsafe { &*(ptr as *const Self) }; + let mut hasher = FxHasher::default(); + $( + hasher.write_u64($prim::raw_hash(&this.$field as *const $prim as *const u8)); + )+ + hasher.finish() + } + + unsafe extern "C-unwind" fn raw_eq(a: *const u8, b: *const u8) -> bool { + let a = unsafe { &*(a as *const Self) }; + let b = unsafe { &*(b as *const Self) }; + + $( + $prim::raw_eq( + &a.$field as *const $prim as *const u8, + &b.$field as *const $prim as *const u8, + ) + )&&+ + } + } + }; + } + custom_fns_impl_glam!(Vec2, f32, x, y); + custom_fns_impl_glam!(Vec3, f32, x, y, z); + custom_fns_impl_glam!(Vec4, f32, x, y, z, w); + custom_fns_impl_glam!(DVec2, f64, x, y); + custom_fns_impl_glam!(DVec3, f64, x, y, z); + custom_fns_impl_glam!(DVec4, f64, x, y, z, w); + custom_fns_impl_glam!(UVec2, u32, x, y); + custom_fns_impl_glam!(UVec3, u32, x, y, z); + custom_fns_impl_glam!(UVec4, u32, x, y, z, w); + custom_fns_impl_glam!(IVec2, i32, x, y); + custom_fns_impl_glam!(IVec3, i32, x, y, z); + custom_fns_impl_glam!(IVec4, i32, x, y, z, w); + custom_fns_impl_glam!(Quat, f32, x, y, z, w); +} + +/// Trait for types that require specific implementations of eq and hash fns, for use in this module only. +trait CustomRawFns { + unsafe extern "C-unwind" fn raw_hash(ptr: *const u8) -> u64; + unsafe extern "C-unwind" fn raw_eq(a: *const u8, b: *const u8) -> bool; +} + +macro_rules! custom_fns_impl_float { + ($ty:ident) => { + impl CustomRawFns for $ty { + unsafe extern "C-unwind" fn raw_hash(ptr: *const u8) -> u64 { + let this = unsafe { &*(ptr as *const Self) }; + + let mut hasher = FxHasher::default(); + if this.is_nan() { + // Ensure all NaN representations hash to the same value + hasher.write(&$ty::to_ne_bytes($ty::NAN)); + } else if *this == 0.0 { + // Ensure both zeroes hash to the same value + hasher.write(&$ty::to_ne_bytes(0.0)); + } else { + hasher.write(&$ty::to_ne_bytes(*this)); + } + hasher.finish() + } + + unsafe extern "C-unwind" fn raw_eq(a: *const u8, b: *const u8) -> bool { + let a = unsafe { &*(a as *const Self) }; + let b = unsafe { &*(b as *const Self) }; + if a.is_nan() && a.is_nan() { + true + } else { + a == b + } + } + } + }; +} +custom_fns_impl_float!(f32); +custom_fns_impl_float!(f64); diff --git a/framework_crates/bones_schema/tests/tests.rs b/framework_crates/bones_schema/tests/tests.rs new file mode 100644 index 0000000000..86e87aa972 --- /dev/null +++ b/framework_crates/bones_schema/tests/tests.rs @@ -0,0 +1,316 @@ +use std::alloc::Layout; + +use bones_schema::prelude::*; +use glam::{Vec2, Vec3}; + +#[derive(HasSchema, Debug, Clone, Default)] +#[repr(C)] +struct DataA { + x: f32, + y: f32, +} + +#[derive(HasSchema, Debug, Clone, Default, PartialEq)] +#[repr(C)] +struct DataB(f32, f32); + +#[derive(HasSchema, Debug, Clone, Default)] +#[repr(C)] +struct DataC { + a: DataA, + b: DataB, +} + +#[derive(HasSchema, Debug, Clone, Default)] +#[repr(C)] +struct Zst; + +#[derive(HasSchema, Debug, Clone, Default)] +#[schema(opaque)] +struct OpaqueZst; + +#[test] +fn cast_glam() { + // Create a glam vector + let mut a = Vec2::new(1.2, 3.4); + // Cast it to custom type with the same layout + let b: &DataA = a.cast(); + + // Make sure the values match after casting + assert_eq!(a.x, b.x); + assert_eq!(a.y, b.y); + + // Try it with tuple struct + let c: &DataB = a.cast(); + assert_eq!(a.x, c.0); + assert_eq!(a.y, c.1); + + // Do a mutable cast + let d: &mut DataA = a.cast_mut(); + // Modify the data through casted ref + d.y = 5.0; + + // Make sure the original vec was modified + assert_eq!(a.y, 5.0); +} + +#[test] +fn ptr_cast() { + // Let's say we have a store that we need to store asset data in. + let mut store = Vec::new(); + + // First we'll create a glam vec + let a = Vec2::new(1.2, 3.4); + + // Create a type-erased pointer to the vec + let ptr = SchemaBox::new(a); + + // And add it to the store + store.push(ptr); + + // Now we can also add a different type to the store + let b = Vec3::new(1.2, 3.4, 5.6); + store.push(SchemaBox::new(b)); + + // When we want to get the data back out + for ptr in &store { + // We can try to cast the data back to any type with the same schema. + if let Ok(data) = ptr.try_cast_ref::() { + assert_eq!(data.x, a.x); + assert_eq!(data.y, a.y); + } else if let Ok(data) = ptr.try_cast_ref::() { + assert_eq!(data.x, b.x); + assert_eq!(data.y, b.y); + assert_eq!(data.z, b.z); + } + } + + // And we can modify the data, too. + // Here we use the panicking version of the cast function + store[1].cast_mut::().x = 7.0; + assert_eq!(store[1].cast_ref::().x, 7.0); + + // And we can even clone it ( cloning will panic if the schema doesn't implement cloning ). + let ptr = store[1].clone(); + + assert_eq!(ptr.cast_ref::(), store[1].cast_ref::()); + + // Finally, we can conver the box to the inner type, if we know what it is. This will panic if + // the schema doesn't match. + let ptr = SchemaBox::new(String::from("hello")); + let inner = ptr.into_inner::(); + assert_eq!(inner, "hello"); +} + +#[test] +fn sbox() { + let mut b = SBox::new(String::from("hello")); + assert_eq!(*b, "hello"); + b.push('!'); + assert_eq!(*b, "hello!"); +} + +#[test] +#[should_panic = "Invalid cast: the schemas of the casted types are not compatible."] +fn cast_not_matching_fails_ref() { + let a = Vec3::ONE; + a.cast::(); +} + +#[test] +#[should_panic = "Invalid cast: the schemas of the casted types are not compatible."] +fn cast_not_matching_fails_mut() { + let mut a = Vec3::ONE; + a.cast_mut::(); +} + +#[test] +fn ptr_fields() { + let mut data = DataC { + a: DataA { x: 1.0, y: 2.0 }, + b: DataB(3.0, 4.0), + }; + + { + let ptr = SchemaRef::new(&data); + + let a = ptr.field("a"); + let x = a.field("x").cast::(); + let y = a.field("y").cast::(); + let b = ptr.field("b"); + let b0 = b.field(0).cast::(); + let b1 = b.field(1).cast::(); + + assert_eq!(*x, 1.0); + assert_eq!(*y, 2.0); + assert_eq!(*b0, 3.0); + assert_eq!(*b1, 4.0); + } + + { + let mut ptr = SchemaRefMut::new(&mut data); + + let mut a = ptr.field("a"); + let mut x = a.field("x"); + let x = x.cast_mut::(); + assert_eq!(*x, 1.0); + *x *= 3.0; + let mut y = a.field("y"); + let y = y.cast_mut::(); + assert_eq!(*y, 2.0); + *y *= 3.0; + + let mut b = ptr.field("b"); + let mut b0 = b.field(0); + let b0 = b0.cast_mut::(); + assert_eq!(*b0, 3.0); + *b0 *= 3.0; + let mut b1 = b.field(1); + let b1 = b1.cast_mut::(); + assert_eq!(*b1, 4.0); + *b1 *= 3.0; + } + + assert_eq!(data.a.x, 3.0); + assert_eq!(data.a.y, 6.0); + assert_eq!(data.b.0, 9.0); + assert_eq!(data.b.1, 12.0); +} + +#[test] +fn schema_vec() { + let mut v = SchemaVec::new(DataA::schema()); + assert_eq!(v.len(), 0); + assert_eq!(v.capacity(), 0); + v.push(DataA { x: 1.0, y: 2.0 }); + v.push_box(SchemaBox::new(DataA { x: 3.0, y: 4.0 })); + assert_eq!(v.len(), 2); + + let d0 = v.get::(0).unwrap(); + assert_eq!(d0.x, 1.0); + assert_eq!(d0.y, 2.0); + let d1 = v.get_ref(1).unwrap().cast::(); + assert_eq!(d1.x, 3.0); + assert_eq!(d1.y, 4.0); + + assert_eq!(v.len(), 2); + + let d1 = v.pop_box().unwrap(); + assert_eq!(d1.cast_ref::().x, 3.0); + let d0 = v.pop::().unwrap(); + assert_eq!(d0.x, 1.0); + assert!(v.pop_box().is_none()); + + let mut v = SchemaVec::new(i32::schema()); + for i in 0..10 { + v.push(i); + } + for mut item in &mut v { + *item.cast_mut::() *= 2; + } + let mut viter = v.iter(); + for i in 0..10 { + assert_eq!(*viter.next().unwrap().cast::(), i * 2); + } +} + +#[test] +fn svec() { + let mut v = SVec::new(); + for i in 0..10 { + v.push(i); + } + + let mut i = 0; + #[allow(clippy::explicit_counter_loop)] + for n in &v { + assert_eq!(i, *n); + i += 1; + } + + for n in &mut v { + *n *= 2; + } + + let mut i = 0; + #[allow(clippy::explicit_counter_loop)] + for n in &v { + assert_eq!(i * 2, *n); + i += 1; + } +} + +#[test] +fn schema_map() { + let k1 = String::from("hello"); + let k2 = String::from("goodbye"); + let mut m = SchemaMap::new(String::schema(), DataB::schema()); + let previous = m.insert(k1.clone(), DataB(1.0, 2.0)); + assert!(previous.is_none()); + m.insert(k2.clone(), DataB(3.0, 4.0)); + + { + let v1: &mut DataB = m.get_mut(&k1).unwrap(); + assert_eq!(v1.1, 2.0); + v1.0 = 7.0; + } + { + let v2: &mut DataB = m.get_mut(&k2).unwrap(); + assert_eq!(v2.0, 3.0); + assert_eq!(v2.1, 4.0); + } + + let v1: &mut DataB = m.get_mut(&k1).unwrap(); + assert_eq!(v1.0, 7.0); + + let previous = m.insert(k1.clone(), DataB(10., 11.)); + assert_eq!(previous, Some(DataB(7.0, 2.0))) +} + +#[test] +fn eq_hash() { + let b1 = SchemaBox::new(String::from("hello")); + let b2 = SchemaBox::new(String::from("hello")); + let b3 = SchemaBox::new(String::from("goodbye")); + assert_eq!(b1, b2); + assert_ne!(b3, b2); + assert_ne!(b3, b1); + assert_eq!(dbg!(b1.hash()), b2.hash()); + assert_ne!(dbg!(b3.hash()), b1.hash()); + + let s_hash_fn = b1.schema().hash_fn.unwrap(); + assert_eq!(unsafe { (s_hash_fn)(b1.as_ref().as_ptr()) }, b1.hash()); +} + +#[test] +fn zst() { + let b = SchemaBox::new(Zst); + assert!(matches!(b.cast_ref(), Zst)); + let b = SchemaBox::new(OpaqueZst); + assert!(matches!(b.cast_ref(), OpaqueZst)); +} + +#[test] +fn schema_layout_matches_rust_layout() { + #[derive(HasSchema, Default, Clone)] + #[repr(C)] + struct A; + + #[derive(HasSchema, Default, Clone)] + #[repr(C)] + struct B { + a: f32, + b: u8, + c: String, + d: SVec, + } + + macro_rules! layout_eq { + ( $( $t:ident ),* ) => { + $( + assert_eq!($t::schema().layout(), Layout::new::<$t>()); + )* + }; + } + layout_eq!(A, B); +} diff --git a/framework_crates/bones_schema/tests/trybuild.rs b/framework_crates/bones_schema/tests/trybuild.rs new file mode 100644 index 0000000000..6ef2174743 --- /dev/null +++ b/framework_crates/bones_schema/tests/trybuild.rs @@ -0,0 +1,6 @@ +#[cfg(not(miri))] +#[test] +fn trybuild_tests() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trybuild_fail/*.rs"); +} diff --git a/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.rs b/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.rs new file mode 100644 index 0000000000..fe46cd021d --- /dev/null +++ b/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.rs @@ -0,0 +1,38 @@ +use bones_schema::prelude::*; + +#[derive(HasSchema, Clone, Default)] +#[repr(C)] +struct DataA { + x: f32, + y: f32, +} + +#[derive(HasSchema, Clone, Default)] +#[repr(C)] +struct DataB(u32, u32); + +#[derive(HasSchema, Clone, Default)] +#[repr(C)] +struct DataC { + a: DataA, + b: DataB, +} + +fn main() { + let mut data = DataC { + a: DataA { x: 1.0, y: 2.0 }, + b: DataB(3, 4), + }; + let mut ptr = SchemaRefMut::new(&mut data); + + let mut a = ptr.field("a"); + let mut x = a.field("x"); + let x = x.cast_mut::(); + *x = 1.5; + let mut y = a.field("y"); + let y = y.cast_mut::(); + *y = 2.5; + + // Important no-no, can't borrow x while y is borrowed. + dbg!(x); +} diff --git a/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.stderr b/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.stderr new file mode 100644 index 0000000000..f7fbad905e --- /dev/null +++ b/framework_crates/bones_schema/tests/trybuild_fail/schema_ptr.stderr @@ -0,0 +1,11 @@ +error[E0499]: cannot borrow `a` as mutable more than once at a time + --> tests/trybuild_fail/schema_ptr.rs:32:17 + | +29 | let mut x = a.field("x"); + | ------------ first mutable borrow occurs here +... +32 | let mut y = a.field("y"); + | ^^^^^^^^^^^^ second mutable borrow occurs here +... +37 | dbg!(x); + | - first borrow later used here diff --git a/framework_crates/bones_utils/Cargo.toml b/framework_crates/bones_utils/Cargo.toml new file mode 100644 index 0000000000..701052831e --- /dev/null +++ b/framework_crates/bones_utils/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bones_utils" +description = "Utilites used throughout the bones_framework." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[features] +serde = ["dep:serde", "hashbrown/serde"] + +[dependencies] +bones_utils_macros = { version = "0.3", path = "./macros" } +smallvec = "1.11" +fxhash = "0.2" +hashbrown = { version = "0.14" } +ulid = "1.0" +bevy_ptr = "0.11.0" +parking_lot = "0.12" +serde = { version = "1.0", optional = true } +maybe-owned = "0.3" +branches = "0.1" diff --git a/framework_crates/bones_utils/macros/Cargo.toml b/framework_crates/bones_utils/macros/Cargo.toml new file mode 100644 index 0000000000..0ddf2f36a1 --- /dev/null +++ b/framework_crates/bones_utils/macros/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bones_utils_macros" +description = "Macros used in bones_utils." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +venial = "0.5" diff --git a/framework_crates/bones_utils/macros/src/lib.rs b/framework_crates/bones_utils/macros/src/lib.rs new file mode 100644 index 0000000000..0f34c78b27 --- /dev/null +++ b/framework_crates/bones_utils/macros/src/lib.rs @@ -0,0 +1,170 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote, quote_spanned, spanned::Spanned}; + +/// Helper macro to bail out of the macro with a compile error. +macro_rules! throw { + ($hasSpan:expr, $err:literal) => { + let span = $hasSpan.__span(); + return quote_spanned!(span => + compile_error!($err); + ).into(); + }; +} + +/// Returns whether or not the passed-in attribute is a simple attribute with no arguments with a +/// name that matches `name`. +fn is_simple_named_attr(attr: &venial::Attribute, name: &str) -> bool { + attr.get_single_path_segment() == Some(&format_ident!("{name}")) + && attr.get_value_tokens().is_empty() +} + +/// Derive macro for deriving [`Deref`] on structs with one field. +#[proc_macro_derive(Deref, attributes(deref))] +pub fn derive_deref(input: TokenStream) -> TokenStream { + let input = venial::parse_declaration(input.into()).unwrap(); + + if let Some(s) = input.as_struct() { + let name = &s.name; + let params = &s.generic_params; + + match &s.fields { + venial::StructFields::Tuple(tuple) => { + if tuple.fields.len() != 1 { + throw!(tuple, "May only derive Deref for structs with one field."); + } + + let deref_type = &tuple.fields[0].0.ty; + + quote! { + impl #params ::std::ops::Deref for #name #params { + type Target = #deref_type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + } + .into() + } + venial::StructFields::Named(named) => { + let (deref_type, field_name) = if named.fields.is_empty() { + throw!(named, "May not derive Deref for struct without fields"); + } else if named.fields.len() > 1 { + let mut info = None; + for (field, _) in named.fields.iter() { + for attr in &field.attributes { + if is_simple_named_attr(attr, "deref") { + if info.is_some() { + throw!(attr, "Only one field may have the #[deref] attribute"); + } else { + info = Some((&field.ty, &field.name)); + } + } + } + } + + if let Some(info) = info { + info + } else { + throw!( + named, + "One field must be annotated with a #[deref] attribute" + ); + } + } else { + (&named.fields[0].0.ty, &named.fields[0].0.name) + }; + + quote! { + impl #params ::std::ops::Deref for #name #params { + type Target = #deref_type; + + fn deref(&self) -> &Self::Target { + &self.#field_name + } + } + } + .into() + } + venial::StructFields::Unit => { + throw!(s, "Cannot derive Deref on anything but structs."); + } + } + } else { + throw!(input, "Cannot derive Deref on anything but structs."); + } +} + +/// Derive macro for deriving [`DerefMut`] on structs with one field. +#[proc_macro_derive(DerefMut, attributes(deref))] +pub fn derive_deref_mut(input: TokenStream) -> TokenStream { + let input = venial::parse_declaration(input.into()).unwrap(); + + if let Some(s) = input.as_struct() { + let name = &s.name; + let params = &s.generic_params; + + match &s.fields { + venial::StructFields::Tuple(tuple) => { + if tuple.fields.len() != 1 { + throw!( + tuple, + "May only derive DerefMut for structs with one field." + ); + } + + quote! { + impl #params std::ops::DerefMut for #name #params { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + } + .into() + } + venial::StructFields::Named(named) => { + let field_name = if named.fields.is_empty() { + throw!(named, "May not derive Deref for struct without fields"); + } else if named.fields.len() > 1 { + let mut info = None; + for (field, _) in named.fields.iter() { + for attr in &field.attributes { + if is_simple_named_attr(attr, "deref") { + if info.is_some() { + throw!(attr, "Only one field may have the #[deref] attribute"); + } else { + info = Some(&field.name); + } + } + } + } + + if let Some(name) = info { + name + } else { + throw!( + named, + "One field must be annotated with a #[deref] attribute" + ); + } + } else { + &named.fields[0].0.name + }; + + quote! { + impl #params std::ops::DerefMut for #name #params { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.#field_name + } + } + } + .into() + } + venial::StructFields::Unit => { + throw!(s, "Cannot derive DerefMut on anything but structs."); + } + } + } else { + throw!(input, "Cannot derive DerefMut on anything but structs."); + } +} diff --git a/framework_crates/bones_utils/src/collections.rs b/framework_crates/bones_utils/src/collections.rs new file mode 100644 index 0000000000..bcac2cd63e --- /dev/null +++ b/framework_crates/bones_utils/src/collections.rs @@ -0,0 +1,14 @@ +use fxhash::FxHasher; +use std::hash::BuildHasherDefault; + +/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high +/// speed keyed hashing algorithm intended for use in in-memory hashmaps. +/// +/// aHash is designed for performance and is NOT cryptographically secure. +pub type HashMap = hashbrown::HashMap>; + +/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high +/// speed keyed hashing algorithm intended for use in in-memory hashmaps. +/// +/// aHash is designed for performance and is NOT cryptographically secure. +pub type HashSet = hashbrown::HashSet>; diff --git a/framework_crates/bones_utils/src/default.rs b/framework_crates/bones_utils/src/default.rs new file mode 100644 index 0000000000..48723654ea --- /dev/null +++ b/framework_crates/bones_utils/src/default.rs @@ -0,0 +1,30 @@ +/// An ergonomic abbreviation for [`Default::default()`] to make initializing structs easier. +/// This is especially helpful when combined with ["struct update syntax"](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax). +/// ``` +/// use bones_utils::default; +/// +/// #[derive(Default)] +/// struct Foo { +/// a: usize, +/// b: usize, +/// c: usize, +/// } +/// +/// // Normally you would initialize a struct with defaults using "struct update syntax" +/// // combined with `Default::default()`. This example sets `Foo::bar` to 10 and the remaining +/// // values to their defaults. +/// let foo = Foo { +/// a: 10, +/// ..Default::default() +/// }; +/// +/// // But now you can do this, which is equivalent: +/// let foo = Foo { +/// a: 10, +/// ..default() +/// }; +/// ``` +#[inline] +pub fn default() -> T { + std::default::Default::default() +} diff --git a/crates/bones_render/src/datatypes.rs b/framework_crates/bones_utils/src/key_mod.rs similarity index 98% rename from crates/bones_render/src/datatypes.rs rename to framework_crates/bones_utils/src/key_mod.rs index c7b3dbe4c6..141331c16e 100644 --- a/crates/bones_render/src/datatypes.rs +++ b/framework_crates/bones_utils/src/key_mod.rs @@ -1,4 +1,4 @@ -//! Useful data types such as [`Key`]. +// TODO: Replace `Key` with `fstr` crate. /// A small ascii byte array stored on the stack and used similarly to a string to represent things /// like animation keys, etc, without requring a heap allocation. diff --git a/framework_crates/bones_utils/src/labeled_id.rs b/framework_crates/bones_utils/src/labeled_id.rs new file mode 100644 index 0000000000..38f31e7a99 --- /dev/null +++ b/framework_crates/bones_utils/src/labeled_id.rs @@ -0,0 +1,192 @@ +use std::str::FromStr; + +use ulid::Ulid; + +/// A [`Ulid`] with a human-readable ascii prefix. +/// +/// This is essentially like a [TypeId](https://github.com/jetpack-io/typeid), but the prefix can be +/// any ascii string instead of only ascii lowercase. +#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct LabeledId { + /// The prefix + prefix: Option<[u8; 63]>, + /// The ULID. + ulid: Ulid, +} + +impl std::fmt::Debug for LabeledId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "LabeledId({self})") + } +} + +/// Error creating a [`LabeledId`]. +#[derive(Debug)] +pub enum LabeledIdCreateError { + /// The prefix was too long ( greater than 63 chars ). + PrefixTooLong, + /// The prefix was not ASCII. + PrefixNotAscii, +} + +impl std::error::Error for LabeledIdCreateError {} +impl std::fmt::Display for LabeledIdCreateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LabeledIdCreateError::PrefixTooLong => write!( + f, + "Labled ID prefix is too long ( maxumum length is 63 chars )." + ), + LabeledIdCreateError::PrefixNotAscii => write!(f, "Labeled ID prefix is not ASCII"), + } + } +} + +impl LabeledId { + /// Create a new labeled ID with the given prefix. + pub fn new(prefix: &str) -> Result { + Self::new_with_ulid(prefix, Ulid::new()) + } + + /// Create a new labeled ID with the given prefix and ULID. + pub fn new_with_ulid(prefix: &str, ulid: Ulid) -> Result { + if prefix.is_empty() { + Ok(Self { prefix: None, ulid }) + } else if prefix.len() > 63 { + Err(LabeledIdCreateError::PrefixTooLong) + } else if !prefix.is_ascii() { + Err(LabeledIdCreateError::PrefixNotAscii) + } else { + let mut prefix_bytes = [0; 63]; + prefix_bytes[0..prefix.len()].copy_from_slice(prefix.as_bytes()); + + Ok(Self { + prefix: Some(prefix_bytes), + ulid, + }) + } + } + + /// Get the prefix of the ID. + pub fn prefix(&self) -> &str { + self.prefix + .as_ref() + .map(|x| { + let prefix_len = Self::prefix_len(x); + let bytes = &x[0..prefix_len]; + std::str::from_utf8(bytes).unwrap() + }) + .unwrap_or("") + } + + /// Get the [`Ulid`] of the ID. + pub fn ulid(&self) -> Ulid { + self.ulid + } + + fn prefix_len(prefix: &[u8; 63]) -> usize { + let mut len = 0; + while prefix[len] != 0 { + len += 1; + } + len + } +} + +impl std::fmt::Display for LabeledId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(prefix) = &self.prefix { + if !prefix.is_ascii() { + return Err(std::fmt::Error); + } + let prefix_len = Self::prefix_len(prefix); + write!( + f, + "{}_{}", + String::from_utf8(prefix[0..prefix_len].into()).unwrap(), + self.ulid + ) + } else { + write!(f, "{}", self.ulid) + } + } +} + +/// Errors that can happen while parsing a [`LabeledId`]. +#[derive(Debug)] +pub enum LabledIdParseError { + /// The ID is in the wrong format. + InvalidFormat, + /// The ULID could not be parsed. + UlidDecode(ulid::DecodeError), + /// Error creating ID + CreateError(LabeledIdCreateError), +} + +impl std::fmt::Display for LabledIdParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LabledIdParseError::InvalidFormat => { + write!(f, "The Labeled ID is in the wrong format.") + } + LabledIdParseError::UlidDecode(e) => write!(f, "Error decoding ULID: {e}"), + LabledIdParseError::CreateError(e) => write!(f, "Error creating LabeledId: {e}"), + } + } +} + +impl FromStr for LabeledId { + type Err = LabledIdParseError; + + fn from_str(s: &str) -> Result { + use LabledIdParseError::*; + if let Some((prefix, ulid_text)) = s.rsplit_once('_') { + let ulid = Ulid::from_str(ulid_text).map_err(UlidDecode)?; + LabeledId::new_with_ulid(prefix, ulid).map_err(CreateError) + } else { + let ulid = Ulid::from_str(s).map_err(UlidDecode)?; + Ok(LabeledId { prefix: None, ulid }) + } + } +} + +#[cfg(feature = "serde")] +mod ser_de { + use super::*; + use serde::{Deserialize, Serialize}; + + impl Serialize for LabeledId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } + } + + impl<'de> Deserialize<'de> for LabeledId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let s = String::deserialize(deserializer)?; + s.parse().map_err(|e| D::Error::custom(format!("{e}"))) + } + } +} + +#[cfg(test)] +mod test { + + #[cfg(not(miri))] + #[test] + fn smoke() { + use crate::LabeledId; + + let id = LabeledId::new("asset").unwrap(); + let parsed: LabeledId = id.to_string().parse().unwrap(); + + assert_eq!(id, parsed) + } +} diff --git a/framework_crates/bones_utils/src/lib.rs b/framework_crates/bones_utils/src/lib.rs new file mode 100644 index 0000000000..1fb8442015 --- /dev/null +++ b/framework_crates/bones_utils/src/lib.rs @@ -0,0 +1,49 @@ +//! General utilities for [Bones] meta-engine crates. +//! +//! [Bones]: https://fishfolk.org/development/bones/introduction/ +//! +#![allow(clippy::type_complexity)] +#![warn(missing_docs)] +#![warn(clippy::undocumented_unsafe_blocks)] + +mod collections; +mod default; +mod key_mod; +mod labeled_id; +mod names; +mod ptr; + +/// Helper to export the same types in the crate root and in the prelude. +macro_rules! pub_use { + () => { + pub use crate::{collections::*, default::*, key_mod::*, labeled_id::*, names::*, ptr::*}; + pub use bevy_ptr::*; + pub use bones_utils_macros::*; + pub use branches::{likely, unlikely}; + pub use fxhash; + pub use hashbrown; + pub use maybe_owned::*; + pub use parking_lot; + pub use smallvec::*; + }; +} +pub_use!(); + +/// The prelude. +pub mod prelude { + pub_use!(); + pub use crate::key; +} + +/// Create a new const [`Key`][key_mod::Key] parsed at compile time. +#[macro_export] +macro_rules! key { + ($s:literal) => {{ + const KEY: Key = match Key::new($s) { + Ok(key) => key, + Err(KeyError::TooLong) => panic!("Key too long"), + Err(KeyError::NotAscii) => panic!("Key not ascii"), + }; + KEY + }}; +} diff --git a/framework_crates/bones_utils/src/names.rs b/framework_crates/bones_utils/src/names.rs new file mode 100644 index 0000000000..d8abbdcf1e --- /dev/null +++ b/framework_crates/bones_utils/src/names.rs @@ -0,0 +1,136 @@ +/// Shortens a type name to remove all module paths. +/// +/// The short name of a type is its full name as returned by +/// [`std::any::type_name`], but with the prefix of all paths removed. For +/// example, the short name of `alloc::vec::Vec>` +/// would be `Vec>`. +pub fn get_short_name(full_name: &str) -> String { + // Generics result in nested paths within <..> blocks. + // Consider "bevy_render::camera::camera::extract_cameras". + // To tackle this, we parse the string from left to right, collapsing as we go. + let mut index: usize = 0; + let end_of_string = full_name.len(); + let mut parsed_name = String::new(); + + while index < end_of_string { + let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); + + // Collapse everything up to the next special character, + // then skip over it + if let Some(special_character_index) = rest_of_string.find(|c: char| { + (c == ' ') + || (c == '<') + || (c == '>') + || (c == '(') + || (c == ')') + || (c == '[') + || (c == ']') + || (c == ',') + || (c == ';') + }) { + let segment_to_collapse = rest_of_string + .get(0..special_character_index) + .unwrap_or_default(); + parsed_name += collapse_type_name(segment_to_collapse); + // Insert the special character + let special_character = + &rest_of_string[special_character_index..=special_character_index]; + parsed_name.push_str(special_character); + + match special_character { + ">" | ")" | "]" + if rest_of_string[special_character_index + 1..].starts_with("::") => + { + parsed_name.push_str("::"); + // Move the index past the "::" + index += special_character_index + 3; + } + // Move the index just past the special character + _ => index += special_character_index + 1, + } + } else { + // If there are no special characters left, we're done! + parsed_name += collapse_type_name(rest_of_string); + index = end_of_string; + } + } + parsed_name +} + +#[inline(always)] +fn collapse_type_name(string: &str) -> &str { + string.split("::").last().unwrap() +} + +#[cfg(test)] +mod name_formatting_tests { + use super::get_short_name; + + #[test] + fn trivial() { + assert_eq!(get_short_name("test_system"), "test_system"); + } + + #[test] + fn path_separated() { + assert_eq!( + get_short_name("bevy_prelude::make_fun_game"), + "make_fun_game".to_string() + ); + } + + #[test] + fn tuple_type() { + assert_eq!( + get_short_name("(String, String)"), + "(String, String)".to_string() + ); + } + + #[test] + fn array_type() { + assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string()); + } + + #[test] + fn trivial_generics() { + assert_eq!(get_short_name("a"), "a".to_string()); + } + + #[test] + fn multiple_type_parameters() { + assert_eq!(get_short_name("a"), "a".to_string()); + } + + #[test] + fn generics() { + assert_eq!( + get_short_name("bevy_render::camera::camera::extract_cameras"), + "extract_cameras".to_string() + ); + } + + #[test] + fn nested_generics() { + assert_eq!( + get_short_name("bevy::mad_science::do_mad_science, bavy::TypeSystemAbuse>"), + "do_mad_science, TypeSystemAbuse>".to_string() + ); + } + + #[test] + fn sub_path_after_closing_bracket() { + assert_eq!( + get_short_name("bevy_asset::assets::Assets::asset_event_system"), + "Assets::asset_event_system".to_string() + ); + assert_eq!( + get_short_name("(String, String)::default"), + "(String, String)::default".to_string() + ); + assert_eq!( + get_short_name("[i32; 16]::default"), + "[i32; 16]::default".to_string() + ); + } +} diff --git a/framework_crates/bones_utils/src/ptr.rs b/framework_crates/bones_utils/src/ptr.rs new file mode 100644 index 0000000000..4877feabec --- /dev/null +++ b/framework_crates/bones_utils/src/ptr.rs @@ -0,0 +1,29 @@ +use std::ptr::NonNull; + +use bevy_ptr::{Ptr, PtrMut}; + +/// Extension trait with utils for [`PtrMut`]. +pub trait PtrMutExt { + /// Unsafely alter the lifetime of this [`PtrMut`]. + /// # Safety + /// You must ensure that the data referenced by this pointer will be valid for the new lifetime. + unsafe fn transmute_lifetime<'new>(self) -> PtrMut<'new>; +} +impl<'a> PtrMutExt for PtrMut<'a> { + unsafe fn transmute_lifetime<'new>(self) -> PtrMut<'new> { + PtrMut::new(NonNull::new_unchecked(self.as_ptr())) + } +} + +/// Extension trait with utils for [`Ptr`]. +pub trait PtrExt { + /// Unsafely alter the lifetime of this [`Ptr`]. + /// # Safety + /// You must ensure that the data referenced by this pointer will be valid for the new lifetime. + unsafe fn transmute_lifetime<'new>(self) -> Ptr<'new>; +} +impl<'a> PtrExt for Ptr<'a> { + unsafe fn transmute_lifetime<'new>(self) -> Ptr<'new> { + Ptr::new(NonNull::new_unchecked(self.as_ptr())) + } +} diff --git a/crates/bones_matchmaker/CHANGELOG.md b/other_crates/bones_matchmaker/CHANGELOG.md similarity index 100% rename from crates/bones_matchmaker/CHANGELOG.md rename to other_crates/bones_matchmaker/CHANGELOG.md diff --git a/other_crates/bones_matchmaker/Cargo.toml b/other_crates/bones_matchmaker/Cargo.toml new file mode 100644 index 0000000000..a77f23d5b8 --- /dev/null +++ b/other_crates/bones_matchmaker/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bones_matchmaker" +description = "Simple matchmaking server for games." +version = "0.2.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = "1.0" +bevy_tasks = "0.11" +bytes = "1.2" +either = "1.8" +futures-lite = "1.12" +once_cell = "1.15" +scc = "1.0" +rcgen = "0.11" +tracing = "0.1" +rand = "0.8" +bones_matchmaker_proto = { version = "0.2", path = "../bones_matchmaker_proto" } +clap = { version = "4.0", features = ["derive", "env"] } +futures = { version = "0.3", default-features = false, features = ["std", "async-await"] } +postcard = { version = "1.0", default-features = false, features = ["alloc"] } +quinn = { version = "0.10", default-features = false, features = ["futures-io", "native-certs", "tls-rustls"] } +quinn_runtime_bevy = { version = "0.3", path = "../quinn_runtime_bevy" } +rustls = { version = "0.21", features = ["dangerous_configuration", "quic"] } +serde = { version = "1.0", features = ["derive"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[dev-dependencies] +async-io = "1.9" diff --git a/crates/bones_matchmaker/README.md b/other_crates/bones_matchmaker/README.md similarity index 100% rename from crates/bones_matchmaker/README.md rename to other_crates/bones_matchmaker/README.md diff --git a/crates/bones_matchmaker/examples/matchmaker_client.rs b/other_crates/bones_matchmaker/examples/matchmaker_client.rs similarity index 100% rename from crates/bones_matchmaker/examples/matchmaker_client.rs rename to other_crates/bones_matchmaker/examples/matchmaker_client.rs diff --git a/crates/bones_matchmaker/src/certs.rs b/other_crates/bones_matchmaker/src/certs.rs similarity index 100% rename from crates/bones_matchmaker/src/certs.rs rename to other_crates/bones_matchmaker/src/certs.rs diff --git a/crates/bones_matchmaker/src/cli.rs b/other_crates/bones_matchmaker/src/cli.rs similarity index 100% rename from crates/bones_matchmaker/src/cli.rs rename to other_crates/bones_matchmaker/src/cli.rs diff --git a/crates/bones_matchmaker/src/lib.rs b/other_crates/bones_matchmaker/src/lib.rs similarity index 100% rename from crates/bones_matchmaker/src/lib.rs rename to other_crates/bones_matchmaker/src/lib.rs diff --git a/crates/bones_matchmaker/src/main.rs b/other_crates/bones_matchmaker/src/main.rs similarity index 100% rename from crates/bones_matchmaker/src/main.rs rename to other_crates/bones_matchmaker/src/main.rs diff --git a/crates/bones_matchmaker/src/matchmaker.rs b/other_crates/bones_matchmaker/src/matchmaker.rs similarity index 100% rename from crates/bones_matchmaker/src/matchmaker.rs rename to other_crates/bones_matchmaker/src/matchmaker.rs diff --git a/crates/bones_matchmaker/src/proxy.rs b/other_crates/bones_matchmaker/src/proxy.rs similarity index 100% rename from crates/bones_matchmaker/src/proxy.rs rename to other_crates/bones_matchmaker/src/proxy.rs diff --git a/crates/bones_matchmaker_proto/CHANGELOG.md b/other_crates/bones_matchmaker_proto/CHANGELOG.md similarity index 100% rename from crates/bones_matchmaker_proto/CHANGELOG.md rename to other_crates/bones_matchmaker_proto/CHANGELOG.md diff --git a/other_crates/bones_matchmaker_proto/Cargo.toml b/other_crates/bones_matchmaker_proto/Cargo.toml new file mode 100644 index 0000000000..18441691b9 --- /dev/null +++ b/other_crates/bones_matchmaker_proto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bones_matchmaker_proto" +description = "Network protocol types for the Bones matchmaking server." +version = "0.2.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/crates/bones_matchmaker_proto/README.md b/other_crates/bones_matchmaker_proto/README.md similarity index 100% rename from crates/bones_matchmaker_proto/README.md rename to other_crates/bones_matchmaker_proto/README.md diff --git a/crates/bones_matchmaker_proto/src/lib.rs b/other_crates/bones_matchmaker_proto/src/lib.rs similarity index 100% rename from crates/bones_matchmaker_proto/src/lib.rs rename to other_crates/bones_matchmaker_proto/src/lib.rs diff --git a/crates/quinn_runtime_bevy/CHANGELOG.md b/other_crates/quinn_runtime_bevy/CHANGELOG.md similarity index 100% rename from crates/quinn_runtime_bevy/CHANGELOG.md rename to other_crates/quinn_runtime_bevy/CHANGELOG.md diff --git a/other_crates/quinn_runtime_bevy/Cargo.toml b/other_crates/quinn_runtime_bevy/Cargo.toml new file mode 100644 index 0000000000..3c73bc673e --- /dev/null +++ b/other_crates/quinn_runtime_bevy/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quinn_runtime_bevy" +description = "Quinn runtime implementation built on Bevy's IO task pool." +version = "0.3.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +async-executor = "1.4" +async-io = "1.9" +bevy_tasks = "0.11" +futures-lite = "1.0" +pin-project = "1.0" +quinn = { version = "0.10", default-features = false, features = ["native-certs", "tls-rustls"] } +quinn-udp = { version = "0.4", default-features = false } diff --git a/crates/quinn_runtime_bevy/README.md b/other_crates/quinn_runtime_bevy/README.md similarity index 100% rename from crates/quinn_runtime_bevy/README.md rename to other_crates/quinn_runtime_bevy/README.md diff --git a/crates/quinn_runtime_bevy/src/lib.rs b/other_crates/quinn_runtime_bevy/src/lib.rs similarity index 100% rename from crates/quinn_runtime_bevy/src/lib.rs rename to other_crates/quinn_runtime_bevy/src/lib.rs diff --git a/crates/type_ulid/CHANGELOG.md b/other_crates/type_ulid/CHANGELOG.md similarity index 100% rename from crates/type_ulid/CHANGELOG.md rename to other_crates/type_ulid/CHANGELOG.md diff --git a/other_crates/type_ulid/Cargo.toml b/other_crates/type_ulid/Cargo.toml new file mode 100644 index 0000000000..8af7a7e86e --- /dev/null +++ b/other_crates/type_ulid/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "type_ulid" +description = "Trait for associating ULIDs with Rust types." +version = "0.2.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +default = ["std"] +# Implement TypeUlid for basic types in the standard library +std = [] + +[dependencies] +type_ulid_macros = { version = "^0.2.0", path = "./macros" } +ulid = { version = "1.0", default-features = false } diff --git a/crates/type_ulid/macros/CHANGELOG.md b/other_crates/type_ulid/macros/CHANGELOG.md similarity index 100% rename from crates/type_ulid/macros/CHANGELOG.md rename to other_crates/type_ulid/macros/CHANGELOG.md diff --git a/other_crates/type_ulid/macros/Cargo.toml b/other_crates/type_ulid/macros/Cargo.toml new file mode 100644 index 0000000000..56c97e30d9 --- /dev/null +++ b/other_crates/type_ulid/macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "type_ulid_macros" +description = "Macros for the type_ulid crate." +version = "0.2.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.43" +quote = "1.0.21" +syn = { version = "1.0.100", features = ["extra-traits"] } +ulid = { version = "1.0.0", default-features = false } diff --git a/crates/type_ulid/macros/src/lib.rs b/other_crates/type_ulid/macros/src/lib.rs similarity index 100% rename from crates/type_ulid/macros/src/lib.rs rename to other_crates/type_ulid/macros/src/lib.rs diff --git a/crates/type_ulid/src/lib.rs b/other_crates/type_ulid/src/lib.rs similarity index 100% rename from crates/type_ulid/src/lib.rs rename to other_crates/type_ulid/src/lib.rs diff --git a/rust-toolchain b/rust-toolchain index 4934985655..12816e6276 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.69.0 +1.71 diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index b8783d7054..0000000000 --- a/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Opinionated game meta-engine built on Bevy. - -#![warn(missing_docs)] -// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable -#![cfg_attr(doc, allow(unknown_lints))] -#![deny(rustdoc::all)] - -#[doc(inline)] -pub use {bones_asset as asset, bones_ecs as ecs, bones_input as input, bones_render as render}; - -#[cfg(feature = "bevy")] -pub use bones_bevy_utils as bevy_utils; - -/// Bones lib prelude -pub mod prelude { - pub use crate::{ - animation::prelude::*, asset::prelude::*, camera::*, ecs::prelude::*, input::prelude::*, - render::prelude::*, FrameTime, - }; - - #[cfg(feature = "bevy")] - pub use crate::bevy_utils::*; -} -use prelude::*; - -pub mod animation; -pub mod camera; - -/// This is a resource that stores the game's fixed frame time. -/// -/// For instance, if the game logic is meant to run at a fixed frame rate of 60 fps, then this -/// should be `1.0 / 60.0`. -/// -/// This resource is used by animation or other timing-sensitive code when running code that should -/// run the same, regardless of the games fixed updates-per-second. -#[derive(Clone, TypeUlid, Deref, DerefMut)] -#[ulid = "01GP1VWPKF2H7CKDCD987PHBWV"] -pub struct FrameTime(pub f32); - -impl Default for FrameTime { - fn default() -> Self { - Self(1.0 / 60.0) - } -} - -/// Install the `bones_lib` systems for things such as animation etc. into a [`SystemStages`]. -pub fn install(stages: &mut SystemStages) { - animation::install(stages); - camera::install(stages); -} diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000000..df1b55010a --- /dev/null +++ b/taplo.toml @@ -0,0 +1,3 @@ +[formatting] +align_entries = true +column_width = 120