├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── contrib ├── minimum-nphysics2d │ ├── Cargo.toml │ └── src │ │ ├── components │ │ ├── mod.rs │ │ └── physics.rs │ │ ├── lib.rs │ │ ├── math_conversions.rs │ │ ├── resources │ │ ├── mod.rs │ │ └── physics.rs │ │ └── systems │ │ ├── mod.rs │ │ └── physics_systems.rs ├── minimum-sdl2 │ ├── Cargo.toml │ ├── fonts │ │ ├── feather.ttf │ │ ├── fontawesome-470.ttf │ │ ├── materialdesignicons-webfont.ttf │ │ └── mplus-1p-regular.ttf │ └── src │ │ ├── imgui.rs │ │ ├── input.rs │ │ ├── lib.rs │ │ └── resources │ │ ├── mod.rs │ │ ├── sdl2_imgui.rs │ │ └── sdl2_window.rs ├── minimum-skulpin │ ├── Cargo.toml │ └── src │ │ ├── components │ │ ├── draw.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── math_conversions.rs │ │ └── resources │ │ ├── canvas_draw.rs │ │ └── mod.rs └── minimum-winit │ ├── Cargo.toml │ ├── fonts │ ├── feather.ttf │ ├── fontawesome-470.ttf │ ├── materialdesignicons-webfont.ttf │ └── mplus-1p-regular.ttf │ └── src │ ├── imgui.rs │ ├── input.rs │ ├── lib.rs │ └── resources │ ├── mod.rs │ ├── winit_imgui.rs │ └── winit_window.rs ├── docs ├── atelier-legion-integration-demo.md ├── component_transform_example.jpg ├── examples │ └── example-sdl2.md ├── index.md └── tutorial │ ├── tutorial001_dev_environment_setup.md │ ├── tutorial002_creating_prefabs.md │ ├── tutorial003_save_and_load_prefabs.md │ ├── tutorial004_cooking_prefabs.md │ ├── tutorial005_spawning_prefabs.md │ ├── tutorial006_transactions.md │ └── tutorial007_creating_prefab_overrides.md ├── examples ├── assets │ ├── demo_level.prefab │ └── demo_level.prefab.meta ├── example-sdl2 │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── registration.rs │ │ └── systems │ │ ├── app_control_systems.rs │ │ ├── draw_systems.rs │ │ └── mod.rs ├── example-shared │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── resources │ │ ├── fps_text.rs │ │ └── mod.rs │ │ ├── systems │ │ ├── fps_text_systems.rs │ │ └── mod.rs │ │ └── viewport.rs ├── example-winit │ ├── Cargo.toml │ └── src │ │ ├── app.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── systems │ │ ├── app_control_systems.rs │ │ ├── draw_systems.rs │ │ └── mod.rs ├── fonts │ ├── feather.ttf │ ├── fontawesome-470.ttf │ ├── materialdesignicons-webfont.ttf │ └── mplus-1p-regular.ttf └── tutorial │ ├── Cargo.toml │ ├── examples │ ├── tutorial001_dev_environment_setup.rs │ ├── tutorial002_creating_prefabs.rs │ ├── tutorial003_save_and_load_prefabs.rs │ ├── tutorial004_cooking_prefabs.rs │ ├── tutorial005_spawning_prefabs.rs │ ├── tutorial006_transactions.rs │ └── tutorial007_creating_prefab_overrides.rs │ └── src │ └── lib.rs ├── minimum-editor ├── Cargo.toml └── src │ ├── components │ ├── editor_metadata_component.rs │ └── mod.rs │ ├── inspect.rs │ ├── lib.rs │ ├── resources │ ├── editor_draw_2d.rs │ ├── editor_draw_3d.rs │ ├── editor_inspect_registry.rs │ ├── editor_selection.rs │ ├── editor_settings.rs │ ├── editor_state.rs │ └── mod.rs │ ├── select.rs │ └── systems │ ├── entity_list_window.rs │ ├── gizmos_2d.rs │ ├── gizmos_3d.rs │ ├── inspector_window.rs │ ├── main_menu.rs │ ├── mod.rs │ └── selection.rs ├── minimum-game ├── Cargo.toml └── src │ ├── imgui.rs │ ├── input.rs │ ├── lib.rs │ ├── resources │ ├── app_control.rs │ ├── camera.rs │ ├── debug_draw_2d.rs │ ├── debug_draw_3d.rs │ ├── imgui.rs │ ├── input.rs │ ├── mod.rs │ ├── time.rs │ └── viewport.rs │ └── systems │ ├── input_systems.rs │ ├── mod.rs │ └── time_systems.rs ├── minimum-kernel ├── Cargo.toml └── src │ ├── asset_storage.rs │ ├── component_registry.rs │ ├── lib.rs │ ├── pipeline │ ├── mod.rs │ └── prefab │ │ ├── assets.rs │ │ ├── importers.rs │ │ └── mod.rs │ ├── prefab_cooking.rs │ ├── resources │ ├── asset.rs │ ├── component_registry.rs │ └── mod.rs │ ├── systems │ ├── asset_manager_systems.rs │ └── mod.rs │ └── util.rs ├── minimum-math ├── Cargo.toml └── src │ ├── bounds.rs │ ├── functions.rs │ ├── lib.rs │ ├── math.rs │ ├── matrix.rs │ └── na_convert.rs ├── minimum-transform ├── Cargo.toml └── src │ ├── components │ ├── mod.rs │ └── transform.rs │ └── lib.rs ├── minimum ├── Cargo.toml └── src │ ├── daemon.rs │ └── lib.rs └── rustfmt.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | toolchain: [stable, nightly] 13 | os: [windows-2019, ubuntu-20.04, macos-10.15] 14 | exclude: 15 | - os: macos-10.15 16 | toolchain: nightly 17 | - os: windows-2019 18 | toolchain: nightly 19 | runs-on: ${{ matrix.os }} 20 | needs: clean 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: ${{ matrix.toolchain }} 27 | override: true 28 | 29 | - uses: actions/cache@v2 30 | with: 31 | path: | 32 | target 33 | key: ${{ runner.os }}-cargo-check-test-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} 34 | 35 | - name: Build 36 | run: cargo check 37 | env: 38 | CARGO_INCREMENTAL: 0 39 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 40 | 41 | - name: Run tests 42 | run: cargo test --workspace 43 | if: ${{ runner.os == 'Linux' }} 44 | env: 45 | CARGO_INCREMENTAL: 0 46 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 47 | 48 | clean: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v2 52 | 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | toolchain: stable 56 | components: rustfmt, clippy 57 | override: true 58 | 59 | - name: Check the format 60 | run: cargo fmt --all -- --check 61 | 62 | # TODO: Enable this 63 | # - name: Run clippy 64 | # run: > 65 | # cargo clippy 66 | # --all-targets 67 | # --all-features 68 | # -- 69 | # -D warnings 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | *.sh 6 | .assets_db -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "contrib/minimum-sdl2", 4 | "contrib/minimum-winit", 5 | "contrib/minimum-nphysics2d", 6 | "contrib/minimum-skulpin", 7 | "examples/example-winit", 8 | "examples/example-sdl2", 9 | "examples/example-shared", 10 | "examples/tutorial", 11 | "minimum-kernel", 12 | "minimum-math", 13 | "minimum-transform", 14 | "minimum-editor", 15 | "minimum" 16 | ] 17 | 18 | 19 | [patch.crates-io] 20 | 21 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minimum 2 | 3 | An experimental game development framework that provides basic tooling and a content authoring workflow. Minimum has 4 | easy-to-use extension points for integrating custom and 3rd-party libraries with your game logic. 5 | 6 | This library is best suited for use by those who want to start with something thin and bring their own tech to put on 7 | top of it. 8 | 9 | [![Build Status](https://travis-ci.org/aclysma/minimum.svg?branch=master)](https://travis-ci.org/aclysma/minimum) 10 | 11 | ## Features 12 | 13 | Editing functionality is currently limited, but the core loop is implemented: 14 | * Entities with components can be loaded from file and saved to file 15 | * Entities can be selected and their components can be edited (at design-time and run-time) 16 | * Entities and components can be added or removed 17 | * Entities can be moved, scaled, and rotated with undo support 18 | * Can start/stop/reset the simulation 19 | 20 | Youtube Video: 21 | 22 | [![IMAGE ALT TEXT](http://img.youtube.com/vi/9Vwi29RuQBE/0.jpg)](https://www.youtube.com/watch?v=9Vwi29RuQBE&feature=youtu.be "Video of Editor in Use") 23 | 24 | ## Status 25 | 26 | This is probably more experimental than what most people are looking for. Unless you're looking for a totally blank 27 | slate with atelier assets and legion integrated, you'll probably be better off looking at something else. I plan to 28 | continue using this project to prove out new ideas, but I'm not updating it as regularly as many other projects. 29 | 30 | Please see the [docs](https://github.com/aclysma/minimum/blob/master/docs/index.md)! 31 | 32 | ## Philosophy 33 | 34 | Game engines are usually huge - and this poses difficulty for OSS projects. It's very difficult for a large OSS project 35 | to have a unified focus and vision. This is especially true in games where potential contributors may want very 36 | different things out of the engine that they are trying to collaborate on. 37 | 38 | By reducing the eponymous "engine" to a kernel, we can push these decisions further down the chain. This allows us to 39 | share both the kernel as well as the upstream integrations - since those are opt-in. There are other benefits too - it 40 | eases distributed development by allowing many people to own small pieces. This flexibility also means that contributors 41 | can choose the work that fits their interest and skill set. 42 | 43 | To achieve interoperability, we will need a common protocol for these integrations to work well together. So we will 44 | standardize on a common base set of components, resources, and systems. For example, a common transform component, or 45 | a common way to represent input state (it would be up to the downstream user to pick an input/windowing implementation 46 | that populates this.) 47 | 48 | ## Alternatives 49 | 50 | For more batteries-included solutions in rust, I would look at amethyst, bevy, coffee, or ggez. The main difference is that 51 | these libraries all tend to take over your game loop or assume you will use a particular windowing or rendering 52 | solution. 53 | 54 | minimum currently requires that you bring your own renderer (including support for imgui.) However, this also gives you 55 | the flexibility to choose your own solutions. The hope going forward is that the "kernel" can remain decoupled from 56 | rendering so that end-users can pick a solution that fits their game best. 57 | 58 | ## Directory Map 59 | 60 | /contrib - Holds integrations for upstream libraries (such as nphysics, sdl2, and winit) 61 | /docs - Some concept-level documentation 62 | /examples 63 | * example-sdl2 - A working implementation using SDL2 for windowing 64 | * example-winit - A working implementation using winit for windowing 65 | * example-shared - Shared code between the examples that isn't useful outside the examples 66 | * tutorial/examples - Completed code samples from the tutorial in docs 67 | /minimum-editor - Editing logic that you do not need to ship in your game 68 | /minimum-game - Common protocol for game-level logic that would ship 69 | /minimum-kernel - Legion, atelier, and prefab integration 70 | /minimum-math - Some wrappers around glam math types to make them friendly with inspection 71 | /minimum-transform - Protocol/structure that defines placement of objects 72 | 73 | ## Running the Demo 74 | 75 | ``` 76 | git clone https://github.com/aclysma/minimum.git 77 | cd minimum 78 | cd examples 79 | cargo run --package example-winit 80 | ``` 81 | 82 | Or alternatively `cargo run --package example-sdl2` if you'd like to run the same demo using SDL2. 83 | 84 | ## Roadmap 85 | 86 | This project is not intended to become a full, ready-to-use engine. It is an experimental "kernel" of a game engine that 87 | can be integrated in downstream projects to solve some tricky problems with editor workflow and asset management. 88 | 89 | ## License 90 | 91 | Licensed under either of 92 | 93 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 94 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 95 | 96 | at your option. 97 | 98 | The fonts directory contains several fonts under their own licenses: 99 | * [Feather](https://github.com/AT-UI/feather-font), MIT 100 | * [Material Design Icons](https://materialdesignicons.com), SIL OFL 1.1 101 | * [FontAwesome 4.7.0](https://fontawesome.com/v4.7.0/license/), available under SIL OFL 1.1 102 | * [`mplus-1p-regular.ttf`](http://mplus-fonts.osdn.jp), available under its own license. 103 | 104 | ### Contribution 105 | 106 | Unless you explicitly state otherwise, any contribution intentionally 107 | submitted for inclusion in the work by you, as defined in the Apache-2.0 108 | license, shall be dual licensed as above, without any additional terms or 109 | conditions. 110 | 111 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). 112 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-nphysics2d" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | imgui-inspect-derive = "0.6" 13 | imgui-inspect = "0.6" 14 | 15 | imgui = "0.5" 16 | 17 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 18 | 19 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 20 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 21 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 22 | 23 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 24 | 25 | glam = { version = "0.8.5", features = ["serde"] } 26 | 27 | # For physics 28 | nalgebra = { version = "0.18", features = [ "serde-serialize" ] } 29 | nalgebra-glm = "0.4" 30 | ncollide2d = "0.20" 31 | ncollide3d = "0.20" 32 | nphysics2d = "0.12" 33 | 34 | crossbeam-channel = "0.4" 35 | 36 | #structopt = "0.3" 37 | serde = "1" 38 | uuid = "0.8" 39 | type-uuid = "0.1" 40 | #inventory = "0.1" 41 | itertools = "0.8" 42 | 43 | serde-diff = "0.3" 44 | 45 | log="0.4" -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | mod physics; 2 | pub use physics::RigidBodyComponent; 3 | pub use physics::RigidBodyBoxComponentDef; 4 | pub use physics::RigidBodyBallComponentDef; 5 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | #[macro_use] 6 | extern crate itertools; 7 | 8 | pub mod components; 9 | pub mod resources; 10 | pub mod systems; 11 | 12 | pub mod math_conversions; 13 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/math_conversions.rs: -------------------------------------------------------------------------------- 1 | pub use minimum::math::na_convert::*; 2 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod physics; 2 | pub use physics::PhysicsResource; 3 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/resources/physics.rs: -------------------------------------------------------------------------------- 1 | use glam::Vec2; 2 | use nphysics2d::object::{DefaultBodySet, DefaultColliderSet, DefaultBodyHandle}; 3 | use nphysics2d::force_generator::DefaultForceGeneratorSet; 4 | use nphysics2d::joint::DefaultJointConstraintSet; 5 | use nphysics2d::world::{DefaultMechanicalWorld, DefaultGeometricalWorld}; 6 | 7 | use crossbeam_channel::{Sender, Receiver}; 8 | 9 | // Handles setting up the physics system and stepping it 10 | pub struct PhysicsResource { 11 | pub geometrical_world: DefaultGeometricalWorld, 12 | pub mechanical_world: DefaultMechanicalWorld, 13 | pub bodies: DefaultBodySet, 14 | pub colliders: DefaultColliderSet, 15 | pub joint_constraints: DefaultJointConstraintSet, 16 | pub force_generators: DefaultForceGeneratorSet, 17 | pub delete_body_tx: Sender, 18 | pub delete_body_rx: Receiver, 19 | } 20 | 21 | impl PhysicsResource { 22 | pub fn new(gravity: Vec2) -> Self { 23 | let geometrical_world = DefaultGeometricalWorld::::new(); 24 | let mechanical_world = 25 | DefaultMechanicalWorld::new(crate::math_conversions::vec2_glam_to_glm(gravity)); 26 | 27 | let bodies = DefaultBodySet::::new(); 28 | let colliders = DefaultColliderSet::new(); 29 | let joint_constraints = DefaultJointConstraintSet::::new(); 30 | let force_generators = DefaultForceGeneratorSet::::new(); 31 | 32 | let (delete_body_tx, delete_body_rx) = crossbeam_channel::unbounded(); 33 | 34 | PhysicsResource { 35 | geometrical_world, 36 | mechanical_world, 37 | bodies, 38 | colliders, 39 | joint_constraints, 40 | force_generators, 41 | delete_body_tx, 42 | delete_body_rx, 43 | } 44 | } 45 | 46 | pub fn delete_body_tx(&self) -> &Sender { 47 | &self.delete_body_tx 48 | } 49 | 50 | fn handle_deletes(&mut self) { 51 | // Delete any bodies that were destroyed since the previous update 52 | for body_to_delete in self.delete_body_rx.try_iter() { 53 | self.bodies.remove(body_to_delete); 54 | 55 | // This is a workaround for this issue: https://github.com/rustsim/nphysics/issues/248 56 | // It's not a long term fix since a linear search across all colliders to find the ones 57 | // attached to this body is expensive. This is only necessary if creating/destroying 58 | // entities in the same frame (between step() and maintain() calls) 59 | let mut colliders_to_remove = vec![]; 60 | for (collider_handle, collider) in self.colliders.iter() { 61 | if collider.body() == body_to_delete { 62 | colliders_to_remove.push(collider_handle); 63 | } 64 | } 65 | 66 | for collider_to_remove in colliders_to_remove { 67 | self.colliders.remove(collider_to_remove); 68 | } 69 | } 70 | } 71 | 72 | pub fn maintain(&mut self) { 73 | self.handle_deletes(); 74 | 75 | self.mechanical_world.maintain( 76 | &mut self.geometrical_world, 77 | &mut self.bodies, 78 | &mut self.colliders, 79 | &mut self.joint_constraints, 80 | ); 81 | } 82 | 83 | pub fn step(&mut self) { 84 | self.handle_deletes(); 85 | 86 | // Run the simulation. 87 | self.mechanical_world.step( 88 | &mut self.geometrical_world, 89 | &mut self.bodies, 90 | &mut self.colliders, 91 | &mut self.joint_constraints, 92 | &mut self.force_generators, 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod physics_systems; 2 | pub use physics_systems::update_physics; 3 | pub use physics_systems::read_from_physics; 4 | -------------------------------------------------------------------------------- /contrib/minimum-nphysics2d/src/systems/physics_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use minimum::resources::TimeResource; 4 | use crate::resources::PhysicsResource; 5 | 6 | use minimum::components::TransformComponent; 7 | use crate::components::RigidBodyComponent; 8 | use crate::math_conversions::{vec2_glm_to_glam}; 9 | 10 | pub fn update_physics(schedule: &mut legion::systems::Builder) { 11 | // Do a physics simulation timestep 12 | schedule.add_system( 13 | SystemBuilder::new("update physics") 14 | .write_resource::() 15 | .read_resource::() 16 | .build(|_, _, (physics, time), _| { 17 | if time.is_simulation_paused() { 18 | physics.maintain() 19 | } else { 20 | physics.step(); 21 | } 22 | }), 23 | ); 24 | } 25 | 26 | pub fn read_from_physics(schedule: &mut legion::systems::Builder) { 27 | schedule.add_system( 28 | SystemBuilder::new("read physics data") 29 | .read_resource::() 30 | .with_query(<(Write, Read)>::query()) 31 | .build(|_, world, physics, query| { 32 | for (transform, body) in query.iter_mut(world) { 33 | if let Some(rigid_body) = physics.bodies.rigid_body(body.handle) { 34 | let position = rigid_body.position().translation.vector; 35 | //TODO: Conversion from 2D to 3D - ideally we'd use 3D physics with a constraint to force 2D 36 | let v3 = vec2_glm_to_glam(position).extend(transform.position().z()); 37 | transform.set_position(v3); 38 | } 39 | } 40 | }), 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-sdl2" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | sdl2 = { version = ">=0.33" } 13 | 14 | imgui = "0.5" 15 | imgui-sdl2 = "0.12.0" 16 | 17 | glam = { version = "0.8.5", features = ["serde"] } 18 | 19 | log="0.4" 20 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/fonts/feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-sdl2/fonts/feather.ttf -------------------------------------------------------------------------------- /contrib/minimum-sdl2/fonts/fontawesome-470.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-sdl2/fonts/fontawesome-470.ttf -------------------------------------------------------------------------------- /contrib/minimum-sdl2/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-sdl2/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /contrib/minimum-sdl2/fonts/mplus-1p-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-sdl2/fonts/mplus-1p-regular.ttf -------------------------------------------------------------------------------- /contrib/minimum-sdl2/src/input.rs: -------------------------------------------------------------------------------- 1 | use sdl2::keyboard::Keycode; 2 | use sdl2::mouse::MouseButton; 3 | 4 | use sdl2::event::Event; 5 | 6 | use minimum::input::InputState; 7 | 8 | use minimum::input as minimum_input; 9 | 10 | #[derive(Copy, Clone)] 11 | pub struct Sdl2KeyboardKey { 12 | keycode: Keycode, 13 | } 14 | 15 | impl Sdl2KeyboardKey { 16 | pub fn new(keycode: Keycode) -> Self { 17 | Sdl2KeyboardKey { keycode } 18 | } 19 | } 20 | 21 | impl Into for Sdl2KeyboardKey { 22 | fn into(self) -> minimum_input::KeyboardKey { 23 | minimum_input::KeyboardKey(self.keycode as u8) 24 | } 25 | } 26 | 27 | #[derive(Copy, Clone)] 28 | pub struct Sdl2MouseButton { 29 | mouse_button: MouseButton, 30 | } 31 | 32 | impl Sdl2MouseButton { 33 | pub fn new(mouse_button: MouseButton) -> Self { 34 | Sdl2MouseButton { mouse_button } 35 | } 36 | } 37 | 38 | impl Into for Sdl2MouseButton { 39 | fn into(self) -> minimum_input::MouseButton { 40 | let button_index = match self.mouse_button { 41 | MouseButton::Left => 0, 42 | MouseButton::Right => 1, 43 | MouseButton::Middle => 2, 44 | MouseButton::X1 => 3, 45 | MouseButton::X2 => 4, 46 | MouseButton::Unknown => 5, 47 | }; 48 | 49 | minimum_input::MouseButton(button_index) 50 | } 51 | } 52 | 53 | /// Call when winit sends an event 54 | pub fn handle_sdl2_event( 55 | event: &Event, 56 | input_state: &mut InputState, 57 | ) { 58 | let _is_close_requested = false; 59 | 60 | match event { 61 | Event::KeyDown { 62 | keycode, repeat: _, .. 63 | } => handle_keyboard_event(input_state, keycode, minimum_input::ButtonState::Pressed), 64 | Event::KeyUp { 65 | keycode, repeat: _, .. 66 | } => handle_keyboard_event(input_state, keycode, minimum_input::ButtonState::Released), 67 | Event::MouseButtonDown { mouse_btn, .. } => { 68 | handle_mouse_button_event(input_state, mouse_btn, minimum_input::ButtonState::Pressed) 69 | } 70 | Event::MouseButtonUp { mouse_btn, .. } => { 71 | handle_mouse_button_event(input_state, mouse_btn, minimum_input::ButtonState::Released) 72 | } 73 | Event::MouseMotion { x, y, .. } => { 74 | input_state.handle_mouse_move_event(glam::Vec2::new(*x as f32, *y as f32)); 75 | } 76 | Event::MouseWheel { x, y, .. } => { 77 | input_state.handle_mouse_wheel_event(minimum_input::MouseScrollDelta::new( 78 | *x as f32, *y as f32, 79 | )); 80 | } 81 | 82 | // Ignore any other events 83 | _ => (), 84 | } 85 | } 86 | 87 | fn handle_mouse_button_event( 88 | input_state: &mut InputState, 89 | mouse_btn: &MouseButton, 90 | button_state: minimum_input::ButtonState, 91 | ) { 92 | input_state.handle_mouse_button_event(Sdl2MouseButton::new(*mouse_btn).into(), button_state) 93 | } 94 | 95 | fn handle_keyboard_event( 96 | input_state: &mut InputState, 97 | keycode: &Option, 98 | button_state: minimum_input::ButtonState, 99 | ) { 100 | if let Some(kc) = keycode { 101 | input_state.handle_keyboard_event(Sdl2KeyboardKey::new(*kc).into(), button_state) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod resources; 2 | 3 | pub mod imgui; 4 | 5 | pub mod input; 6 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod sdl2_imgui; 2 | pub use sdl2_imgui::Sdl2ImguiManagerResource; 3 | 4 | mod sdl2_window; 5 | pub use sdl2_window::Sdl2WindowResource; 6 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/src/resources/sdl2_imgui.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use crate::imgui::Sdl2ImguiManager; 3 | 4 | // For now just wrap the input helper that skulpin provides 5 | pub struct Sdl2ImguiManagerResource { 6 | pub sdl2_imgui_manager: Sdl2ImguiManager, 7 | } 8 | 9 | impl Sdl2ImguiManagerResource { 10 | /// Create a new TimeState. Default is not allowed because the current time affects the object 11 | #[allow(clippy::new_without_default)] 12 | pub fn new(sdl2_imgui_manager: Sdl2ImguiManager) -> Self { 13 | Sdl2ImguiManagerResource { sdl2_imgui_manager } 14 | } 15 | } 16 | 17 | impl Deref for Sdl2ImguiManagerResource { 18 | type Target = Sdl2ImguiManager; 19 | 20 | #[inline] 21 | fn deref(&self) -> &Self::Target { 22 | &self.sdl2_imgui_manager 23 | } 24 | } 25 | 26 | impl DerefMut for Sdl2ImguiManagerResource { 27 | #[inline] 28 | fn deref_mut(&mut self) -> &mut Self::Target { 29 | &mut self.sdl2_imgui_manager 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contrib/minimum-sdl2/src/resources/sdl2_window.rs: -------------------------------------------------------------------------------- 1 | use sdl2::video::Window; 2 | 3 | #[derive(Copy, Clone)] 4 | pub struct Sdl2WindowSize { 5 | pub width: u32, 6 | pub height: u32, 7 | } 8 | 9 | impl From<(u32, u32)> for Sdl2WindowSize { 10 | fn from(size: (u32, u32)) -> Self { 11 | Sdl2WindowSize { 12 | width: size.0, 13 | height: size.1, 14 | } 15 | } 16 | } 17 | 18 | pub struct Sdl2WindowResource { 19 | drawable_size: Sdl2WindowSize, 20 | } 21 | 22 | impl Sdl2WindowResource { 23 | pub fn new(window: &Window) -> Self { 24 | Sdl2WindowResource { 25 | drawable_size: window.drawable_size().into(), 26 | } 27 | } 28 | 29 | pub fn update( 30 | &mut self, 31 | window: &Window, 32 | ) { 33 | self.drawable_size = window.drawable_size().into(); 34 | } 35 | 36 | pub fn drawable_size(&self) -> Sdl2WindowSize { 37 | self.drawable_size 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-skulpin" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | skulpin = "0.11" 13 | 14 | imgui-inspect-derive = "0.6" 15 | imgui-inspect = "0.6" 16 | 17 | imgui = "0.5" 18 | #imgui-sdl2 = "0.12.0" 19 | 20 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 21 | 22 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 23 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 24 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 25 | 26 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 27 | 28 | 29 | nalgebra = { version = "0.18", features = [ "serde-serialize" ] } 30 | nalgebra-glm = "0.4" 31 | ncollide3d = "0.20" 32 | 33 | structopt = "0.3" 34 | serde = "1" 35 | uuid = "0.8" 36 | type-uuid = "0.1" 37 | image2 = { version = "0.11", features = [ "ser" ] } 38 | inventory = "0.1" 39 | 40 | serde-diff = "0.3" 41 | 42 | glam = { version = "0.8.5", features = ["serde"] } 43 | 44 | log="0.4" -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/components/draw.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_diff::SerdeDiff; 3 | use type_uuid::TypeUuid; 4 | use skulpin::skia_safe; 5 | use ncollide3d::world::CollisionWorld; 6 | use legion::world::World; 7 | use ncollide3d::pipeline::{CollisionGroups, GeometricQueryType}; 8 | use ncollide3d::shape::{Ball, Cuboid}; 9 | use ncollide3d::shape::ShapeHandle; 10 | use minimum::components::{TransformComponentDef}; 11 | use minimum::math::Vec3; 12 | use minimum::math::Vec4; 13 | use imgui_inspect_derive::Inspect; 14 | use legion::*; 15 | use minimum::resources::editor::OpenedPrefabState; 16 | 17 | use crate::math_conversions::{vec3_glam_to_glm, quat_glam_to_glm}; 18 | 19 | // A utility struct to describe color for a skia shape 20 | #[derive(Clone, Copy, Debug, Serialize, Deserialize, SerdeDiff, PartialEq, Inspect, Default)] 21 | pub struct PaintDef { 22 | #[serde_diff(opaque)] 23 | pub color: Vec4, 24 | pub stroke_width: f32, 25 | } 26 | 27 | pub struct Paint(pub std::sync::Mutex); 28 | unsafe impl Send for Paint {} 29 | unsafe impl Sync for Paint {} 30 | 31 | impl From for Paint { 32 | fn from(from: PaintDef) -> Self { 33 | let color = skia_safe::Color4f::new( 34 | from.color.x(), 35 | from.color.y(), 36 | from.color.z(), 37 | from.color.w(), 38 | ); 39 | 40 | let mut paint = skia_safe::Paint::new(color, None); 41 | paint.set_anti_alias(true); 42 | paint.set_style(skia_safe::paint::Style::Stroke); 43 | paint.set_stroke_width(from.stroke_width); 44 | 45 | Paint(std::sync::Mutex::new(paint)) 46 | } 47 | } 48 | 49 | // 50 | // Draw a box at the component's current location. Will be affected by scale, if the scale component 51 | // exists 52 | // 53 | #[derive(TypeUuid, Serialize, Deserialize, SerdeDiff, Debug, PartialEq, Clone, Inspect, Default)] 54 | #[uuid = "c05e5c27-58ca-4d68-b825-b20f67fdaf37"] 55 | pub struct DrawSkiaBoxComponentDef { 56 | #[serde_diff(opaque)] 57 | pub half_extents: Vec3, 58 | pub paint: PaintDef, 59 | } 60 | 61 | legion_prefab::register_component_type!(DrawSkiaBoxComponentDef); 62 | 63 | pub struct DrawSkiaBoxComponent { 64 | pub half_extents: Vec3, 65 | pub paint: Paint, 66 | } 67 | 68 | impl From for DrawSkiaBoxComponent { 69 | fn from(from: DrawSkiaBoxComponentDef) -> Self { 70 | DrawSkiaBoxComponent { 71 | half_extents: from.half_extents, 72 | paint: from.paint.into(), 73 | } 74 | } 75 | } 76 | 77 | impl minimum::editor::EditorSelectable for DrawSkiaBoxComponent { 78 | fn create_editor_selection_world( 79 | &self, 80 | collision_world: &mut CollisionWorld, 81 | _resources: &Resources, 82 | _opened_prefab: &OpenedPrefabState, 83 | world: &World, 84 | entity: Entity, 85 | ) { 86 | let entity_ref = world.entry_ref(entity).unwrap(); 87 | if let Ok(transform) = entity_ref.get_component::() { 88 | let mut half_extents = *self.half_extents; 89 | 90 | half_extents *= transform.scale(); 91 | 92 | let shape_handle = ShapeHandle::new(Cuboid::new(vec3_glam_to_glm(half_extents))); 93 | 94 | //TODO: This might be wrong 95 | let rotation = quat_glam_to_glm(transform.rotation_quat()); 96 | let rotation = nalgebra::UnitQuaternion::from_quaternion(rotation); 97 | collision_world.add( 98 | ncollide3d::math::Isometry::from_parts( 99 | nalgebra::Translation::from(vec3_glam_to_glm(*transform.position)), 100 | rotation, 101 | ), 102 | shape_handle, 103 | CollisionGroups::new(), 104 | GeometricQueryType::Proximity(0.001), 105 | entity, 106 | ); 107 | } 108 | } 109 | } 110 | 111 | // 112 | // Draw a circle at the component's current location. Will be affected by scale, if the scale 113 | // component exists 114 | // 115 | #[derive(TypeUuid, Serialize, Deserialize, SerdeDiff, Debug, PartialEq, Clone, Inspect, Default)] 116 | #[uuid = "e47f9943-d5bf-4e1b-9601-13e47d7b737c"] 117 | pub struct DrawSkiaCircleComponentDef { 118 | pub radius: f32, 119 | pub paint: PaintDef, 120 | } 121 | 122 | legion_prefab::register_component_type!(DrawSkiaCircleComponentDef); 123 | 124 | pub struct DrawSkiaCircleComponent { 125 | pub radius: f32, 126 | pub paint: Paint, 127 | } 128 | 129 | impl From for DrawSkiaCircleComponent { 130 | fn from(from: DrawSkiaCircleComponentDef) -> Self { 131 | DrawSkiaCircleComponent { 132 | radius: from.radius, 133 | paint: from.paint.into(), 134 | } 135 | } 136 | } 137 | 138 | impl minimum::editor::EditorSelectable for DrawSkiaCircleComponent { 139 | fn create_editor_selection_world( 140 | &self, 141 | collision_world: &mut CollisionWorld, 142 | _resources: &Resources, 143 | _opened_prefab: &OpenedPrefabState, 144 | world: &World, 145 | entity: Entity, 146 | ) { 147 | let entity_ref = world.entry_ref(entity).unwrap(); 148 | if let Ok(transform) = entity_ref.get_component::() { 149 | let mut radius = self.radius; 150 | radius *= transform.uniform_scale(); 151 | 152 | //TODO: Warn if radius is 0 153 | let shape_handle = ShapeHandle::new(Ball::new(radius.max(0.01))); 154 | //TODO: This might be wrong 155 | let rotation = quat_glam_to_glm(transform.rotation_quat()); 156 | let rotation = nalgebra::UnitQuaternion::from_quaternion(rotation); 157 | collision_world.add( 158 | ncollide3d::math::Isometry::from_parts( 159 | nalgebra::Translation::from(vec3_glam_to_glm(transform.position())), 160 | rotation, 161 | ), 162 | shape_handle, 163 | CollisionGroups::new(), 164 | GeometricQueryType::Proximity(0.001), 165 | entity, 166 | ); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | mod draw; 2 | pub use draw::DrawSkiaCircleComponent; 3 | pub use draw::DrawSkiaCircleComponentDef; 4 | pub use draw::DrawSkiaBoxComponent; 5 | pub use draw::DrawSkiaBoxComponentDef; 6 | pub use draw::PaintDef; 7 | pub use draw::Paint; 8 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod components; 2 | 3 | pub mod resources; 4 | 5 | pub mod math_conversions; 6 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/math_conversions.rs: -------------------------------------------------------------------------------- 1 | pub use minimum::math::na_convert::*; 2 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/resources/canvas_draw.rs: -------------------------------------------------------------------------------- 1 | use skulpin::CoordinateSystemHelper; 2 | use skulpin::skia_safe; 3 | 4 | struct CanvasDrawContextInner { 5 | canvas: *mut skia_safe::Canvas, 6 | coordinate_system_helper: CoordinateSystemHelper, 7 | } 8 | 9 | #[derive(Default)] 10 | pub struct CanvasDrawResource { 11 | inner: std::sync::Mutex>, 12 | } 13 | 14 | unsafe impl Send for CanvasDrawResource {} 15 | unsafe impl Sync for CanvasDrawResource {} 16 | 17 | impl CanvasDrawResource { 18 | pub fn begin_draw_context( 19 | &mut self, 20 | canvas: &mut skia_safe::Canvas, 21 | coordinate_system_helper: skulpin::CoordinateSystemHelper, 22 | ) { 23 | let mut lock = self.inner.lock().unwrap(); 24 | *lock = Some(CanvasDrawContextInner { 25 | canvas: canvas as *mut skia_safe::Canvas, 26 | coordinate_system_helper, 27 | }); 28 | } 29 | 30 | pub fn end_draw_context(&mut self) { 31 | let mut lock = self.inner.lock().unwrap(); 32 | *lock = None; 33 | } 34 | 35 | pub fn with_canvas( 36 | &mut self, 37 | f: F, 38 | ) where 39 | F: FnOnce(&mut skia_safe::Canvas, &CoordinateSystemHelper), 40 | { 41 | let lock = self.inner.lock().unwrap(); 42 | let lock_ref = (*lock).as_ref().unwrap(); 43 | //let x = lock_ref.as_ref().unwrap(); 44 | let canvas = unsafe { &mut *lock_ref.canvas }; 45 | (f)(canvas, &lock_ref.coordinate_system_helper); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contrib/minimum-skulpin/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod canvas_draw; 2 | pub use canvas_draw::CanvasDrawResource; 3 | -------------------------------------------------------------------------------- /contrib/minimum-winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-winit" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | winit = "0.22" 13 | 14 | imgui = "0.5" 15 | imgui-winit-support = "0.5.0" 16 | 17 | glam = { version = "0.8.5", features = ["serde"] } 18 | 19 | log="0.4" 20 | -------------------------------------------------------------------------------- /contrib/minimum-winit/fonts/feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-winit/fonts/feather.ttf -------------------------------------------------------------------------------- /contrib/minimum-winit/fonts/fontawesome-470.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-winit/fonts/fontawesome-470.ttf -------------------------------------------------------------------------------- /contrib/minimum-winit/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-winit/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /contrib/minimum-winit/fonts/mplus-1p-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/contrib/minimum-winit/fonts/mplus-1p-regular.ttf -------------------------------------------------------------------------------- /contrib/minimum-winit/src/imgui.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::sync::Mutex; 3 | 4 | use minimum::ImguiManager; 5 | 6 | // Inner state for ImguiManager, which will be protected by a Mutex. Mutex protection required since 7 | // this object is Send but not Sync 8 | struct WinitImguiManagerInner { 9 | // Handles the integration between imgui and winit 10 | platform: imgui_winit_support::WinitPlatform, 11 | } 12 | 13 | //TODO: Investigate usage of channels/draw lists 14 | #[derive(Clone)] 15 | pub struct WinitImguiManager { 16 | imgui_manager: ImguiManager, 17 | inner: Arc>, 18 | } 19 | 20 | // Wraps imgui (and winit integration logic) 21 | impl WinitImguiManager { 22 | pub fn imgui_manager(&self) -> ImguiManager { 23 | self.imgui_manager.clone() 24 | } 25 | 26 | // imgui and winit platform are expected to be pre-configured 27 | pub fn new( 28 | mut imgui_context: imgui::Context, 29 | platform: imgui_winit_support::WinitPlatform, 30 | ) -> Self { 31 | // Ensure font atlas is built and cache a pointer to it 32 | let _font_atlas_texture = { 33 | let mut fonts = imgui_context.fonts(); 34 | let font_atlas_texture = Box::new(fonts.build_rgba32_texture()); 35 | log::info!("Building ImGui font atlas"); 36 | 37 | // Remove the lifetime of the texture. (We're assuming we have ownership over it 38 | // now since imgui_context is being passed to us) 39 | let font_atlas_texture: *mut imgui::FontAtlasTexture = 40 | Box::into_raw(font_atlas_texture); 41 | let font_atlas_texture: *mut imgui::FontAtlasTexture<'static> = 42 | unsafe { std::mem::transmute(font_atlas_texture) }; 43 | font_atlas_texture 44 | }; 45 | 46 | let imgui_manager = ImguiManager::new(imgui_context); 47 | 48 | WinitImguiManager { 49 | imgui_manager, 50 | inner: Arc::new(Mutex::new(WinitImguiManagerInner { platform })), 51 | } 52 | } 53 | 54 | // Call when a winit event is received 55 | //TODO: Taking a lock per event sucks 56 | pub fn handle_event( 57 | &self, 58 | window: &winit::window::Window, 59 | event: &winit::event::Event, 60 | ) { 61 | let mut inner = self.inner.lock().unwrap(); 62 | let inner = &mut *inner; 63 | let platform = &mut inner.platform; 64 | 65 | self.imgui_manager.with_context(|context| { 66 | match event { 67 | winit::event::Event::WindowEvent { 68 | event: winit::event::WindowEvent::ReceivedCharacter(ch), 69 | .. 70 | } if *ch == '\u{7f}' => { 71 | // Do not pass along a backspace character 72 | // This hack can be removed when https://github.com/Gekkio/imgui-rs/pull/253 is 73 | // implemented upstream and I switch to using it 74 | } 75 | _ => { 76 | platform.handle_event(context.io_mut(), &window, &event); 77 | } 78 | } 79 | }) 80 | } 81 | 82 | // Start a new frame 83 | pub fn begin_frame( 84 | &self, 85 | window: &winit::window::Window, 86 | ) { 87 | self.imgui_manager.with_context(|context| { 88 | let inner = self.inner.lock().unwrap(); 89 | inner 90 | .platform 91 | .prepare_frame(context.io_mut(), window) 92 | .unwrap(); 93 | }); 94 | 95 | self.imgui_manager.begin_frame(); 96 | } 97 | 98 | // Finishes the frame. Draw data becomes available via get_draw_data() 99 | pub fn render( 100 | &self, 101 | window: &winit::window::Window, 102 | ) { 103 | self.imgui_manager.with_ui(|ui| { 104 | let mut inner = self.inner.lock().unwrap(); 105 | inner.platform.prepare_render(&ui, window); 106 | }); 107 | 108 | self.imgui_manager.render(); 109 | } 110 | 111 | // Allows access to the context without caller needing to be aware of locking 112 | #[allow(dead_code)] 113 | pub fn with_context( 114 | &self, 115 | f: F, 116 | ) where 117 | F: FnOnce(&mut imgui::Context), 118 | { 119 | self.imgui_manager.with_context(f); 120 | } 121 | 122 | // Allows access to the ui without the caller needing to be aware of locking. A frame must be started 123 | pub fn with_ui( 124 | &self, 125 | f: F, 126 | ) where 127 | F: FnOnce(&mut imgui::Ui), 128 | { 129 | self.imgui_manager.with_ui(f); 130 | } 131 | 132 | // Get reference to the underlying font atlas. The ref will be valid as long as this object 133 | // is not destroyed 134 | pub fn font_atlas_texture(&self) -> &imgui::FontAtlasTexture { 135 | unsafe { self.imgui_manager.sys_font_atlas_texture().unwrap() } 136 | } 137 | 138 | // Returns true if a frame has been started (and not ended) 139 | pub fn is_frame_started(&self) -> bool { 140 | self.imgui_manager.is_frame_started() 141 | } 142 | 143 | // Returns draw data (render must be called first to end the frame) 144 | pub fn draw_data(&self) -> Option<&imgui::DrawData> { 145 | unsafe { self.imgui_manager.sys_draw_data() } 146 | } 147 | 148 | pub fn want_capture_keyboard(&self) -> bool { 149 | self.imgui_manager.want_capture_keyboard() 150 | } 151 | 152 | pub fn want_capture_mouse(&self) -> bool { 153 | self.imgui_manager.want_capture_mouse() 154 | } 155 | 156 | pub fn want_set_mouse_pos(&self) -> bool { 157 | self.imgui_manager.want_set_mouse_pos() 158 | } 159 | 160 | pub fn want_text_input(&self) -> bool { 161 | self.imgui_manager.want_text_input() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /contrib/minimum-winit/src/input.rs: -------------------------------------------------------------------------------- 1 | use minimum::input::KeyboardKey; 2 | use minimum::input::ButtonState; 3 | 4 | pub use winit::event::VirtualKeyCode; 5 | pub use winit::event::ElementState; 6 | pub use winit::event::MouseScrollDelta; 7 | pub use winit::event::MouseButton; 8 | 9 | #[derive(Copy, Clone)] 10 | pub struct WinitKeyboardKey { 11 | keycode: VirtualKeyCode, 12 | } 13 | 14 | impl WinitKeyboardKey { 15 | pub fn new(keycode: VirtualKeyCode) -> Self { 16 | WinitKeyboardKey { keycode } 17 | } 18 | } 19 | 20 | impl Into for WinitKeyboardKey { 21 | fn into(self) -> KeyboardKey { 22 | KeyboardKey(self.keycode as u8) 23 | } 24 | } 25 | 26 | #[derive(Copy, Clone)] 27 | pub struct WinitElementState { 28 | element_state: ElementState, 29 | } 30 | 31 | impl WinitElementState { 32 | pub fn new(element_state: ElementState) -> Self { 33 | WinitElementState { element_state } 34 | } 35 | } 36 | 37 | impl Into for WinitElementState { 38 | fn into(self) -> ButtonState { 39 | match self.element_state { 40 | ElementState::Pressed => ButtonState::Pressed, 41 | ElementState::Released => ButtonState::Released, 42 | } 43 | } 44 | } 45 | 46 | #[derive(Copy, Clone)] 47 | pub struct WinitMouseButton { 48 | mouse_button: MouseButton, 49 | } 50 | 51 | impl WinitMouseButton { 52 | pub fn new(mouse_button: MouseButton) -> Self { 53 | WinitMouseButton { mouse_button } 54 | } 55 | } 56 | 57 | impl Into for WinitMouseButton { 58 | fn into(self) -> minimum::input::MouseButton { 59 | let button_index = match self.mouse_button { 60 | MouseButton::Left => 0, 61 | MouseButton::Right => 1, 62 | MouseButton::Middle => 2, 63 | MouseButton::Other(x) => x + 3, 64 | }; 65 | 66 | minimum::input::MouseButton(button_index) 67 | } 68 | } 69 | 70 | #[derive(Copy, Clone)] 71 | pub struct WinitMouseScrollDelta { 72 | mouse_scroll_delta: MouseScrollDelta, 73 | } 74 | 75 | impl WinitMouseScrollDelta { 76 | pub fn new(mouse_scroll_delta: MouseScrollDelta) -> Self { 77 | WinitMouseScrollDelta { mouse_scroll_delta } 78 | } 79 | } 80 | 81 | impl Into for WinitMouseScrollDelta { 82 | fn into(self) -> minimum::input::MouseScrollDelta { 83 | let delta = match self.mouse_scroll_delta { 84 | MouseScrollDelta::LineDelta(x, y) => (x, y), 85 | MouseScrollDelta::PixelDelta(delta) => (delta.x as f32, delta.y as f32), 86 | }; 87 | 88 | minimum::input::MouseScrollDelta { 89 | x: delta.0, 90 | y: delta.1, 91 | } 92 | } 93 | } 94 | 95 | /// Call when winit sends an event 96 | pub fn handle_winit_event( 97 | event: &winit::event::Event, 98 | input_state: &mut minimum::input::InputState, 99 | ) { 100 | use winit::event::Event; 101 | use winit::event::WindowEvent; 102 | 103 | let _is_close_requested = false; 104 | 105 | match event { 106 | //Process keyboard input 107 | Event::WindowEvent { 108 | event: WindowEvent::KeyboardInput { input, .. }, 109 | .. 110 | } => { 111 | trace!("keyboard input {:?}", input); 112 | if let Some(vk) = input.virtual_keycode { 113 | input_state.handle_keyboard_event( 114 | WinitKeyboardKey::new(vk).into(), 115 | WinitElementState::new(input.state).into(), 116 | ); 117 | } 118 | } 119 | 120 | Event::WindowEvent { 121 | event: 122 | WindowEvent::MouseInput { 123 | device_id, 124 | state, 125 | button, 126 | .. 127 | }, 128 | .. 129 | } => { 130 | trace!( 131 | "mouse button input {:?} {:?} {:?}", 132 | device_id, 133 | state, 134 | button, 135 | ); 136 | 137 | input_state.handle_mouse_button_event( 138 | WinitMouseButton::new(*button).into(), 139 | WinitElementState::new(*state).into(), 140 | ); 141 | } 142 | 143 | Event::WindowEvent { 144 | event: 145 | WindowEvent::CursorMoved { 146 | device_id, 147 | position, 148 | .. 149 | }, 150 | .. 151 | } => { 152 | trace!("mouse move input {:?} {:?}", device_id, position,); 153 | input_state 154 | .handle_mouse_move_event(glam::Vec2::new(position.x as f32, position.y as f32)); 155 | } 156 | 157 | Event::WindowEvent { 158 | event: WindowEvent::MouseWheel { 159 | device_id, delta, .. 160 | }, 161 | .. 162 | } => { 163 | trace!("mouse wheel {:?} {:?}", device_id, delta); 164 | input_state.handle_mouse_wheel_event(WinitMouseScrollDelta::new(*delta).into()); 165 | } 166 | 167 | // Ignore any other events 168 | _ => (), 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /contrib/minimum-winit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | pub mod resources; 5 | 6 | pub mod imgui; 7 | 8 | pub mod input; 9 | -------------------------------------------------------------------------------- /contrib/minimum-winit/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod winit_imgui; 2 | pub use self::winit_imgui::WinitImguiManagerResource; 3 | 4 | mod winit_window; 5 | pub use winit_window::WinitWindowResource; 6 | -------------------------------------------------------------------------------- /contrib/minimum-winit/src/resources/winit_imgui.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use crate::imgui::WinitImguiManager; 3 | 4 | // For now just wrap the input helper that skulpin provides 5 | pub struct WinitImguiManagerResource { 6 | pub winit_imgui_manager: WinitImguiManager, 7 | } 8 | 9 | impl WinitImguiManagerResource { 10 | /// Create a new TimeState. Default is not allowed because the current time affects the object 11 | #[allow(clippy::new_without_default)] 12 | pub fn new(imgui_platform_manager: WinitImguiManager) -> Self { 13 | WinitImguiManagerResource { 14 | winit_imgui_manager: imgui_platform_manager, 15 | } 16 | } 17 | } 18 | 19 | impl Deref for WinitImguiManagerResource { 20 | type Target = WinitImguiManager; 21 | 22 | #[inline] 23 | fn deref(&self) -> &Self::Target { 24 | &self.winit_imgui_manager 25 | } 26 | } 27 | 28 | impl DerefMut for WinitImguiManagerResource { 29 | #[inline] 30 | fn deref_mut(&mut self) -> &mut Self::Target { 31 | &mut self.winit_imgui_manager 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contrib/minimum-winit/src/resources/winit_window.rs: -------------------------------------------------------------------------------- 1 | use winit::window::Window; 2 | use winit::dpi::PhysicalSize; 3 | 4 | pub struct WinitWindowResource { 5 | inner_size: PhysicalSize, 6 | } 7 | 8 | impl WinitWindowResource { 9 | pub fn new(window: &Window) -> Self { 10 | WinitWindowResource { 11 | inner_size: window.inner_size(), 12 | } 13 | } 14 | 15 | pub fn update( 16 | &mut self, 17 | window: &Window, 18 | ) { 19 | self.inner_size = window.inner_size(); 20 | } 21 | 22 | pub fn inner_size(&self) -> PhysicalSize { 23 | self.inner_size 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/component_transform_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/docs/component_transform_example.jpg -------------------------------------------------------------------------------- /docs/examples/example-sdl2.md: -------------------------------------------------------------------------------- 1 | 2 | # SDL2 Example 3 | 4 | This is a walkthrough of how the SDL2 example works. This example demonstrates using SDL2, nphysics, and skia to do a 5 | few common tasks that would be required to make a physics-enabled game. SDL2 and skia were chosen for their maturity 6 | and stability. nphysics was chosen as an easy to use and build component that seems to be popular in the rust community. 7 | 8 | I don't think the current method of integrating these together is the best, so rather than detail it at length, I'll 9 | just list out what's there. 10 | 11 | * main.rs - Sets up logging, starts the daemon, runs the game 12 | * lib.rs - Opens a window, inits the renderer and other systems. (see `create_resources()`). Then runs an update loop 13 | * registration.rs - There is some registration and configuration needed for components. 14 | * `create_asset_manager()` - Enumerates all the assets that could be loaded 15 | * `create_component_registry()` - Enumerates all component types and how they are spawned 16 | * `create_editor_selection_registry()` - Registers the components that can be selected by clicking on them 17 | * `create_editor_inspector_registry()` - Registers components that can be added/removed/modified in the inspector 18 | * systems/mod.rs - Produces the update loop schedule. Many of the functions referred to here are in other upstream 19 | modules 20 | * systems/app_control_systems.rs - A system that exits when escape is hit 21 | * systems/draw_systems.rs - A system that sends data that needs to be drawn to skia 22 | 23 | The main non-ideal issue here is that you have to register all the components, assets, and systems manually that are 24 | contained in upstream code. There would ideally be a way to refer to a single idiomatic thing in upstream code. 25 | 26 | However, this does demonstrate the potential of a thin kernel hosting major features that can be mixed and matched by 27 | and end-user. -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | # Minimum Documentation 3 | 4 | ## Tutorials/Examples 5 | 6 | * Prefab System ([Complete code for each tutorial here!](../examples/tutorial)) 7 | * [Dev Environment Setup](tutorial/tutorial001_dev_environment_setup.md) 8 | * [Creating Prefabs](tutorial/tutorial002_creating_prefabs.md) 9 | * [Saving and Loading Prefabs](tutorial/tutorial003_save_and_load_prefabs.md) 10 | * [Cooking Prefabs](tutorial/tutorial004_cooking_prefabs.md) 11 | * [Spawning Prefabs](tutorial/tutorial005_spawning_prefabs.md) 12 | * [Transactions](tutorial/tutorial006_transactions.md) 13 | * [Creating Prefab Overrides](tutorial/tutorial007_creating_prefab_overrides.md) 14 | * TODO: More tutorials.. in the meantime please see the examples! 15 | * [Winit](..examples/example-winit) 16 | * [SDL2](../examples/example-sdl2) 17 | 18 | ### Running the Examples 19 | 20 | ``` 21 | # Fetch the repo 22 | git clone https://github.com/aclysma/minimum.git 23 | cd minimum 24 | 25 | # These are not technically examples so use run --package 26 | # Make sure the current directory is "examples" so assets can be found/loaded! 27 | cd examples 28 | cargo run --package example-winit 29 | cargo run --package example-sdl2 30 | 31 | # These *are* examples - one per above tutorial 32 | cd tutorial 33 | cargo run --example tutorial001_dev_environment_setup 34 | ``` 35 | 36 | ## Philosophy 37 | 38 | Most game engines are vertical integrations of every system one would want for a game. For example, UE4 and Unity 39 | provide their own prescribed systems (for rendering, audio, etc.) along with a way to glue custom game code with their 40 | systems. 41 | 42 | The primary goal of this (experimental) "engine" is to avoid packing in prescribed solutions, for at least two reasons: 43 | * Scope Management - We don't have the bandwidth to implement or maintain everything ourselves 44 | * Quality - It's better to let you pick exactly what you need rather that prescribing the same solution 45 | for everyone 46 | 47 | However, finding a way for people using slightly different solutions to share code is challenging. Minimum aims 48 | to solve this by defining a "protocol" for game data and an extendable toolset to work with it. (We prefer the word 49 | "kernel" over "engine") 50 | 51 | The hope is that this approach can support to an ecosystem of easy-to-share solutions with well-managed scope. I think 52 | such an ecosystem would be more compatible with the OSS model than a monolithic design. It lets people own just what 53 | they're interested in and decentralizes technical decisions - a requirement for sustainable growth. 54 | 55 | ## How Extension Works 56 | 57 | Rather than have one system directly call functions on another system, systems read/write data on resources and 58 | components. It is only necessary to share the format of the data - not any of the implementation that produces or 59 | consumes the data. 60 | 61 | Example: Any physics system could write a matrix into the transform component, and any renderer could read the matrix from 62 | the transform component. 63 | ``` 64 | [Physics System] -> Transform Component -> [Rendering System] 65 | ``` 66 | 67 | This avoids dependencies between the physics and rendering systems, allowing downstream crates to be owned by different 68 | authors. The data is essentially the interface between the systems. 69 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial001_dev_environment_setup.md: -------------------------------------------------------------------------------- 1 | # Dev Environment Setup 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | In order to get things started, you'll need to create a new crate: 6 | 7 | ``` 8 | cargo new --bin your_crate_name 9 | ``` 10 | 11 | Next, we need to add some dependencies to the .toml 12 | 13 | ```toml 14 | # The minimum game engine kernel 15 | minimum = { git = "https://github.com/aclysma/minimum" } 16 | 17 | # Asset Pipeline 18 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 19 | 20 | # Prefab/Transactions 21 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 22 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 23 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 24 | 25 | # Legion ECS 26 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 27 | 28 | # Required for serializing/desieralizing components 29 | serde = "1" 30 | uuid = "0.8" 31 | type-uuid = "0.1" 32 | itertools = "0.8" 33 | ron = "0.5" 34 | 35 | # Identifies diffs between two structs, used when creating transactions 36 | serde-diff = "0.3" 37 | 38 | # Logging 39 | log="0.4" 40 | env_logger = "0.6" 41 | 42 | # Not required, but we'll use it for math in the tutorial 43 | glam = "0.8.6" 44 | ``` 45 | 46 | Now, we need to set up a couple things 47 | 48 | ```rust 49 | fn main() { 50 | // Setup logging 51 | env_logger::Builder::from_default_env() 52 | .filter_level(log::LevelFilter::Debug) 53 | .init(); 54 | 55 | // Spawn the daemon in a background thread. This could be a different process, but 56 | // for simplicity we'll launch it here. 57 | std::thread::spawn(move || { 58 | minimum::daemon::create_default_asset_daemon(); 59 | }); 60 | } 61 | ``` 62 | 63 | This enables logging and starts the asset daemon. If you wanted to customize where on disk assets are being pulled from, 64 | you could look at the implementation of `minimum::daemon::create_default_asset_daemon()`. This implementation uses command line args, but you 65 | could implement this yourself if you prefer different defaults. 66 | 67 | At this point you can start working with legion and many of the features we've built on top of it. 68 | 69 | Normally, the next thing you'd do is create a window (with SDL2 or winit, for example) but for now lets just take a 70 | short tour of what we can do with legion. 71 | 72 | ## A Quick Legion Tour 73 | 74 | (If you're familiar with legion already, you can skim this section. We're just going to demo the parts of legion 75 | required for the rest of this tutorial.) 76 | 77 | For a practical example, lets create some entities with position and velocity, and a resource that holds gravity. 78 | 79 | First lets add some types for the math 80 | 81 | ```rust 82 | use glam::Vec2; 83 | 84 | struct Position(pub glam::Vec2); 85 | struct Velocity(pub glam::Vec2); 86 | struct Gravity(pub glam::Vec2); 87 | ``` 88 | 89 | 90 | Next, we need to start up legion and register components. 91 | 92 | ```rust 93 | // This import grabs most of the things you'd want in legion 94 | use legion::*; 95 | 96 | // Create a legion world and resources 97 | let mut world = World::default(); 98 | let mut resources = Resources::default(); 99 | ``` 100 | 101 | What is this stuff? 102 | * World - This is a set of entities and the components that are attached to them 103 | * Resources - A resource is a bit like a hash map of globals. Elements can be read and written by type. For example: 104 | 105 | ```rust 106 | // Read only access 107 | let gravity = resources.get::(); 108 | 109 | // Mutable access 110 | let mut gravity = resources.get_mut::(); 111 | ``` 112 | 113 | These calls return a Fetch or FetchMut of your type. These are a bit like holding a lock on the value. It is 114 | essentially overriding the borrow checker and moving the checking it would be doing to runtime. 115 | 116 | The standard borrowing rules apply, if they are violated you'll get a panic. There's also a bit of overhead induced 117 | to do the lookup. Legion has a solution for both issues - but we won't cover it here just yet. Please see the legion 118 | documentation for more info on this. 119 | 120 | Now we can insert a gravity resource and create some entities: 121 | 122 | ```rust 123 | // Insert a resource that can be queried to find gravity 124 | resources.insert(Gravity(-9.8 * Vec2::unit_y())); 125 | 126 | // Insert an object with position and velocity 127 | let entity = *world 128 | .insert( 129 | (), 130 | (0..1).map(|_| { 131 | ( 132 | PositionComponent(Vec2::new(0.0, 500.0)), 133 | VelocityComponent(Vec2::new(5.0, 0.0)), 134 | ) 135 | }), 136 | ) 137 | .first() 138 | .unwrap(); 139 | ``` 140 | 141 | The code for this is a bit busy, but realistically we'd be wanting to spawn this from prefabs defined by a file and 142 | created by an editor. (We'll get to that soon!) 143 | 144 | And now lets write some code to integrate acceleration from gravity into velocity, and velocity into position 145 | 146 | ```rust 147 | for _ in 0..10 { 148 | // Fetch gravity... and integrate it to velocity. 149 | let gravity = resources.get::().unwrap(); 150 | let query = <(Write)>::query(); 151 | for mut vel in query.iter_mut(&mut world) { 152 | vel.0 += gravity.0; 153 | } 154 | 155 | // Iterate across all entities and integrate velocity to position 156 | let query = <(Write, TryRead)>::query(); 157 | for (mut pos, vel) in query.iter_mut(&mut world) { 158 | if let Some(vel) = vel { 159 | pos.0 += vel.0; 160 | } 161 | 162 | pos.0 += gravity.0; 163 | } 164 | 165 | let position = world.entry_ref(entity).unwrap().into_component::().unwrap(); 166 | println!("Position is {}", position.0); 167 | } 168 | ``` 169 | 170 | You should see something like this print out: 171 | 172 | ``` 173 | Position is [5, 480.40002] 174 | Position is [10, 451.00003] 175 | Position is [15, 411.80005] 176 | Position is [20, 362.80005] 177 | Position is [25, 304.00006] 178 | Position is [30, 235.40005] 179 | Position is [35, 157.00005] 180 | Position is [40, 68.80004] 181 | Position is [45, -29.199963] 182 | Position is [50, -136.99997] 183 | ``` 184 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial002_creating_prefabs.md: -------------------------------------------------------------------------------- 1 | # Creating Prefabs 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | In this chapter, we'll convert a legion world into a prefab. 6 | 7 | ## Setting Up Components 8 | 9 | In the prior example, we use a couple raw structs for components. In order to use the features we will be covering in 10 | this tutorial, these must be changed: 11 | 12 | ``` 13 | use type_uuid::TypeUuid; 14 | use serde::Serialize; 15 | use serde::Deserialize; 16 | use serde_diff::SerdeDiff; 17 | 18 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 19 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 20 | pub struct PositionComponent { 21 | #[serde_diff(opaque)] 22 | pub value: Vec2, 23 | } 24 | 25 | legion_prefab::register_component_type!(PositionComponent); 26 | 27 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 28 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 29 | pub struct VelocityComponent { 30 | #[serde_diff(opaque)] 31 | pub value: Vec2, 32 | } 33 | 34 | legion_prefab::register_component_type!(VelocityComponent); 35 | ``` 36 | 37 | This gives all components a UUID - which allows you to rename these structs without breaking serialization. The UUID 38 | can be anything you like - there are online generators for UUIDs that make it easy. (Pro tip: generate a hundred or so 39 | and save them to a file for future use!) 40 | 41 | It is possible to use components that can't be serialized. We'll cover that later. The general approach is that you'd 42 | define a definition that can be serialized and implement a trait to transform it into what you'd like to use at runtime. 43 | This trait implementation can access data stored in resources and other components on the same entity. 44 | 45 | The next step is to create a component registry: 46 | 47 | ```rust 48 | // Register all components (based on legion_prefab::register_component_type! macro) 49 | let component_registry = minimum::ComponentRegistryBuilder::new() 50 | .auto_register_components() 51 | .build(); 52 | ``` 53 | 54 | Having the component registry isn't technically necessary yet, but we might as well do it at the same time as setting 55 | up the components. 56 | 57 | ## Creating the Prefab 58 | 59 | Now starting from tutorial 1 and the above changes, we have a legion world and a component registry. To convert the 60 | world into a prefab: 61 | 62 | ```rust 63 | use legion_prefab::Prefab; 64 | 65 | // Create the prefab 66 | let prefab = Prefab::new(prefab_world); 67 | ``` 68 | 69 | Prefabs can contain Entities and EntityRefs. The EntityRef is a pointer to another prefab that indicates we should copy 70 | entities from that prefab into this one. We won't cover EntityRefs yet - partly because it's complex, and partly because 71 | the feature still needs more work. 72 | 73 | Every entity that exists in a prefab gets its own UUID. It's stored in prefab.prefab_meta.entities. You can get it like 74 | this: 75 | 76 | ```rust 77 | // Get the UUID of the entity 78 | let entity_uuid = prefab 79 | .prefab_meta 80 | .entities 81 | .iter() 82 | .find(|(_, value)| **value == prefab_entity) 83 | .map(|(entity_uuid, _)| *entity_uuid) 84 | .unwrap(); 85 | ``` 86 | 87 | In addition, entities are placed in a legion world within the prefab. Because this world is a copy of the original world, 88 | the Entity will be different. However, you can use the UUID to look it up. 89 | 90 | ```rust 91 | // Look up the entity associated with the entity_uuid. To do this, we have to: 92 | // - Look at the original prefab to find the UUID of the entity 93 | // - Then use prefab_meta on the deserialized prefab to find the entity in the deserialized 94 | // prefab's world 95 | let entity = prefab.prefab_meta.entities[&entity_uuid]; 96 | 97 | let position = prefab 98 | .world 99 | .entry_ref(entity) 100 | .unwrap() 101 | .into_component::() 102 | .unwrap(); 103 | println!( 104 | "Position of {} is {}", 105 | uuid::Uuid::from_bytes(entity_uuid), 106 | position.value 107 | ); 108 | ``` 109 | 110 | Up to this point we've created a prefab. In the next chapters, we'll go over how to save and load prefabs to disk and 111 | how to instantiate them into a runtime world. -------------------------------------------------------------------------------- /docs/tutorial/tutorial003_save_and_load_prefabs.md: -------------------------------------------------------------------------------- 1 | # Saving and Loading Prefabs 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | Starting from tutorial 2, we have set up components and a component registry. We will also use the same legion world. 6 | 7 | First lets convert that world to a prefab 8 | 9 | ```rust 10 | // Create the prefab 11 | let prefab_uncooked = Prefab::new(prefab_world); 12 | 13 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 14 | let entity_uuid = prefab_uncooked 15 | .prefab_meta 16 | .entities 17 | .iter() 18 | .find(|(_, value)| **value == prefab_entity) 19 | .map(|(entity_uuid, _)| *entity_uuid) 20 | .unwrap(); 21 | ``` 22 | 23 | ## Serializing the Prefab 24 | 25 | Now we can serialize it to a string. I've wrapped the logic in a function since there's a few moving parts: 26 | * prefab_serde_context - Provides the component lookup to the serialization code 27 | * ron_ser - Responsible for writing out the data in RON format 28 | * prefab_ser - Holds references to everything the serializer needs 29 | * prefab_format::serialize - Does the actual work to serialize the data 30 | 31 | After prefab_format::serialize is called, the RON serializer holds the produced string 32 | 33 | ```rust 34 | fn serialize_prefab( 35 | component_registry: &ComponentRegistry, 36 | prefab: &Prefab, 37 | ) -> String { 38 | let prefab_serde_context = legion_prefab::PrefabSerdeContext { 39 | registered_components: component_registry.components_by_uuid(), 40 | }; 41 | 42 | let mut ron_ser = ron::ser::Serializer::new(Some(ron::ser::PrettyConfig::default()), true); 43 | let prefab_ser = legion_prefab::PrefabFormatSerializer::new(prefab_serde_context, prefab); 44 | 45 | prefab_format::serialize(&mut ron_ser, &prefab_ser, prefab.prefab_id()) 46 | .expect("failed to round-trip prefab"); 47 | 48 | ron_ser.into_output_string() 49 | } 50 | 51 | // Serialize the prefab to a string 52 | let serialized_prefab = serialize_prefab(&component_registry, &prefab_uncooked); 53 | ``` 54 | 55 | ## Prefab Format 56 | 57 | It looks something like this: 58 | 59 | ```rust 60 | Prefab( 61 | id: "599a55e1-1e07-4604-98a8-7df40ae9ee78", 62 | objects: [ 63 | Entity(PrefabEntity( 64 | id: "25a63c38-f7ab-4e44-8f59-ef2f05054e94", 65 | components: [ 66 | EntityComponent( 67 | type: "8bf67228-f96c-4649-b306-ecd107190000", 68 | data: PositionComponent( 69 | value: Vec2(0, 500), 70 | ), 71 | ), 72 | EntityComponent( 73 | type: "8bf67228-f96c-4649-b306-ecd107190001", 74 | data: VelocityComponent( 75 | value: Vec2(5, 0), 76 | ), 77 | ), 78 | ], 79 | )), 80 | ], 81 | ) 82 | ``` 83 | 84 | I'd like to point out a few pieces: 85 | * The prefab gets assigned a random UUID automatically 86 | * Each entity in the world also gets a UUID 87 | * Each entity can have components. The type is identified by the UUID specified in 88 | code when decorating the component type 89 | * The raw data is defined by serde 90 | 91 | ## Deserializing the Prefab 92 | 93 | We can now read it back from a string (or a file.) The process is a mirror of serializing. 94 | 95 | ```rust 96 | fn deserialize_prefab( 97 | component_registry: &ComponentRegistry, 98 | serialized: &str, 99 | ) -> Prefab { 100 | let prefab_serde_context = legion_prefab::PrefabSerdeContext { 101 | registered_components: component_registry.components_by_uuid(), 102 | }; 103 | 104 | let mut deserializer = ron::de::Deserializer::from_str(serialized).unwrap(); 105 | 106 | let prefab_deser = legion_prefab::PrefabFormatDeserializer::new(prefab_serde_context); 107 | prefab_format::deserialize(&mut deserializer, &prefab_deser).unwrap(); 108 | prefab_deser.prefab() 109 | } 110 | 111 | // Deserialize the world from a string 112 | let deserialized_prefab = deserialize_prefab(&component_registry, &serialized_prefab); 113 | ``` 114 | 115 | If you use atelier-assets and minimum, you won't need to work with files or serialization directly, 116 | but it's still nice to know it's an option! 117 | 118 | We'll end this chapter as we did the prior chapter - show how to access the data in the deserialized 119 | prefab by UUID. (Again, the deserialized prefab's world is different and you can't use the same entity 120 | as before.) 121 | 122 | ```rust 123 | // Look up the entity associated with the entity_uuid. To do this, we have to: 124 | // - Look at the original prefab to find the UUID of the entity 125 | // - Then use prefab_meta on the deserialized prefab to find the entity in the deserialized 126 | // prefab's world 127 | let mut deserialized_entity = deserialized_prefab.prefab_meta.entities[&entity_uuid]; 128 | 129 | // Now run some code to demonstrate that we found the same entity in the deserialized world and 130 | // that we get the same results as before 131 | let position = deserialized_prefab 132 | .world 133 | .entry_ref(deserialized_entity) 134 | .unwrap() 135 | .into_component::() 136 | .unwrap(); 137 | 138 | println!( 139 | "Position of {} is {}", 140 | uuid::Uuid::from_bytes(entity_uuid), 141 | position.value 142 | ); 143 | ``` -------------------------------------------------------------------------------- /docs/tutorial/tutorial004_cooking_prefabs.md: -------------------------------------------------------------------------------- 1 | # Cooking Prefabs 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | Prefabs come in two forms - cooked, and uncooked. 6 | 7 | Uncooked prefabs can reference other prefabs. "Cooking" a prefab is the process of flattening a prefab and all the other 8 | prefabs it depends on into a single prefab. At edit time, we want to use the hierarchy to avoid duplicated data, but at 9 | runtime we don't want spawning to require merging a bunch of data. Cooking allows us to prepare data offline for 10 | spawning. 11 | 12 | The long-term goal is that this is handled for you within atelier-assets, but some work remains to be done. So currently 13 | we cook the data at runtime right before we use it. 14 | 15 | ## Cooking a Prefab 16 | 17 | As mentioned before, the cooking process requires having all the dependency prefabs. Again, in the future this is 18 | expected to be handled in atelier-assets, but for now we have to do it manually. 19 | 20 | Prefabs must be cooked in dependency order, based on overrides. For example, if prefab B 21 | overrides something on prefab A, and prefab C overrides something on prefab B, we must cook 22 | A, then B, then C. When cooking B, we must be able to access A in cooked form, and when 23 | cooking C, we must be able to access B in cooked form. 24 | 25 | In this case there are no overrides, so our ordering is simply the prefab we want to cook 26 | and the lookup just contains this prefab. 27 | 28 | ```rust 29 | // 30 | // Cook the prefab world 31 | // 32 | 33 | // Prefabs must be cooked in dependency order, based on overrides. For example, if prefab B 34 | // overrides something on prefab A, and prefab C overrides something on prefab B, we must cook 35 | // A, then B, then C. When cooking B, we must be able to access A in cooked form, and when 36 | // cooking C, we must be able to access B in cooked form. 37 | // 38 | // In this case there are no overrides, so our ordering is simply the prefab we want to cook 39 | // and the lookup just contains this prefab. 40 | // 41 | // In the future this can be happened in atelier as part of the asset build process. For now, 42 | // cooking happens at runtime in minimum but dependency management is automatically handled. 43 | let prefab_cook_order = vec![prefab.prefab_id()]; 44 | let mut prefab_lookup = HashMap::new(); 45 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 46 | 47 | let cooked_prefab = legion_prefab::cook_prefab( 48 | component_registry.components(), 49 | component_registry.components_by_uuid(), 50 | prefab_cook_order.as_slice(), 51 | &prefab_lookup, 52 | ); 53 | ``` 54 | 55 | The cooked prefab contains its own legion world - a copy of the data of all upstream prefabs. This includes overridden 56 | data, which we will cover later. 57 | 58 | ```rust 59 | // Look up the entity associated with the entity_uuid 60 | let cooked_entity = cooked_prefab.entities[&entity_uuid]; 61 | 62 | let position = cooked_prefab 63 | .world 64 | .entry_ref(cooked_entity) 65 | .unwrap() 66 | .into_component::() 67 | .unwrap(); 68 | println!( 69 | "Position of {} is {}", 70 | uuid::Uuid::from_bytes(entity_uuid), 71 | position.value 72 | ); 73 | ``` -------------------------------------------------------------------------------- /docs/tutorial/tutorial005_spawning_prefabs.md: -------------------------------------------------------------------------------- 1 | # Spawning Prefabs 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | Now that we have a cooked prefab, we can spawn it into a runtime world. Before we do that, lets return to component 6 | registration briefly. 7 | 8 | ## Component Types 9 | 10 | Component types can be divided into two categories: 11 | 12 | ### Cloneable Components 13 | 14 | Cloneable components must implement a number of traits: 15 | 16 | * Clone - Used for cloning edit-time data into the runtime world 17 | * Serialize/Deserialize - Used for saving/loading data in the editor 18 | * Default - Used when adding a component to an entity to determine initial state 19 | * PartialEq - Required for SerdeDiff… 20 | * SerdeDiff - SerdeDiff lets us determine what changed in user-edited data 21 | * TypeUuid - In our implementation, we use UUIDs to identify types in a registry. 22 | 23 | If a structure implements the above traits, then it can be used to represent a component at all stages of the pipeline - 24 | editing, loading, and running. Our position component is a good example of this. 25 | 26 | ### Non-Cloneable Components 27 | 28 | Sometimes, components at runtime won’t be able to implement some of these traits. A common case is data within the 29 | component that is not cloneable or serializable. For example, a physics component that represents a rigid body attached 30 | to the entity might need a handle to the rigid body in a physics engine. 31 | 32 | This clearly can’t be serialized as it would be meaningless once deserialized. Further, the data required to describe 33 | how to create the rigid body is not the same as the data required to represent the rigid body once it has been created. 34 | 35 | In order to support this, we have the concept of ComponentDefinitions. The ComponentDefinition is used for editing and 36 | loading and the actual Component is only used in the game world at runtime. 37 | 38 | ```rust 39 | // This is the edit-time representation, it must support 40 | // a number of traits 41 | #[derive( 42 | Clone, Serialize, Deserialize, Default, 43 | PartialEq, SerdeDiff, TypeUuid 44 | )] 45 | #[uuid = "8bf67228-f96c-4649-b306-ecd107194cf1"] 46 | pub struct RigidBodyBallComponentDef { 47 | pub radius: f32, 48 | pub is_static: bool, 49 | } 50 | 51 | // Runtime data, no derives required for this! 52 | pub struct RigidBodyComponent { 53 | pub handle: DefaultBodyHandle, 54 | delete_body_tx: crossbeam_channel::Sender, 55 | } 56 | ``` 57 | 58 | In this example, the handle points to a rigid body stored in an nphysics world. 59 | 60 | It’s important to note here, for non-cloneable components, the end-user would not edit actual component data - they edit 61 | the component definition. So from an end-user’s perspective, they just see radius and is_static as editable fields. 62 | 63 | #### Registration 64 | 65 | A typical component registration might look like this: 66 | 67 | ```rust 68 | minimum::ComponentRegistryBuilder::new() 69 | .auto_register_components() 70 | .add_spawn_mapping_into::() 71 | .add_spawn_mapping_into::() 72 | .add_spawn_mapping::() 73 | .add_spawn_mapping::() 74 | .build() 75 | ``` 76 | 77 | #### Using Into/From 78 | 79 | For the DrawSkiaCircleComponentDef/DrawSkiaBoxComponentDef (see the demo!) we can just use the rust built-in Into/From 80 | traits. 81 | 82 | ```rust 83 | impl From for DrawSkiaBoxComponent { 84 | fn from(from: DrawSkiaBoxComponentDef) -> Self { 85 | DrawSkiaBoxComponent { 86 | half_extents: from.half_extents, 87 | paint: from.paint.into(), 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | In this case, a transform was required because paint is an FFI type in the skia library. It is not possible to serialize 94 | it, so we implemented a serializable type for paint that could be used to construct the FFI type at runtime. 95 | 96 | #### Using SpawnInto/SpawnFrom 97 | 98 | For RigidBodyBallComponentDef and RigidBodyBoxComponentDef, we needed to access other components on the entity and 99 | resources. This is a more complex usage, please see the demo source code for details. But in short, it's a matter of 100 | implementing SpawnFrom or SpawnInto 101 | 102 | impl SpawnFrom for RigidBodyComponent { 103 | fn spawn_from( 104 | src_world: &World, 105 | src_component_storage: &ComponentStorage, 106 | src_component_storage_indexes: Range, 107 | resources: &Resources, 108 | src_entities: &[Entity], 109 | dst_entities: &[Entity], 110 | from: &[RigidBodyBallComponentDef], 111 | into: &mut [std::mem::MaybeUninit], 112 | ) { 113 | // Implement your transform here. You'll likely read resources and components from the old world. When the 114 | // function returns, you must have initialized all the data in the `into` slice 115 | } 116 | } 117 | 118 | ## Spawning Components 119 | 120 | ```rust 121 | let mut world = World::default(); 122 | let mut resources = Resources::default(); 123 | 124 | // 125 | // Spawn the prefab in a new world. 126 | // 127 | let mut clone_impl_result = HashMap::default(); 128 | let mut spawn_impl = component_registry.spawn_clone_impl(&resources, &mut clone_impl_result); 129 | world.clone_from( 130 | &cooked_prefab.world, 131 | &legion::query::any(), 132 | &mut spawn_impl, 133 | ); 134 | ``` 135 | 136 | The world will now contain copies of cloneable components, and the results of transforming non-cloneable components. 137 | 138 | To find the entities in the world that correspond with the prefab UUID, we must 139 | * Look at the cooked prefab to determine which entity is associated with the UUID 140 | * Then use clone_impl_result in clone_from to find the copy that was cloned into the world 141 | 142 | ```rust 143 | let cooked_entity = cooked_prefab.entities[&entity_uuid]; 144 | let world_entity = clone_impl_result[&cooked_entity]; 145 | 146 | let position = world 147 | .entry_ref(world_entity) 148 | .unwrap() 149 | .into_component::() 150 | .unwrap(); 151 | println!( 152 | "Position of {} is {}", 153 | uuid::Uuid::from_bytes(entity_uuid), 154 | position.value 155 | ); 156 | ``` -------------------------------------------------------------------------------- /docs/tutorial/tutorial006_transactions.md: -------------------------------------------------------------------------------- 1 | # Transactions 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | Once you have a cooked prefab, you can modify it by using transactions. We use transactions for: 6 | 7 | * Applying changes to the runtime state 8 | * Sending the changes back to the atelier daemon to commit changes to disk, and possibly other connected devices 9 | * Implementing Undo/Redo 10 | 11 | Using a transaction is simple: 12 | 13 | 1. Open the transaction 14 | 2. Modify the transaction's world (just as if you weren't using transactions) 15 | 3. Produce diffs from the transaction 16 | 17 | One you have a transaction diff, you can either apply or revert the change to the prefab it is based on. 18 | 19 | ## Opening the Transaction 20 | 21 | When opening the transaction, you must indicate what entities you intend to modify. These entities will be copied into 22 | the transaction's world. In a typical case if you were implementing an editor, most of the time you'd include every 23 | entity that is selected in the editor. 24 | 25 | ```rust 26 | // Start a new transaction 27 | let mut transaction = legion_transaction::TransactionBuilder::new() 28 | .add_entity(cooked_entity, entity_uuid) 29 | .begin( 30 | &cooked_prefab.world, 31 | &component_registry.copy_clone_impl(), 32 | ); 33 | ``` 34 | 35 | ## Modifying the Transaction's World 36 | 37 | To modify the transaction, you can access it's world: 38 | 39 | ```rust 40 | // Mess with a value in the transaction's world 41 | let transaction_entity = transaction.uuid_to_entity(entity_uuid).unwrap(); 42 | transaction 43 | .world_mut() 44 | .get_component_mut::(transaction_entity) 45 | .unwrap() 46 | .value += glam::Vec2::new(0.0, 1000.0); 47 | ``` 48 | 49 | The following operations are supported: 50 | * Adding entities 51 | * Adding components 52 | * Changing existing components 53 | * Removing entities 54 | * Removing components 55 | 56 | In short, whatever you do on the world will be reflected in the diffs that the transaction produces. 57 | 58 | ## Producing Diffs from the Transaction 59 | 60 | To produce the diffs, call `create_transaction_diffs` 61 | 62 | ```rust 63 | let diffs = transaction.create_transaction_diffs(component_registry.components_by_uuid()); 64 | ``` 65 | 66 | You can use the resulting value's `apply_diff()` or `revert_diff()` to apply or undo the change. 67 | 68 | ### Applying to Prefabs 69 | 70 | Currently, this only works with prefabs that have no overrides. We hope to implement support for this in the future. 71 | 72 | ```rust 73 | // Apply the change to the prefab 74 | // The return value is a result that may indicate failure if there are prefab overrides 75 | let mut prefab = legion_transaction::apply_diff_to_prefab( 76 | &mut prefab, 77 | diffs.apply_diff(), 78 | component_registry.components_by_uuid(), 79 | &component_registry.copy_clone_impl(), 80 | ) 81 | .unwrap(); 82 | ``` 83 | 84 | ### Applying to Cooked Prefabs 85 | 86 | ```rust 87 | // Apply the change to the prefab 88 | let mut cooked_prefab = legion_transaction::apply_diff_to_cooked_prefab( 89 | &mut cooked_prefab, 90 | diffs.apply_diff(), 91 | component_registry.components_by_uuid(), 92 | &component_registry.copy_clone_impl(), 93 | ); 94 | ``` -------------------------------------------------------------------------------- /docs/tutorial/tutorial007_creating_prefab_overrides.md: -------------------------------------------------------------------------------- 1 | # Creating Prefab Overrides 2 | 3 | *Compiling code for this demo is located in [/examples/tutorial/examples](../../examples/tutorial/examples)* 4 | 5 | Currently we have some very basic support for prefabs. This chapter will explain how to produce a prefab that overrides 6 | another prefab and how to cook it. 7 | 8 | Unlike transactions, building prefabs has a more limited feature set. We hope to expand on it soon. Currently, we only 9 | support modifying and adding components, and adding entities. 10 | 11 | ## Producing the Prefab 12 | 13 | ```rust 14 | // Use a prefab builder to make a new prefab that overrides a field on the given prefab 15 | let mut prefab_builder = PrefabBuilder::new( 16 | prefab.prefab_id(), 17 | cooked_prefab, 18 | &component_registry.copy_clone_impl(), 19 | ); 20 | 21 | // Here, we modify the world on the prefab builder. 22 | // The changes here are scanned to produce the prefab. 23 | let prefab_builder_entity = prefab_builder.uuid_to_entity(entity_uuid).unwrap(); 24 | prefab_builder 25 | .world_mut() 26 | .get_component_mut::(prefab_builder_entity) 27 | .unwrap() 28 | .value = glam::Vec2::new(0.0, 1000.0); 29 | 30 | // Produce the prefab that overrides the original prefab 31 | let prefab_with_override = prefab_builder 32 | .create_prefab( 33 | component_registry.components_by_uuid(), 34 | &component_registry.copy_clone_impl(), 35 | ) 36 | .unwrap(); 37 | ``` 38 | 39 | ## Cooking the Prefab 40 | 41 | We cook the prefab as we did before except now we need to provide both prefabs to `legion_prefab::cook_prefab`. 42 | In the long term, this process will be handled by `atelier-assets` but for now must be 43 | done manually. 44 | 45 | The cooked prefab can be persisted or used directly in memory. 46 | 47 | ```rust 48 | // 49 | // Cook the prefab that has the override 50 | // 51 | let prefab_cook_order = vec![prefab.prefab_id(), prefab_with_override.prefab_id()]; 52 | let mut prefab_lookup = HashMap::new(); 53 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 54 | prefab_lookup.insert(prefab_with_override.prefab_id(), &prefab_with_override); 55 | 56 | let cooked_prefab_with_override = legion_prefab::cook_prefab( 57 | component_registry.components(), 58 | component_registry.components_by_uuid(), 59 | prefab_cook_order.as_slice(), 60 | &prefab_lookup, 61 | ); 62 | ``` 63 | 64 | Now that we have the prefab, we can look up the data in the new prefab to see that the change has taken effect. 65 | 66 | By using prefabs in this way, any changes other than the field we just overrode will propagage into the cooked prefab. 67 | 68 | ```rust 69 | 70 | // Look up the entity in the cooked prefab with override by UUID 71 | let entity = cooked_prefab_with_override.entities[&entity_uuid]; 72 | 73 | let position = cooked_prefab_with_override 74 | .world 75 | .entry_ref(entity) 76 | .unwrap() 77 | .into_component::() 78 | .unwrap(); 79 | println!( 80 | "Position of {} is {}", 81 | uuid::Uuid::from_bytes(entity_uuid), 82 | position.value 83 | ); 84 | ``` -------------------------------------------------------------------------------- /examples/assets/demo_level.prefab.meta: -------------------------------------------------------------------------------- 1 | ( 2 | version: 1, 3 | import_hash: Some(14533717437868859226), 4 | importer_version: 1, 5 | importer_type: "5bdf4d06-a1cb-437b-b182-d6d8cb23512c", 6 | importer_options: (), 7 | importer_state: ( 8 | id: Some("3991506e-ed7e-4bcb-8cfd-3366b31a6439"), 9 | ), 10 | assets: [ 11 | ( 12 | id: "3991506e-ed7e-4bcb-8cfd-3366b31a6439", 13 | search_tags: [ 14 | ("file_name", Some("demo_level.prefab")), 15 | ], 16 | build_pipeline: None, 17 | artifact: Some(( 18 | hash: 13527730066795646633, 19 | id: "3991506e-ed7e-4bcb-8cfd-3366b31a6439", 20 | build_deps: [], 21 | load_deps: [], 22 | compression: None, 23 | compressed_size: Some(2714), 24 | uncompressed_size: Some(2714), 25 | type_id: "5e751ea4-e63b-4192-a008-f5bf8674e45b", 26 | )), 27 | ), 28 | ], 29 | ) -------------------------------------------------------------------------------- /examples/example-sdl2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-sdl2" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | minimum-sdl2 = { path = "../../contrib/minimum-sdl2" } 12 | minimum-skulpin = { path = "../../contrib/minimum-skulpin" } 13 | minimum-nphysics2d = { path = "../../contrib/minimum-nphysics2d" } 14 | 15 | example-shared = { path = "../example-shared" } 16 | 17 | sdl2 = { version = "0.34", features = ["bundled", "static-link"] } 18 | 19 | skulpin = { version = "0.11", default-features = false, features = ["skulpin_sdl2", "skia_complete", "winit-22"] } 20 | skulpin-plugin-imgui = "0.7" 21 | 22 | imgui-inspect-derive = "0.6" 23 | imgui-inspect = "0.6" 24 | 25 | imgui = "0.5" 26 | imgui-sdl2 = "0.12.0" 27 | 28 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 29 | 30 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 31 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 32 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 33 | 34 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 35 | 36 | glam = { version = "0.8.5", features = ["serde"] } 37 | 38 | serde = "1" 39 | uuid = "0.8" 40 | type-uuid = "0.1" 41 | itertools = "0.8" 42 | 43 | serde-diff = "0.3" 44 | 45 | log="0.4" 46 | env_logger = "0.6" -------------------------------------------------------------------------------- /examples/example-sdl2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Setup logging 3 | env_logger::Builder::from_default_env() 4 | .filter_level(log::LevelFilter::Debug) 5 | .init(); 6 | 7 | // Spawn the daemon in a background thread. This could be a different process, but 8 | // for simplicity we'll launch it here. 9 | std::thread::spawn(move || { 10 | minimum::daemon::create_default_asset_daemon() 11 | .with_importer("prefab", minimum::pipeline::PrefabImporter::default()) 12 | .run(); 13 | }); 14 | 15 | example_sdl2::run(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/example-sdl2/src/registration.rs: -------------------------------------------------------------------------------- 1 | use minimum::resources::*; 2 | use minimum::components::*; 3 | 4 | use minimum::editor::EditorSelectRegistry; 5 | use minimum::editor::EditorSelectRegistryBuilder; 6 | use minimum::editor::EditorInspectRegistry; 7 | use minimum::editor::EditorInspectRegistryBuilder; 8 | 9 | use minimum::ComponentRegistry; 10 | 11 | use minimum_skulpin::components::*; 12 | use minimum_nphysics2d::components::*; 13 | 14 | use atelier_assets::loader::rpc_loader::RpcLoader; 15 | 16 | /// Create the asset manager that has all the required types registered 17 | pub fn create_asset_manager(loader: RpcLoader) -> AssetResource { 18 | let mut asset_manager = AssetResource::new(loader); 19 | asset_manager.add_storage::(); 20 | asset_manager 21 | } 22 | 23 | pub fn create_component_registry() -> ComponentRegistry { 24 | minimum::ComponentRegistryBuilder::new() 25 | .auto_register_components() 26 | .add_spawn_mapping_into::() 27 | .add_spawn_mapping_into::() 28 | .add_spawn_mapping::() 29 | .add_spawn_mapping::() 30 | .add_spawn_mapping_into::() 31 | .build() 32 | } 33 | 34 | pub fn create_editor_selection_registry() -> EditorSelectRegistry { 35 | EditorSelectRegistryBuilder::new() 36 | .register::() 37 | .register::() 38 | .register_transformed::() 39 | .register_transformed::() 40 | .build() 41 | } 42 | 43 | pub fn create_editor_inspector_registry() -> EditorInspectRegistry { 44 | EditorInspectRegistryBuilder::default() 45 | .register::() 46 | .register::() 47 | .register::() 48 | .register::() 49 | .register::() 50 | .build() 51 | } 52 | -------------------------------------------------------------------------------- /examples/example-sdl2/src/systems/app_control_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use sdl2::keyboard::Keycode; 3 | use minimum::resources::InputResource; 4 | use minimum::resources::AppControlResource; 5 | use minimum_sdl2::input::Sdl2KeyboardKey; 6 | 7 | pub fn quit_if_escape_pressed(schedule: &mut legion::systems::Builder) { 8 | schedule.add_system( 9 | SystemBuilder::new("quit_if_escape_pressed") 10 | .read_resource::() 11 | .write_resource::() 12 | .build(|_, _, (input_state, app_control), _| { 13 | if input_state.is_key_down(Sdl2KeyboardKey::new(Keycode::Escape).into()) { 14 | app_control.enqueue_terminate_process(); 15 | } 16 | }), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/example-sdl2/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod app_control_systems; 2 | pub use app_control_systems::quit_if_escape_pressed; 3 | 4 | mod draw_systems; 5 | pub use draw_systems::draw; 6 | 7 | use minimum::systems::*; 8 | use example_shared::systems::*; 9 | 10 | use legion::*; 11 | 12 | use minimum::editor::resources::EditorMode; 13 | use minimum_nphysics2d::systems::{update_physics, read_from_physics}; 14 | 15 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 16 | pub struct ScheduleCriteria { 17 | is_simulation_paused: bool, 18 | editor_mode: EditorMode, 19 | } 20 | 21 | impl ScheduleCriteria { 22 | pub fn new( 23 | is_simulation_paused: bool, 24 | editor_mode: EditorMode, 25 | ) -> Self { 26 | ScheduleCriteria { 27 | is_simulation_paused, 28 | editor_mode, 29 | } 30 | } 31 | } 32 | 33 | struct ScheduleBuilder<'a> { 34 | criteria: &'a ScheduleCriteria, 35 | schedule: legion::systems::Builder, 36 | } 37 | 38 | impl<'a> ScheduleBuilder<'a> { 39 | fn new(criteria: &'a ScheduleCriteria) -> Self { 40 | ScheduleBuilder::<'a> { 41 | criteria, 42 | schedule: Default::default(), 43 | } 44 | } 45 | 46 | fn build(mut self) -> Schedule { 47 | self.schedule.build() 48 | } 49 | 50 | fn always( 51 | mut self, 52 | f: F, 53 | ) -> Self 54 | where 55 | F: Fn(&mut legion::systems::Builder), 56 | { 57 | (f)(&mut self.schedule); 58 | self 59 | } 60 | 61 | fn editor_only( 62 | mut self, 63 | f: F, 64 | ) -> Self 65 | where 66 | F: Fn(&mut legion::systems::Builder), 67 | { 68 | if self.criteria.editor_mode == EditorMode::Active { 69 | (f)(&mut self.schedule); 70 | } 71 | 72 | self 73 | } 74 | 75 | fn simulation_unpaused_only( 76 | mut self, 77 | f: F, 78 | ) -> Self 79 | where 80 | F: Fn(&mut legion::systems::Builder), 81 | { 82 | if !self.criteria.is_simulation_paused { 83 | (f)(&mut self.schedule); 84 | } 85 | 86 | self 87 | } 88 | 89 | fn always_thread_local( 90 | mut self, 91 | f: F, 92 | ) -> Self { 93 | self.schedule.add_thread_local_fn(f); 94 | self 95 | } 96 | 97 | fn flush(mut self) -> Self { 98 | self.schedule.flush(); 99 | self 100 | } 101 | } 102 | 103 | pub fn create_update_schedule(criteria: &ScheduleCriteria) -> Schedule { 104 | use minimum::editor::systems::*; 105 | 106 | ScheduleBuilder::new(criteria) 107 | .always(update_input_resource) 108 | .always(advance_time) 109 | .always(quit_if_escape_pressed) 110 | .always_thread_local(update_asset_manager) 111 | .always(update_fps_text) 112 | .always(update_physics) 113 | .simulation_unpaused_only(read_from_physics) 114 | // --- Editor stuff here --- 115 | // Prepare to handle editor input 116 | .always_thread_local(editor_refresh_selection_world) 117 | // Editor input 118 | .always_thread_local(reload_editor_state_if_file_changed) 119 | .always(editor_keybinds) 120 | .always(editor_mouse_input) 121 | .always(editor_update_editor_draw) 122 | .always(editor_gizmos) 123 | .always(editor_handle_selection) 124 | .always(editor_imgui_menu) 125 | .always(editor_entity_list_window) 126 | .always_thread_local(editor_inspector_window) 127 | // Editor processing 128 | .always_thread_local(editor_process_edit_diffs) 129 | .always_thread_local(editor_process_selection_ops) 130 | .always_thread_local(editor_process_editor_ops) 131 | // Editor output 132 | .always(draw_selection_shapes) 133 | // --- End editor stuff --- 134 | .always(input_reset_for_next_frame) 135 | .build() 136 | } 137 | 138 | pub fn create_draw_schedule(criteria: &ScheduleCriteria) -> Schedule { 139 | ScheduleBuilder::new(criteria).always(draw).build() 140 | } 141 | -------------------------------------------------------------------------------- /examples/example-shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-shared" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | imgui-inspect-derive = "0.6" 13 | imgui-inspect = "0.6" 14 | 15 | imgui = "0.5" 16 | imgui-sdl2 = "0.12.0" 17 | 18 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 19 | 20 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 21 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 22 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 23 | 24 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 25 | 26 | glam = { version = "0.8.5", features = ["serde"] } 27 | 28 | #structopt = "0.3" 29 | serde = "1" 30 | uuid = "0.8" 31 | type-uuid = "0.1" 32 | #inventory = "0.1" 33 | itertools = "0.8" 34 | 35 | serde-diff = "0.3" 36 | 37 | log="0.4" 38 | env_logger = "0.6" -------------------------------------------------------------------------------- /examples/example-shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | pub mod resources; 6 | pub mod systems; 7 | pub mod viewport; 8 | -------------------------------------------------------------------------------- /examples/example-shared/src/resources/fps_text.rs: -------------------------------------------------------------------------------- 1 | pub struct FpsTextResource { 2 | pub last_fps_text_change: Option, 3 | pub fps_text: String, 4 | } 5 | 6 | impl FpsTextResource { 7 | pub fn new() -> Self { 8 | FpsTextResource { 9 | last_fps_text_change: None, 10 | fps_text: "".to_string(), 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/example-shared/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod fps_text; 2 | pub use fps_text::FpsTextResource; 3 | -------------------------------------------------------------------------------- /examples/example-shared/src/systems/fps_text_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use crate::resources::FpsTextResource; 4 | use minimum::resources::TimeResource; 5 | 6 | pub fn update_fps_text(schedule: &mut legion::systems::Builder) { 7 | schedule.add_system( 8 | SystemBuilder::new("update fps text") 9 | .read_resource::() 10 | .write_resource::() 11 | .build(|_, _, (time_resource, fps_text), _| { 12 | let now = time_resource.time_state.current_instant(); 13 | // 14 | // Update FPS once a second 15 | // 16 | let update_text_string = match fps_text.last_fps_text_change { 17 | Some(last_update_instant) => (now - last_update_instant).as_secs_f32() >= 1.0, 18 | None => true, 19 | }; 20 | 21 | // Refresh FPS text 22 | if update_text_string { 23 | let fps = time_resource.time_state.updates_per_second(); 24 | fps_text.fps_text = format!("Fps: {:.1}", fps); 25 | fps_text.last_fps_text_change = Some(now); 26 | } 27 | }), 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-shared/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod fps_text_systems; 2 | pub use fps_text_systems::update_fps_text; 3 | -------------------------------------------------------------------------------- /examples/example-shared/src/viewport.rs: -------------------------------------------------------------------------------- 1 | use minimum::resources::ViewportResource; 2 | 3 | // this is a virtual coordinate system 4 | // top-left: (0, 0) 5 | // bottom-right: (600 * aspect_ratio, 600) where aspect_ratio is window_width / window_height 6 | fn calculate_screen_space_matrix( 7 | _viewport_size_in_pixels: glam::Vec2, 8 | view_half_extents: glam::Vec2, 9 | ) -> glam::Mat4 { 10 | let view = glam::Mat4::look_at_rh( 11 | glam::Vec3::from([0.0, 0.0, 5.0]), 12 | glam::Vec3::from([0.0, 0.0, 0.0]), 13 | glam::Vec3::from([0.0, 1.0, 0.0]), 14 | ); 15 | 16 | let projection = glam::Mat4::orthographic_rh( 17 | 0.0, 18 | view_half_extents.x() * 2.0, 19 | view_half_extents.y() * 2.0, 20 | 0.0, 21 | -100.0, 22 | 100.0, 23 | ); 24 | 25 | projection * view 26 | } 27 | 28 | // this is a virtual coordinate system where h = 600 and w = 600 * aspect_ratio where 29 | // aspect_ratio is window_width / window_height 30 | // top-left: (-w/2, -h/2) 31 | // bottom-right: (w/2, h/2) 32 | fn calculate_world_space_view_matrix( 33 | _viewport_size_in_pixels: glam::Vec2, 34 | _position: glam::Vec3, 35 | _view_half_extents: glam::Vec2, 36 | ) -> glam::Mat4 { 37 | glam::Mat4::look_at_rh( 38 | glam::Vec3::from([0.0, 0.0, 5.0]), 39 | glam::Vec3::from([0.0, 0.0, 0.0]), 40 | glam::Vec3::from([0.0, 1.0, 0.0]), 41 | ) 42 | } 43 | 44 | fn calculate_world_space_proj_matrix( 45 | _viewport_size_in_pixels: glam::Vec2, 46 | position: glam::Vec3, 47 | view_half_extents: glam::Vec2, 48 | ) -> glam::Mat4 { 49 | glam::Mat4::orthographic_rh( 50 | position.x() - view_half_extents.x(), 51 | position.x() + view_half_extents.x(), 52 | position.y() + view_half_extents.y(), 53 | position.y() - view_half_extents.y(), 54 | -100.0, 55 | 100.0, 56 | ) 57 | } 58 | 59 | pub fn update_viewport( 60 | viewport: &mut ViewportResource, 61 | viewport_size_in_pixels: glam::Vec2, 62 | camera_position: glam::Vec2, 63 | x_half_extents: f32, 64 | ) -> glam::Vec2 { 65 | let y_half_extents = 66 | x_half_extents / (viewport_size_in_pixels.x() / viewport_size_in_pixels.y()); 67 | 68 | let view_half_extents = glam::Vec2::new(x_half_extents, y_half_extents); 69 | 70 | let camera_position = glam::Vec3::new(camera_position.x(), camera_position.y(), 0.0); 71 | 72 | viewport.set_viewport_size_in_pixels(viewport_size_in_pixels); 73 | viewport.set_screen_space_view(calculate_screen_space_matrix( 74 | viewport_size_in_pixels, 75 | view_half_extents, 76 | )); 77 | 78 | viewport.set_world_space_view( 79 | calculate_world_space_proj_matrix( 80 | viewport_size_in_pixels, 81 | camera_position, 82 | view_half_extents, 83 | ), 84 | calculate_world_space_view_matrix( 85 | viewport_size_in_pixels, 86 | camera_position, 87 | view_half_extents, 88 | ), 89 | camera_position, 90 | ); 91 | 92 | view_half_extents 93 | } 94 | -------------------------------------------------------------------------------- /examples/example-winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-winit" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | minimum-winit = { path = "../../contrib/minimum-winit" } 12 | minimum-skulpin = { path = "../../contrib/minimum-skulpin" } 13 | minimum-nphysics2d = { path = "../../contrib/minimum-nphysics2d" } 14 | 15 | example-shared = { path = "../example-shared" } 16 | 17 | skulpin = { version = "0.11", default-features = false, features = ["skia_complete", "skulpin_winit", "winit-22"] } 18 | skulpin-plugin-imgui = "0.7" 19 | 20 | imgui-inspect-derive = "0.6" 21 | imgui-inspect = "0.6" 22 | 23 | imgui = "0.5" 24 | imgui-winit-support = "0.5.0" 25 | 26 | crossbeam-channel = "0.4" 27 | 28 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 29 | 30 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 31 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 32 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 33 | 34 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 35 | 36 | glam = { version = "0.8.5", features = ["serde"] } 37 | 38 | structopt = "0.3" 39 | serde = "1" 40 | uuid = "0.8" 41 | type-uuid = "0.1" 42 | image2 = { version = "0.11", features = [ "ser" ] } 43 | inventory = "0.1" 44 | 45 | # We need this PR (https://github.com/servo/bincode/pull/288) but it's not published yet 46 | bincode = "1.3.1" 47 | mopa = "0.2" 48 | itertools = "0.8" 49 | 50 | serde-diff = "0.3" 51 | 52 | ron = "0.5" 53 | erased-serde = "0.3" 54 | 55 | log="0.4" 56 | env_logger = "0.6" -------------------------------------------------------------------------------- /examples/example-winit/src/main.rs: -------------------------------------------------------------------------------- 1 | use skulpin::app::LogicalSize; 2 | 3 | use std::ffi::CString; 4 | 5 | use example_winit::app::App; 6 | use example_winit::DemoApp; 7 | 8 | fn main() { 9 | // Setup logging 10 | env_logger::Builder::from_default_env() 11 | .filter_level(log::LevelFilter::Debug) 12 | .filter_module("tokio_reactor", log::LevelFilter::Info) 13 | .init(); 14 | 15 | // Spawn the daemon in a background thread. This could be a different process, but 16 | // for simplicity we'll launch it here. 17 | std::thread::spawn(move || { 18 | minimum::daemon::create_default_asset_daemon() 19 | .with_importer("prefab", minimum::pipeline::PrefabImporter::default()) 20 | .run(); 21 | }); 22 | 23 | // Build the app and run it 24 | let example_app = DemoApp::new(); 25 | let renderer_builder = skulpin::RendererBuilder::new() 26 | .app_name(CString::new("Minimum Winit Example").unwrap()) 27 | .use_vulkan_debug_layer(true); 28 | 29 | App::run(example_app, LogicalSize::new(900, 600), renderer_builder); 30 | } 31 | -------------------------------------------------------------------------------- /examples/example-winit/src/systems/app_control_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use skulpin::winit::event::VirtualKeyCode; 3 | use minimum::resources::InputResource; 4 | use minimum::resources::AppControlResource; 5 | use minimum_winit::input::WinitKeyboardKey; 6 | 7 | pub fn quit_if_escape_pressed(schedule: &mut legion::systems::Builder) { 8 | schedule.add_system( 9 | SystemBuilder::new("quit_if_escape_pressed") 10 | .read_resource::() 11 | .write_resource::() 12 | .build(|_, _, (input_state, app_control), _| { 13 | if input_state.is_key_down(WinitKeyboardKey::new(VirtualKeyCode::Escape).into()) { 14 | app_control.enqueue_terminate_process(); 15 | } 16 | }), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/example-winit/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | pub use example_shared::systems::*; 2 | 3 | mod app_control_systems; 4 | pub use app_control_systems::quit_if_escape_pressed; 5 | 6 | mod draw_systems; 7 | pub use draw_systems::draw; 8 | 9 | use minimum::systems::*; 10 | 11 | use legion::*; 12 | 13 | use minimum::editor::resources::EditorMode; 14 | use minimum_nphysics2d::systems::*; 15 | 16 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 17 | pub struct ScheduleCriteria { 18 | is_simulation_paused: bool, 19 | editor_mode: EditorMode, 20 | } 21 | 22 | impl ScheduleCriteria { 23 | pub fn new( 24 | is_simulation_paused: bool, 25 | editor_mode: EditorMode, 26 | ) -> Self { 27 | ScheduleCriteria { 28 | is_simulation_paused, 29 | editor_mode, 30 | } 31 | } 32 | } 33 | 34 | struct ScheduleBuilder<'a> { 35 | criteria: &'a ScheduleCriteria, 36 | schedule: legion::systems::Builder, 37 | } 38 | 39 | impl<'a> ScheduleBuilder<'a> { 40 | fn new(criteria: &'a ScheduleCriteria) -> Self { 41 | ScheduleBuilder::<'a> { 42 | criteria, 43 | schedule: Default::default(), 44 | } 45 | } 46 | 47 | fn build(mut self) -> Schedule { 48 | self.schedule.build() 49 | } 50 | 51 | fn always( 52 | mut self, 53 | f: F, 54 | ) -> Self 55 | where 56 | F: Fn(&mut legion::systems::Builder), 57 | { 58 | (f)(&mut self.schedule); 59 | self 60 | } 61 | 62 | fn editor_only( 63 | mut self, 64 | f: F, 65 | ) -> Self 66 | where 67 | F: Fn(&mut legion::systems::Builder), 68 | { 69 | if self.criteria.editor_mode == EditorMode::Active { 70 | (f)(&mut self.schedule); 71 | } 72 | 73 | self 74 | } 75 | 76 | fn simulation_unpaused_only( 77 | mut self, 78 | f: F, 79 | ) -> Self 80 | where 81 | F: Fn(&mut legion::systems::Builder), 82 | { 83 | if !self.criteria.is_simulation_paused { 84 | (f)(&mut self.schedule); 85 | } 86 | 87 | self 88 | } 89 | 90 | fn always_thread_local( 91 | mut self, 92 | f: F, 93 | ) -> Self { 94 | self.schedule.add_thread_local_fn(f); 95 | self 96 | } 97 | 98 | fn flush(mut self) -> Self { 99 | self.schedule.flush(); 100 | self 101 | } 102 | } 103 | 104 | pub fn create_update_schedule(criteria: &ScheduleCriteria) -> Schedule { 105 | use minimum::editor::systems::*; 106 | 107 | ScheduleBuilder::new(criteria) 108 | .always(update_input_resource) 109 | .always(advance_time) 110 | .always(quit_if_escape_pressed) 111 | .always_thread_local(update_asset_manager) 112 | .always(update_fps_text) 113 | .always(update_physics) 114 | .simulation_unpaused_only(read_from_physics) 115 | // --- Editor stuff here --- 116 | // Prepare to handle editor input 117 | .always_thread_local(editor_refresh_selection_world) 118 | // Editor input 119 | .always_thread_local(reload_editor_state_if_file_changed) 120 | .always(editor_keybinds) 121 | .always(editor_mouse_input) 122 | .always(editor_update_editor_draw) 123 | .always(editor_gizmos) 124 | .always(editor_handle_selection) 125 | .always(editor_imgui_menu) 126 | .always(editor_entity_list_window) 127 | .always_thread_local(editor_inspector_window) 128 | // Editor processing 129 | .always_thread_local(editor_process_edit_diffs) 130 | .always_thread_local(editor_process_selection_ops) 131 | .always_thread_local(editor_process_editor_ops) 132 | // Editor output 133 | .always(draw_selection_shapes) 134 | // --- End editor stuff --- 135 | .always(input_reset_for_next_frame) 136 | .build() 137 | } 138 | 139 | pub fn create_draw_schedule(criteria: &ScheduleCriteria) -> Schedule { 140 | ScheduleBuilder::new(criteria).always(draw).build() 141 | } 142 | -------------------------------------------------------------------------------- /examples/fonts/feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/examples/fonts/feather.ttf -------------------------------------------------------------------------------- /examples/fonts/fontawesome-470.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/examples/fonts/fontawesome-470.ttf -------------------------------------------------------------------------------- /examples/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/examples/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /examples/fonts/mplus-1p-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aclysma/minimum/f7c2ba6f76976eb2bce6a950eaa54e0377809bb2/examples/fonts/mplus-1p-regular.ttf -------------------------------------------------------------------------------- /examples/tutorial/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tutorial" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum = { path = "../../minimum" } 11 | 12 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 13 | 14 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 15 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 16 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 17 | 18 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 19 | 20 | serde = "1" 21 | uuid = "0.8" 22 | type-uuid = "0.1" 23 | itertools = "0.8" 24 | fnv = "1.0" 25 | 26 | ron = "0.5" 27 | serde-diff = "0.3" 28 | 29 | log="0.4" 30 | env_logger = "0.6" 31 | 32 | # Not required, but we'll use it for math in the tutorial 33 | glam = "0.8.6" -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial001_dev_environment_setup.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use glam::Vec2; 3 | 4 | struct PositionComponent(pub glam::Vec2); 5 | struct VelocityComponent(pub glam::Vec2); 6 | struct Gravity(pub glam::Vec2); 7 | 8 | fn main() { 9 | // Setup logging 10 | env_logger::Builder::from_default_env() 11 | .filter_level(log::LevelFilter::Debug) 12 | .init(); 13 | 14 | // Spawn the daemon in a background thread. This could be a different process, but 15 | // for simplicity we'll launch it here. 16 | std::thread::spawn(move || { 17 | minimum::daemon::create_default_asset_daemon(); 18 | }); 19 | 20 | // Create a legion world 21 | let mut world = World::default(); 22 | let mut resources = Resources::default(); 23 | 24 | // Insert a resource that can be queried to find gravity 25 | resources.insert(Gravity(-9.8 * Vec2::unit_y())); 26 | 27 | // Insert an object with position and velocity 28 | let entity = *world 29 | .extend((0..1).map(|_| { 30 | ( 31 | PositionComponent(Vec2::new(0.0, 500.0)), 32 | VelocityComponent(Vec2::new(5.0, 0.0)), 33 | ) 34 | })) 35 | .first() 36 | .unwrap(); 37 | 38 | for _ in 0..10 { 39 | // Fetch gravity... and integrate it to velocity. 40 | let gravity = resources.get::().unwrap(); 41 | let mut query = >::query(); 42 | for vel in query.iter_mut(&mut world) { 43 | vel.0 += gravity.0; 44 | } 45 | 46 | // Iterate across all entities and integrate velocity to position 47 | let mut query = <(Write, TryRead)>::query(); 48 | for (pos, vel) in query.iter_mut(&mut world) { 49 | if let Some(vel) = vel { 50 | pos.0 += vel.0; 51 | } 52 | 53 | pos.0 += gravity.0; 54 | } 55 | 56 | let position = world 57 | .entry_ref(entity) 58 | .unwrap() 59 | .into_component::() 60 | .unwrap(); 61 | println!("Position is {}", position.0); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial002_creating_prefabs.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use glam::Vec2; 4 | 5 | use type_uuid::TypeUuid; 6 | 7 | use serde::Serialize; 8 | use serde::Deserialize; 9 | use serde_diff::SerdeDiff; 10 | 11 | use legion_prefab::Prefab; 12 | 13 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 14 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 15 | pub struct PositionComponent { 16 | #[serde_diff(opaque)] 17 | pub value: Vec2, 18 | } 19 | 20 | legion_prefab::register_component_type!(PositionComponent); 21 | 22 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 23 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 24 | pub struct VelocityComponent { 25 | #[serde_diff(opaque)] 26 | pub value: Vec2, 27 | } 28 | 29 | legion_prefab::register_component_type!(VelocityComponent); 30 | 31 | fn main() { 32 | // Setup logging 33 | env_logger::Builder::from_default_env() 34 | .filter_level(log::LevelFilter::Debug) 35 | .init(); 36 | 37 | // Spawn the daemon in a background thread. This could be a different process, but 38 | // for simplicity we'll launch it here. 39 | std::thread::spawn(move || { 40 | minimum::daemon::create_default_asset_daemon(); 41 | }); 42 | 43 | // Register all components (based on legion_prefab::register_component_type! macro) 44 | let _component_registry = minimum::ComponentRegistryBuilder::new() 45 | .auto_register_components() 46 | .build(); 47 | 48 | // Create a world and insert data into it that we would like to save into a prefab 49 | let mut prefab_world = World::default(); 50 | let prefab_entity = *prefab_world 51 | .extend((0..1).map(|_| { 52 | ( 53 | PositionComponent { 54 | value: Vec2::new(0.0, 500.0), 55 | }, 56 | VelocityComponent { 57 | value: Vec2::new(5.0, 0.0), 58 | }, 59 | ) 60 | })) 61 | .first() 62 | .unwrap(); 63 | 64 | // Create the prefab 65 | let prefab = Prefab::new(prefab_world); 66 | 67 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 68 | let entity_uuid = prefab 69 | .prefab_meta 70 | .entities 71 | .iter() 72 | .find(|(_, value)| **value == prefab_entity) 73 | .map(|(entity_uuid, _)| *entity_uuid) 74 | .unwrap(); 75 | 76 | // Look up the entity associated with the entity_uuid. To do this, we have to: 77 | // - Look at the original prefab to find the UUID of the entity 78 | // - Then use prefab_meta on the deserialized prefab to find the entity in the deserialized 79 | // prefab's world 80 | let entity = prefab.prefab_meta.entities[&entity_uuid]; 81 | 82 | let position = prefab 83 | .world 84 | .entry_ref(entity) 85 | .unwrap() 86 | .into_component::() 87 | .unwrap(); 88 | println!( 89 | "Position of {} is {}", 90 | uuid::Uuid::from_bytes(entity_uuid), 91 | position.value 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial003_save_and_load_prefabs.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use glam::Vec2; 4 | 5 | use type_uuid::TypeUuid; 6 | 7 | use serde::Serialize; 8 | use serde::Deserialize; 9 | use serde_diff::SerdeDiff; 10 | 11 | use legion_prefab::Prefab; 12 | 13 | use minimum::ComponentRegistry; 14 | 15 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 16 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 17 | pub struct PositionComponent { 18 | #[serde_diff(opaque)] 19 | pub value: Vec2, 20 | } 21 | 22 | legion_prefab::register_component_type!(PositionComponent); 23 | 24 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 25 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 26 | pub struct VelocityComponent { 27 | #[serde_diff(opaque)] 28 | pub value: Vec2, 29 | } 30 | 31 | legion_prefab::register_component_type!(VelocityComponent); 32 | 33 | fn main() { 34 | // Setup logging 35 | env_logger::Builder::from_default_env() 36 | .filter_level(log::LevelFilter::Debug) 37 | .init(); 38 | 39 | // Spawn the daemon in a background thread. This could be a different process, but 40 | // for simplicity we'll launch it here. 41 | std::thread::spawn(move || { 42 | minimum::daemon::create_default_asset_daemon(); 43 | }); 44 | 45 | // Register all components (based on legion_prefab::register_component_type! macro) 46 | let component_registry = minimum::ComponentRegistryBuilder::new() 47 | .auto_register_components() 48 | .build(); 49 | 50 | // Create a world and insert data into it that we would like to save into a prefab 51 | let mut prefab_world = World::default(); 52 | let prefab_entity = *prefab_world 53 | .extend((0..1).map(|_| { 54 | ( 55 | PositionComponent { 56 | value: Vec2::new(0.0, 500.0), 57 | }, 58 | VelocityComponent { 59 | value: Vec2::new(5.0, 0.0), 60 | }, 61 | ) 62 | })) 63 | .first() 64 | .unwrap(); 65 | 66 | // Create the prefab 67 | let prefab_uncooked = Prefab::new(prefab_world); 68 | 69 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 70 | let entity_uuid = prefab_uncooked 71 | .prefab_meta 72 | .entities 73 | .iter() 74 | .find(|(_, value)| **value == prefab_entity) 75 | .map(|(entity_uuid, _)| *entity_uuid) 76 | .unwrap(); 77 | 78 | // Serialize the prefab to a string 79 | let serialized_prefab = serialize_prefab(&component_registry, &prefab_uncooked); 80 | 81 | println!("Prefab serialized to string"); 82 | println!("{}", serialized_prefab); 83 | 84 | // Deserialize the world from a string 85 | let deserialized_prefab = deserialize_prefab(&component_registry, &serialized_prefab); 86 | 87 | // Look up the entity associated with the entity_uuid. To do this, we have to: 88 | // - Look at the original prefab to find the UUID of the entity 89 | // - Then use prefab_meta on the deserialized prefab to find the entity in the deserialized 90 | // prefab's world 91 | let deserialized_entity = deserialized_prefab.prefab_meta.entities[&entity_uuid]; 92 | 93 | // Now run some code to demonstrate that we found the same entity in the deserialized world and 94 | // that we get the same results as before 95 | let position = deserialized_prefab 96 | .world 97 | .entry_ref(deserialized_entity) 98 | .unwrap() 99 | .into_component::() 100 | .unwrap(); 101 | 102 | println!( 103 | "Position of {} is {}", 104 | uuid::Uuid::from_bytes(entity_uuid), 105 | position.value 106 | ); 107 | } 108 | 109 | fn serialize_prefab( 110 | component_registry: &ComponentRegistry, 111 | prefab: &Prefab, 112 | ) -> String { 113 | let prefab_serde_context = legion_prefab::PrefabSerdeContext { 114 | registered_components: component_registry.components_by_uuid(), 115 | }; 116 | 117 | let mut ron_ser = ron::ser::Serializer::new(Some(ron::ser::PrettyConfig::default()), true); 118 | let prefab_ser = legion_prefab::PrefabFormatSerializer::new(prefab_serde_context, prefab); 119 | 120 | prefab_format::serialize(&mut ron_ser, &prefab_ser, prefab.prefab_id()) 121 | .expect("failed to round-trip prefab"); 122 | 123 | ron_ser.into_output_string() 124 | } 125 | 126 | fn deserialize_prefab( 127 | component_registry: &ComponentRegistry, 128 | serialized: &str, 129 | ) -> Prefab { 130 | let prefab_serde_context = legion_prefab::PrefabSerdeContext { 131 | registered_components: component_registry.components_by_uuid(), 132 | }; 133 | 134 | let mut deserializer = ron::de::Deserializer::from_str(serialized).unwrap(); 135 | 136 | let prefab_deser = legion_prefab::PrefabFormatDeserializer::new(prefab_serde_context); 137 | prefab_format::deserialize(&mut deserializer, &prefab_deser).unwrap(); 138 | prefab_deser.prefab() 139 | } 140 | -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial004_cooking_prefabs.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use glam::Vec2; 4 | 5 | use type_uuid::TypeUuid; 6 | 7 | use serde::Serialize; 8 | use serde::Deserialize; 9 | use serde_diff::SerdeDiff; 10 | use std::collections::HashMap; 11 | use legion_prefab::Prefab; 12 | 13 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 14 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 15 | pub struct PositionComponent { 16 | #[serde_diff(opaque)] 17 | pub value: Vec2, 18 | } 19 | 20 | legion_prefab::register_component_type!(PositionComponent); 21 | 22 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 23 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 24 | pub struct VelocityComponent { 25 | #[serde_diff(opaque)] 26 | pub value: Vec2, 27 | } 28 | 29 | legion_prefab::register_component_type!(VelocityComponent); 30 | 31 | fn main() { 32 | // Setup logging 33 | env_logger::Builder::from_default_env() 34 | .filter_level(log::LevelFilter::Debug) 35 | .init(); 36 | 37 | // Spawn the daemon in a background thread. This could be a different process, but 38 | // for simplicity we'll launch it here. 39 | std::thread::spawn(move || { 40 | minimum::daemon::create_default_asset_daemon(); 41 | }); 42 | 43 | // Register all components (based on legion_prefab::register_component_type! macro) 44 | let component_registry = minimum::ComponentRegistryBuilder::new() 45 | .auto_register_components() 46 | .build(); 47 | 48 | // Create a world and insert data into it that we would like to save into a prefab 49 | let mut prefab_world = World::default(); 50 | let prefab_entity = *prefab_world 51 | .extend((0..1).map(|_| { 52 | ( 53 | PositionComponent { 54 | value: Vec2::new(0.0, 500.0), 55 | }, 56 | VelocityComponent { 57 | value: Vec2::new(5.0, 0.0), 58 | }, 59 | ) 60 | })) 61 | .first() 62 | .unwrap(); 63 | 64 | // Create the prefab 65 | let prefab = Prefab::new(prefab_world); 66 | 67 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 68 | let entity_uuid = prefab 69 | .prefab_meta 70 | .entities 71 | .iter() 72 | .find(|(_, value)| **value == prefab_entity) 73 | .map(|(entity_uuid, _)| *entity_uuid) 74 | .unwrap(); 75 | 76 | // 77 | // Cook the prefab world 78 | // 79 | 80 | // Prefabs must be cooked in dependency order, based on overrides. For example, if prefab B 81 | // overrides something on prefab A, and prefab C overrides something on prefab B, we must cook 82 | // A, then B, then C. When cooking B, we must be able to access A in cooked form, and when 83 | // cooking C, we must be able to access B in cooked form. 84 | // 85 | // In this case there are no overrides, so our ordering is simply the prefab we want to cook 86 | // and the lookup just contains this prefab. 87 | // 88 | // In the future this can happen in atelier as part of the asset build process. For now, 89 | // cooking happens at runtime in minimum but dependency management is automatically handled. 90 | let prefab_cook_order = vec![prefab.prefab_id()]; 91 | let mut prefab_lookup = HashMap::new(); 92 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 93 | 94 | let cooked_prefab = legion_prefab::cook_prefab( 95 | component_registry.components(), 96 | component_registry.components_by_uuid(), 97 | prefab_cook_order.as_slice(), 98 | &prefab_lookup, 99 | ); 100 | 101 | // Look up the entity associated with the entity_uuid 102 | let cooked_entity = cooked_prefab.entities[&entity_uuid]; 103 | 104 | let position = cooked_prefab 105 | .world 106 | .entry_ref(cooked_entity) 107 | .unwrap() 108 | .into_component::() 109 | .unwrap(); 110 | println!( 111 | "Position of {} is {}", 112 | uuid::Uuid::from_bytes(entity_uuid), 113 | position.value 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial005_spawning_prefabs.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use glam::Vec2; 4 | 5 | use type_uuid::TypeUuid; 6 | 7 | use serde::Serialize; 8 | use serde::Deserialize; 9 | use serde_diff::SerdeDiff; 10 | use std::collections::HashMap; 11 | use legion_prefab::Prefab; 12 | 13 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 14 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 15 | pub struct PositionComponent { 16 | #[serde_diff(opaque)] 17 | pub value: Vec2, 18 | } 19 | 20 | legion_prefab::register_component_type!(PositionComponent); 21 | 22 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 23 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 24 | pub struct VelocityComponent { 25 | #[serde_diff(opaque)] 26 | pub value: Vec2, 27 | } 28 | 29 | legion_prefab::register_component_type!(VelocityComponent); 30 | 31 | fn main() { 32 | // Setup logging 33 | env_logger::Builder::from_default_env() 34 | .filter_level(log::LevelFilter::Debug) 35 | .init(); 36 | 37 | // Spawn the daemon in a background thread. This could be a different process, but 38 | // for simplicity we'll launch it here. 39 | std::thread::spawn(move || { 40 | minimum::daemon::create_default_asset_daemon(); 41 | }); 42 | 43 | // Register all components (based on legion_prefab::register_component_type! macro) 44 | let component_registry = minimum::ComponentRegistryBuilder::new() 45 | .auto_register_components() 46 | .build(); 47 | 48 | // Create a world and insert data into it that we would like to save into a prefab 49 | let mut prefab_world = World::default(); 50 | let prefab_entity = *prefab_world 51 | .extend((0..1).map(|_| { 52 | ( 53 | PositionComponent { 54 | value: Vec2::new(0.0, 500.0), 55 | }, 56 | VelocityComponent { 57 | value: Vec2::new(5.0, 0.0), 58 | }, 59 | ) 60 | })) 61 | .first() 62 | .unwrap(); 63 | 64 | // Create the prefab 65 | let prefab = Prefab::new(prefab_world); 66 | 67 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 68 | let entity_uuid = prefab 69 | .prefab_meta 70 | .entities 71 | .iter() 72 | .find(|(_, value)| **value == prefab_entity) 73 | .map(|(entity_uuid, _)| *entity_uuid) 74 | .unwrap(); 75 | 76 | // 77 | // Cook the prefab world we just deserialized 78 | // 79 | let prefab_cook_order = vec![prefab.prefab_id()]; 80 | let mut prefab_lookup = HashMap::new(); 81 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 82 | 83 | let cooked_prefab = legion_prefab::cook_prefab( 84 | component_registry.components(), 85 | component_registry.components_by_uuid(), 86 | prefab_cook_order.as_slice(), 87 | &prefab_lookup, 88 | ); 89 | 90 | let mut world = World::default(); 91 | let resources = Resources::default(); 92 | 93 | // 94 | // Spawn the prefab in a new world. 95 | // 96 | let mut clone_impl_result = HashMap::default(); 97 | let mut spawn_impl = component_registry.spawn_clone_impl(&resources, &mut clone_impl_result); 98 | world.clone_from(&cooked_prefab.world, &legion::query::any(), &mut spawn_impl); 99 | 100 | // Look up the entity associated with the entity_uuid. To do this, we have to: 101 | // - Look at the cooked prefab to determine which entity is associated with the UUID 102 | // - Then use clone_impl_result in clone_from to find the copy that was cloned into the world 103 | let cooked_entity = cooked_prefab.entities[&entity_uuid]; 104 | let world_entity = clone_impl_result[&cooked_entity]; 105 | 106 | let position = world 107 | .entry_ref(world_entity) 108 | .unwrap() 109 | .into_component::() 110 | .unwrap(); 111 | println!( 112 | "Position of {} is {}", 113 | uuid::Uuid::from_bytes(entity_uuid), 114 | position.value 115 | ); 116 | } 117 | -------------------------------------------------------------------------------- /examples/tutorial/examples/tutorial007_creating_prefab_overrides.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use glam::Vec2; 4 | 5 | use type_uuid::TypeUuid; 6 | 7 | use serde::Serialize; 8 | use serde::Deserialize; 9 | use serde_diff::SerdeDiff; 10 | use std::collections::HashMap; 11 | use legion_prefab::{Prefab, PrefabBuilder}; 12 | 13 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 14 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190000"] 15 | pub struct PositionComponent { 16 | #[serde_diff(opaque)] 17 | pub value: Vec2, 18 | } 19 | 20 | legion_prefab::register_component_type!(PositionComponent); 21 | 22 | #[derive(TypeUuid, Clone, Serialize, Deserialize, SerdeDiff, Debug, Default)] 23 | #[uuid = "8bf67228-f96c-4649-b306-ecd107190001"] 24 | pub struct VelocityComponent { 25 | #[serde_diff(opaque)] 26 | pub value: Vec2, 27 | } 28 | 29 | legion_prefab::register_component_type!(VelocityComponent); 30 | 31 | fn main() { 32 | // Setup logging 33 | env_logger::Builder::from_default_env() 34 | .filter_level(log::LevelFilter::Debug) 35 | .init(); 36 | 37 | // Spawn the daemon in a background thread. This could be a different process, but 38 | // for simplicity we'll launch it here. 39 | std::thread::spawn(move || { 40 | minimum::daemon::create_default_asset_daemon(); 41 | }); 42 | 43 | // Register all components (based on legion_prefab::register_component_type! macro) 44 | let component_registry = minimum::ComponentRegistryBuilder::new() 45 | .auto_register_components() 46 | .build(); 47 | 48 | // Create a world and insert data into it that we would like to save into a prefab 49 | let mut prefab_world = World::default(); 50 | let prefab_entity = *prefab_world 51 | .extend((0..1).map(|_| { 52 | ( 53 | PositionComponent { 54 | value: Vec2::new(0.0, 500.0), 55 | }, 56 | VelocityComponent { 57 | value: Vec2::new(5.0, 0.0), 58 | }, 59 | ) 60 | })) 61 | .first() 62 | .unwrap(); 63 | 64 | // Create the prefab 65 | let prefab = Prefab::new(prefab_world); 66 | 67 | // Get the UUID of the entity. This UUID is maintained throughout the whole chain. 68 | let entity_uuid = prefab 69 | .prefab_meta 70 | .entities 71 | .iter() 72 | .find(|(_, value)| **value == prefab_entity) 73 | .map(|(entity_uuid, _)| *entity_uuid) 74 | .unwrap(); 75 | 76 | // 77 | // Cook the prefab world we just deserialized 78 | // 79 | let prefab_cook_order = vec![prefab.prefab_id()]; 80 | let mut prefab_lookup = HashMap::new(); 81 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 82 | 83 | let cooked_prefab = legion_prefab::cook_prefab( 84 | component_registry.components(), 85 | component_registry.components_by_uuid(), 86 | prefab_cook_order.as_slice(), 87 | &prefab_lookup, 88 | ); 89 | 90 | // 91 | // Use a prefab builder to make a new prefab that overrides a field on the given prefab 92 | // 93 | let mut prefab_builder = PrefabBuilder::new( 94 | prefab.prefab_id(), 95 | cooked_prefab, 96 | component_registry.copy_clone_impl(), 97 | ); 98 | 99 | // Here, we modify the world on the prefab builder. 100 | // The changes here are scanned to produce the prefab. 101 | let prefab_builder_entity = prefab_builder.uuid_to_entity(entity_uuid).unwrap(); 102 | prefab_builder 103 | .world_mut() 104 | .entry(prefab_builder_entity) 105 | .unwrap() 106 | .get_component_mut::() 107 | .unwrap() 108 | .value = glam::Vec2::new(0.0, 1000.0); 109 | 110 | //NOTE: Temporary hack to compile, create_prefab is not generic over the hasher 111 | let components_by_uuid_temp_hack: HashMap<_, _> = component_registry 112 | .components_by_uuid() 113 | .iter() 114 | .map(|(k, v)| (*k, v.clone())) 115 | .collect(); 116 | 117 | // Produce the prefab that overrides the original prefab 118 | let prefab_with_override = prefab_builder 119 | .create_prefab( 120 | &components_by_uuid_temp_hack, 121 | component_registry.copy_clone_impl(), 122 | ) 123 | .unwrap(); 124 | 125 | // 126 | // Cook the prefab that has the override 127 | // 128 | let prefab_cook_order = vec![prefab.prefab_id(), prefab_with_override.prefab_id()]; 129 | let mut prefab_lookup = HashMap::new(); 130 | prefab_lookup.insert(prefab.prefab_id(), &prefab); 131 | prefab_lookup.insert(prefab_with_override.prefab_id(), &prefab_with_override); 132 | 133 | let cooked_prefab_with_override = legion_prefab::cook_prefab( 134 | component_registry.components(), 135 | component_registry.components_by_uuid(), 136 | prefab_cook_order.as_slice(), 137 | &prefab_lookup, 138 | ); 139 | 140 | // Look up the entity in the cooked prefab with override by UUID 141 | let entity = cooked_prefab_with_override.entities[&entity_uuid]; 142 | 143 | let position = cooked_prefab_with_override 144 | .world 145 | .entry_ref(entity) 146 | .unwrap() 147 | .into_component::() 148 | .unwrap(); 149 | println!( 150 | "Position of {} is {}", 151 | uuid::Uuid::from_bytes(entity_uuid), 152 | position.value 153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /examples/tutorial/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Please see the examples folder 2 | -------------------------------------------------------------------------------- /minimum-editor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-editor" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | minimum-game = { path = "../minimum-game" } 12 | minimum-transform = { path = "../minimum-transform" } 13 | minimum-kernel = { path = "../minimum-kernel" } 14 | minimum-math = { path = "../minimum-math", features = ["na_conversion"] } 15 | 16 | imgui-inspect-derive = "0.6" 17 | imgui-inspect = "0.6" 18 | 19 | imgui = "0.5" 20 | 21 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3", features = ["asset_uuid_macro"] } 22 | legion = { version = "0.3", default-features = false, features = ["serialize", "extended-tuple-impls"] } 23 | 24 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 25 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 26 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 27 | 28 | glam = { version = "0.8.5", features = ["serde"] } 29 | 30 | structopt = "0.3" 31 | serde = "1" 32 | uuid = "0.8" 33 | type-uuid = "0.1" 34 | image2 = { version = "0.11", features = [ "ser" ] } 35 | inventory = "0.1" 36 | serde-diff = "0.3" 37 | 38 | # For selection logic 39 | nalgebra = { version = "0.18", features = [ "serde-serialize" ] } 40 | nalgebra-glm = "0.4" 41 | ncollide3d = "0.20" 42 | 43 | ron = "0.5" 44 | 45 | log="0.4" -------------------------------------------------------------------------------- /minimum-editor/src/components/editor_metadata_component.rs: -------------------------------------------------------------------------------- 1 | use type_uuid::TypeUuid; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_diff::SerdeDiff; 4 | use imgui_inspect_derive::Inspect; 5 | 6 | #[derive(TypeUuid, Serialize, Deserialize, SerdeDiff, Debug, PartialEq, Clone, Default, Inspect)] 7 | #[uuid = "9dfad44f-72e8-4ba6-b89a-96b017fb9cd9"] 8 | pub struct EditorMetadataComponent { 9 | pub name: String, 10 | } 11 | 12 | legion_prefab::register_component_type!(EditorMetadataComponent); 13 | -------------------------------------------------------------------------------- /minimum-editor/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | mod editor_metadata_component; 2 | pub use editor_metadata_component::EditorMetadataComponent; 3 | -------------------------------------------------------------------------------- /minimum-editor/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | #[allow(unused_imports)] 6 | #[macro_use] 7 | extern crate imgui; 8 | 9 | //TODO: This is for selection, get rid of this when possible 10 | extern crate nalgebra as na; 11 | extern crate nalgebra_glm as glm; 12 | 13 | mod select; 14 | pub use select::EditorSelectRegistryBuilder; 15 | pub use select::EditorSelectRegistry; 16 | pub use select::EditorSelectable; 17 | pub use select::EditorSelectableTransformed; 18 | 19 | mod inspect; 20 | pub use inspect::EditorInspectRegistryBuilder; 21 | pub use inspect::EditorInspectRegistry; 22 | 23 | pub mod components; 24 | pub mod resources; 25 | pub mod systems; 26 | -------------------------------------------------------------------------------- /minimum-editor/src/resources/editor_inspect_registry.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref}; 2 | use crate::inspect::EditorInspectRegistry; 3 | 4 | // For now just wrap the input helper that skulpin provides 5 | pub struct EditorInspectRegistryResource { 6 | registry: EditorInspectRegistry, 7 | } 8 | 9 | impl EditorInspectRegistryResource { 10 | pub fn new(registry: EditorInspectRegistry) -> Self { 11 | EditorInspectRegistryResource { registry } 12 | } 13 | 14 | pub fn registry(&self) -> &EditorInspectRegistry { 15 | &self.registry 16 | } 17 | } 18 | 19 | impl Deref for EditorInspectRegistryResource { 20 | type Target = EditorInspectRegistry; 21 | 22 | fn deref(&self) -> &Self::Target { 23 | self.registry() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /minimum-editor/src/resources/editor_selection.rs: -------------------------------------------------------------------------------- 1 | use ncollide3d::world::CollisionWorld; 2 | use ncollide3d::bounding_volume::AABB; 3 | use legion::*; 4 | 5 | use std::collections::HashSet; 6 | use std::collections::HashMap; 7 | use std::sync::Arc; 8 | 9 | use crate::resources::EditorStateResource; 10 | use crate::EditorSelectRegistry; 11 | 12 | enum SelectionOp { 13 | Add(Vec), 14 | Remove(Vec), 15 | Set(Vec), 16 | Clear, 17 | } 18 | 19 | pub struct EditorSelectionResource { 20 | registry: Arc, 21 | editor_selection_world: CollisionWorld, 22 | 23 | // These are entities in the world 24 | selected_entities: HashSet, 25 | 26 | pending_selection_ops: Vec, 27 | } 28 | 29 | impl EditorSelectionResource { 30 | pub fn new(registry: EditorSelectRegistry) -> Self { 31 | let editor_selection_world = registry.create_empty_editor_selection_world(); 32 | 33 | EditorSelectionResource { 34 | registry: Arc::new(registry), 35 | editor_selection_world, 36 | selected_entities: Default::default(), 37 | pending_selection_ops: Default::default(), 38 | } 39 | } 40 | 41 | pub fn create_editor_selection_world( 42 | resources: &Resources, 43 | world: &World, 44 | ) -> CollisionWorld { 45 | let registry = { resources.get::().unwrap().registry.clone() }; 46 | 47 | registry.create_editor_selection_world(resources, world) 48 | } 49 | 50 | pub fn set_editor_selection_world( 51 | &mut self, 52 | editor_selection_world: CollisionWorld, 53 | ) { 54 | self.editor_selection_world = editor_selection_world; 55 | } 56 | 57 | pub fn editor_selection_world(&mut self) -> &CollisionWorld { 58 | &self.editor_selection_world 59 | } 60 | 61 | pub fn selected_entities(&self) -> &HashSet { 62 | &self.selected_entities 63 | } 64 | 65 | pub fn selected_entity_aabbs(&mut self) -> HashMap>> { 66 | Self::get_entity_aabbs(&self.selected_entities, &self.editor_selection_world) 67 | } 68 | 69 | pub fn enqueue_add_to_selection( 70 | &mut self, 71 | entities: Vec, 72 | ) { 73 | log::info!("add entities {:?} from selection", entities); 74 | self.pending_selection_ops.push(SelectionOp::Add(entities)); 75 | } 76 | 77 | pub fn enqueue_remove_from_selection( 78 | &mut self, 79 | entities: Vec, 80 | ) { 81 | log::info!("remove entities {:?} to selection", entities); 82 | self.pending_selection_ops 83 | .push(SelectionOp::Remove(entities)); 84 | } 85 | 86 | pub fn enqueue_clear_selection(&mut self) { 87 | log::info!("Clear selection"); 88 | self.pending_selection_ops.push(SelectionOp::Clear); 89 | } 90 | 91 | pub fn enqueue_set_selection( 92 | &mut self, 93 | selected_entities: Vec, 94 | ) { 95 | log::trace!("Selected entities: {:?}", selected_entities); 96 | self.pending_selection_ops 97 | .push(SelectionOp::Set(selected_entities)); 98 | } 99 | 100 | pub fn is_entity_selected( 101 | &self, 102 | entity: Entity, 103 | ) -> bool { 104 | self.selected_entities.contains(&entity) 105 | } 106 | 107 | pub fn process_selection_ops( 108 | &mut self, 109 | _editor_state: &mut EditorStateResource, 110 | _world: &mut World, 111 | ) -> bool { 112 | let ops: Vec<_> = self.pending_selection_ops.drain(..).collect(); 113 | 114 | let mut changed = false; 115 | for op in ops { 116 | changed |= match op { 117 | SelectionOp::Add(entities) => { 118 | let mut changed = false; 119 | for e in entities { 120 | changed |= self.selected_entities.insert(e); 121 | } 122 | 123 | changed 124 | } 125 | SelectionOp::Remove(entities) => { 126 | let mut changed = false; 127 | for e in entities { 128 | changed |= self.selected_entities.remove(&e); 129 | } 130 | 131 | changed 132 | } 133 | SelectionOp::Clear => { 134 | if !self.selected_entities.is_empty() { 135 | self.selected_entities.clear(); 136 | true 137 | } else { 138 | false 139 | } 140 | } 141 | SelectionOp::Set(entities) => { 142 | self.selected_entities = entities.iter().copied().collect(); 143 | true 144 | } 145 | } 146 | } 147 | 148 | changed 149 | } 150 | 151 | // The main reason for having such a specific function here is that it's awkward for an external 152 | // caller to borrow entities and world seperately 153 | fn get_entity_aabbs( 154 | entities: &HashSet, 155 | world: &CollisionWorld, 156 | ) -> HashMap>> { 157 | let mut entity_aabbs = HashMap::new(); 158 | for e in entities { 159 | entity_aabbs.insert(*e, None); 160 | } 161 | 162 | for (_, shape) in world.collision_objects() { 163 | let _entry = 164 | entity_aabbs 165 | .entry(*shape.data()) 166 | .and_modify(|aabb: &mut Option>| { 167 | let mut new_aabb = shape.shape().aabb(shape.position()); 168 | if let Some(existing_aabb) = aabb { 169 | use ncollide3d::bounding_volume::BoundingVolume; 170 | new_aabb.merge(existing_aabb); 171 | }; 172 | 173 | *aabb = Some(new_aabb); 174 | }); 175 | } 176 | 177 | entity_aabbs 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /minimum-editor/src/resources/editor_settings.rs: -------------------------------------------------------------------------------- 1 | use minimum_game::input::KeyboardKey; 2 | 3 | pub struct Keybinds { 4 | pub selection_add: KeyboardKey, 5 | pub selection_subtract: KeyboardKey, 6 | pub selection_toggle: KeyboardKey, 7 | 8 | pub tool_translate: KeyboardKey, 9 | pub tool_scale: KeyboardKey, 10 | pub tool_rotate: KeyboardKey, 11 | 12 | pub action_quit: KeyboardKey, 13 | pub action_toggle_editor_pause: KeyboardKey, 14 | } 15 | 16 | pub struct EditorSettingsResource { 17 | keybinds: Keybinds, 18 | } 19 | 20 | impl EditorSettingsResource { 21 | pub fn new(keybinds: Keybinds) -> Self { 22 | EditorSettingsResource { keybinds } 23 | } 24 | 25 | pub fn keybinds(&self) -> &Keybinds { 26 | &self.keybinds 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /minimum-editor/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod editor_state; 2 | pub use editor_state::PostCommitSelection; 3 | pub use editor_state::EditorStateResource; 4 | pub use editor_state::EditorTool; 5 | pub use editor_state::EditorMode; 6 | pub use editor_state::EditorTransactionId; 7 | pub use editor_state::EditorTransaction; 8 | pub use editor_state::OpenedPrefabState; 9 | 10 | mod editor_selection; 11 | pub use editor_selection::EditorSelectionResource; 12 | 13 | // mod editor_draw_2d; 14 | // pub use editor_draw_2d::EditorDraw2DResource; 15 | // pub use editor_draw_2d::EditorDraw2DShapeClickedState; 16 | // pub use editor_draw_2d::EditorDraw2DShapeDragState; 17 | 18 | mod editor_draw_3d; 19 | pub use editor_draw_3d::EditorDraw3DResource; 20 | pub use editor_draw_3d::EditorDraw3DShapeClickedState; 21 | pub use editor_draw_3d::EditorDraw3DShapeDragState; 22 | pub use editor_draw_3d::EditorDraw3DConstraint; 23 | 24 | mod editor_inspect_registry; 25 | pub use editor_inspect_registry::EditorInspectRegistryResource; 26 | 27 | mod editor_settings; 28 | pub use editor_settings::Keybinds; 29 | pub use editor_settings::EditorSettingsResource; 30 | -------------------------------------------------------------------------------- /minimum-editor/src/systems/main_menu.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use minimum_game::resources::TimeResource; 4 | use crate::resources::{EditorStateResource}; 5 | use minimum_game::resources::ImguiResource; 6 | use crate::resources::EditorTool; 7 | 8 | use imgui::im_str; 9 | 10 | fn imgui_menu_tool_button( 11 | ui: &imgui::Ui, 12 | editor_state: &mut EditorStateResource, 13 | editor_tool: EditorTool, 14 | string: &'static str, 15 | ) { 16 | let color_stack_token = if editor_state.active_editor_tool() == editor_tool { 17 | Some(ui.push_style_color(imgui::StyleColor::Text, [0.8, 0.0, 0.0, 1.0])) 18 | } else { 19 | None 20 | }; 21 | 22 | if imgui::MenuItem::new(&im_str!("{}", string)).build(ui) { 23 | editor_state.enqueue_set_active_editor_tool(editor_tool); 24 | } 25 | 26 | if let Some(color_stack_token) = color_stack_token { 27 | color_stack_token.pop(ui); 28 | } 29 | } 30 | 31 | pub fn editor_imgui_menu(schedule: &mut legion::systems::Builder) { 32 | schedule.add_system( 33 | SystemBuilder::new("editor_imgui_menu") 34 | .write_resource::() 35 | .write_resource::() 36 | .read_resource::() 37 | .build(|_command_buffer, _, (imgui, editor_state, time_state), _| { 38 | imgui.with_ui(|ui| { 39 | { 40 | let window_settings = editor_state.window_options_mut(); 41 | if window_settings.show_imgui_metrics { 42 | ui.show_metrics_window(&mut window_settings.show_imgui_metrics); 43 | } 44 | 45 | if window_settings.show_imgui_style_editor { 46 | imgui::Window::new(im_str!("Editor")).build(ui, || { 47 | ui.show_default_style_editor(); 48 | }); 49 | } 50 | 51 | if window_settings.show_imgui_demo { 52 | ui.show_demo_window(&mut window_settings.show_imgui_demo); 53 | } 54 | } 55 | 56 | ui.main_menu_bar(|| { 57 | //axis-arrow 58 | imgui_menu_tool_button( 59 | ui, 60 | &mut *editor_state, 61 | EditorTool::Translate, 62 | "\u{fd25}", 63 | ); 64 | //resize 65 | imgui_menu_tool_button( 66 | ui, 67 | &mut *editor_state, 68 | EditorTool::Scale, 69 | "\u{fa67}", 70 | ); 71 | //rotate-orbit 72 | imgui_menu_tool_button( 73 | ui, 74 | &mut *editor_state, 75 | EditorTool::Rotate, 76 | "\u{fd74}", 77 | ); 78 | 79 | ui.menu(imgui::im_str!("File"), true, || { 80 | if imgui::MenuItem::new(imgui::im_str!("Open")).build(ui) { 81 | if let Some(opened_prefab) = editor_state.opened_prefab() { 82 | // temporarily get the recently opened prefab uuid from editor state 83 | let uuid = opened_prefab.uuid(); 84 | editor_state.enqueue_open_prefab(*uuid); 85 | } 86 | } 87 | 88 | if imgui::MenuItem::new(im_str!("Save")).build(ui) { 89 | editor_state.enqueue_save_prefab(); 90 | } 91 | }); 92 | 93 | ui.menu(imgui::im_str!("Edit"), true, || { 94 | if imgui::MenuItem::new(im_str!("Undo")).build(ui) { 95 | editor_state.enqueue_undo(); 96 | } 97 | 98 | if imgui::MenuItem::new(im_str!("Redo")).build(ui) { 99 | editor_state.enqueue_redo(); 100 | } 101 | }); 102 | 103 | let window_settings = editor_state.window_options_mut(); 104 | ui.menu(im_str!("Windows"), true, || { 105 | ui.checkbox( 106 | im_str!("ImGui Metrics"), 107 | &mut window_settings.show_imgui_metrics, 108 | ); 109 | ui.checkbox( 110 | im_str!("ImGui Style Editor"), 111 | &mut window_settings.show_imgui_style_editor, 112 | ); 113 | ui.checkbox( 114 | im_str!("ImGui Demo"), 115 | &mut window_settings.show_imgui_demo, 116 | ); 117 | ui.checkbox( 118 | im_str!("Entity List"), 119 | &mut window_settings.show_entity_list, 120 | ); 121 | ui.checkbox(im_str!("Inspector"), &mut window_settings.show_inspector); 122 | }); 123 | 124 | ui.separator(); 125 | 126 | if editor_state.is_editor_active() { 127 | if imgui::MenuItem::new(im_str!("\u{e8c4} Reset")).build(ui) { 128 | editor_state.enqueue_reset(); 129 | } 130 | 131 | if imgui::MenuItem::new(im_str!("\u{f40a} Play")).build(ui) { 132 | editor_state.enqueue_play(); 133 | } 134 | } else { 135 | if imgui::MenuItem::new(im_str!("\u{e8c4} Reset")).build(ui) { 136 | editor_state.enqueue_reset(); 137 | } 138 | 139 | if imgui::MenuItem::new(im_str!("\u{f3e4} Pause")).build(ui) { 140 | editor_state.enqueue_pause(); 141 | } 142 | } 143 | 144 | ui.text(im_str!( 145 | "FPS: {:.1}", 146 | time_state.system_time().updates_per_second_smoothed() 147 | )); 148 | 149 | if time_state.is_simulation_paused() { 150 | ui.text(im_str!("SIMULATION PAUSED")); 151 | } 152 | }); 153 | }); 154 | }), 155 | ); 156 | } 157 | -------------------------------------------------------------------------------- /minimum-editor/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use minimum_game::resources::{InputResource, ViewportResource, DebugDraw3DResource, CameraResource}; 4 | use crate::resources::{ 5 | EditorStateResource, EditorSelectionResource, EditorDraw3DResource, EditorSettingsResource, 6 | }; 7 | use crate::resources::EditorTool; 8 | 9 | mod main_menu; 10 | pub use main_menu::editor_imgui_menu; 11 | 12 | mod entity_list_window; 13 | pub use entity_list_window::editor_entity_list_window; 14 | 15 | mod inspector_window; 16 | pub use inspector_window::editor_inspector_window; 17 | 18 | mod selection; 19 | pub use selection::draw_selection_shapes; 20 | pub use selection::editor_handle_selection; 21 | 22 | // mod gizmos_2d; 23 | // pub use gizmos_2d::editor_gizmos; 24 | 25 | mod gizmos_3d; 26 | pub use gizmos_3d::editor_gizmos; 27 | 28 | pub fn editor_refresh_selection_world( 29 | world: &mut World, 30 | resources: &mut Resources, 31 | ) { 32 | let mut selection_world = 33 | EditorSelectionResource::create_editor_selection_world(resources, world); 34 | selection_world.update(); 35 | resources 36 | .get_mut::() 37 | .unwrap() 38 | .set_editor_selection_world(selection_world); 39 | } 40 | 41 | pub fn editor_process_selection_ops( 42 | world: &mut World, 43 | resources: &mut Resources, 44 | ) { 45 | let mut editor_selection = resources.get_mut::().unwrap(); 46 | let mut editor_state = resources.get_mut::().unwrap(); 47 | 48 | editor_selection.process_selection_ops(&mut *editor_state, world); 49 | } 50 | 51 | pub fn reload_editor_state_if_file_changed( 52 | world: &mut World, 53 | resources: &mut Resources, 54 | ) { 55 | EditorStateResource::hot_reload_if_asset_changed(world, resources); 56 | } 57 | 58 | pub fn editor_process_edit_diffs( 59 | world: &mut World, 60 | resources: &mut Resources, 61 | ) { 62 | EditorStateResource::process_diffs(world, resources); 63 | } 64 | 65 | pub fn editor_process_editor_ops( 66 | world: &mut World, 67 | resources: &mut Resources, 68 | ) { 69 | EditorStateResource::process_editor_ops(world, resources); 70 | } 71 | 72 | pub fn editor_keybinds(schedule: &mut legion::systems::Builder) { 73 | schedule.add_system( 74 | SystemBuilder::new("editor_input") 75 | .write_resource::() 76 | .read_resource::() 77 | .read_resource::() 78 | .write_resource::() 79 | .write_resource::() 80 | .write_resource::() 81 | .read_resource::() 82 | .build( 83 | |_command_buffer, 84 | _subworld, 85 | ( 86 | editor_state, 87 | input_state, 88 | _viewport, 89 | _editor_selection, 90 | _debug_draw, 91 | _editor_draw, 92 | editor_settings, 93 | ), 94 | _| { 95 | if input_state.is_key_just_down(editor_settings.keybinds().tool_translate) { 96 | editor_state.enqueue_set_active_editor_tool(EditorTool::Translate); 97 | } 98 | 99 | if input_state.is_key_just_down(editor_settings.keybinds().tool_scale) { 100 | editor_state.enqueue_set_active_editor_tool(EditorTool::Scale); 101 | } 102 | 103 | if input_state.is_key_just_down(editor_settings.keybinds().tool_rotate) { 104 | editor_state.enqueue_set_active_editor_tool(EditorTool::Rotate); 105 | } 106 | 107 | if input_state 108 | .is_key_just_down(editor_settings.keybinds().action_toggle_editor_pause) 109 | { 110 | editor_state.enqueue_toggle_pause(); 111 | } 112 | }, 113 | ), 114 | ); 115 | } 116 | 117 | pub fn editor_mouse_input(schedule: &mut legion::systems::Builder) { 118 | schedule.add_system( 119 | SystemBuilder::new("editor_input") 120 | .read_resource::() 121 | .write_resource::() 122 | .read_resource::() 123 | .build( 124 | |_command_buffer, 125 | _subworld, 126 | (input_state, camera_resource, _viewport_resource), 127 | _| { 128 | // Right click drag pans the viewport 129 | // if let Some(mouse_drag) = input_state.mouse_drag_in_progress(MouseButton::RIGHT) { 130 | // //let mut delta = mouse_drag.world_scale_previous_frame_delta; 131 | // let mut delta = mouse_drag.ui_ 132 | // delta *= glam::Vec2::new(-1.0, -1.0); 133 | // camera_resource.position += delta; 134 | // } 135 | 136 | // Right click drag pans the viewport 137 | let mouse_scroll = input_state.mouse_wheel_delta(); 138 | let delta = mouse_scroll.y as f32; 139 | 140 | let delta = 1.05_f32.powf(-delta); 141 | camera_resource.x_half_extents *= delta; 142 | }, 143 | ), 144 | ); 145 | } 146 | 147 | pub fn editor_update_editor_draw(schedule: &mut legion::systems::Builder) { 148 | schedule.add_system( 149 | SystemBuilder::new("editor_input") 150 | .write_resource::() 151 | .read_resource::() 152 | .read_resource::() 153 | .write_resource::() 154 | .write_resource::() 155 | .build( 156 | |_command_buffer, 157 | _subworld, 158 | (_editor_state, input_state, viewport, _editor_selection, editor_draw), 159 | _| { 160 | editor_draw.update(input_state.input_state(), &*viewport); 161 | }, 162 | ), 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /minimum-game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-game" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum-kernel = { path = "../minimum-kernel" } 11 | minimum-math = { path = "../minimum-math" } 12 | 13 | imgui = "0.5" 14 | 15 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 16 | 17 | glam = { version = "0.8.5", features = ["serde"] } 18 | 19 | log="0.4" -------------------------------------------------------------------------------- /minimum-game/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | pub mod resources; 6 | pub mod systems; 7 | 8 | pub mod input; 9 | 10 | pub mod imgui; 11 | pub use crate::imgui::ImguiManager; 12 | -------------------------------------------------------------------------------- /minimum-game/src/resources/app_control.rs: -------------------------------------------------------------------------------- 1 | pub struct AppControlResource { 2 | /// If true, the application will quit when the next frame ends 3 | should_terminate_process: bool, 4 | } 5 | 6 | impl AppControlResource { 7 | pub fn new() -> Self { 8 | Default::default() 9 | } 10 | 11 | /// Direct the application to terminate at the end of the next frame 12 | pub fn enqueue_terminate_process(&mut self) { 13 | self.should_terminate_process = true; 14 | } 15 | 16 | /// Returns true iff `enqueue_terminate_process` is called, indicating that the app should terminate 17 | pub fn should_terminate_process(&self) -> bool { 18 | self.should_terminate_process 19 | } 20 | } 21 | 22 | impl Default for AppControlResource { 23 | fn default() -> Self { 24 | AppControlResource { 25 | should_terminate_process: false, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /minimum-game/src/resources/camera.rs: -------------------------------------------------------------------------------- 1 | pub struct CameraResource { 2 | pub position: glam::Vec2, 3 | pub x_half_extents: f32, 4 | } 5 | 6 | impl CameraResource { 7 | pub fn new( 8 | position: glam::Vec2, 9 | x_half_extents: f32, 10 | ) -> Self { 11 | CameraResource { 12 | position, 13 | x_half_extents, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /minimum-game/src/resources/debug_draw_2d.rs: -------------------------------------------------------------------------------- 1 | pub struct LineList2D { 2 | pub points: Vec, 3 | pub color: glam::Vec4, 4 | } 5 | 6 | impl LineList2D { 7 | pub fn new( 8 | points: Vec, 9 | color: glam::Vec4, 10 | ) -> Self { 11 | LineList2D { points, color } 12 | } 13 | } 14 | 15 | pub struct DebugDraw2DResource { 16 | line_lists: Vec, 17 | } 18 | 19 | impl DebugDraw2DResource { 20 | pub fn new() -> Self { 21 | DebugDraw2DResource { line_lists: vec![] } 22 | } 23 | 24 | // Adds a single polygon 25 | pub fn add_line_strip( 26 | &mut self, 27 | points: Vec, 28 | color: glam::Vec4, 29 | ) { 30 | // Nothing will draw if we don't have at least 2 points 31 | if points.len() > 1 { 32 | self.line_lists.push(LineList2D::new(points, color)); 33 | } 34 | } 35 | 36 | pub fn add_line_loop( 37 | &mut self, 38 | mut points: Vec, 39 | color: glam::Vec4, 40 | ) { 41 | // Nothing will draw if we don't have at least 2 points 42 | if points.len() > 1 { 43 | points.push(points[0]); 44 | self.add_line_strip(points, color); 45 | } 46 | } 47 | 48 | pub fn add_line( 49 | &mut self, 50 | p0: glam::Vec2, 51 | p1: glam::Vec2, 52 | color: glam::Vec4, 53 | ) { 54 | let points = vec![p0, p1]; 55 | 56 | self.add_line_strip(points, color); 57 | } 58 | 59 | pub fn add_tristrip( 60 | &mut self, 61 | points: &[glam::Vec2], 62 | color: glam::Vec4, 63 | ) { 64 | // Nothing will draw if we don't have at least 2 points 65 | for index in 0..(points.len() - 2) { 66 | let v = vec![points[index], points[index + 1], points[index + 2]]; 67 | self.add_line_loop(v, color); 68 | } 69 | } 70 | 71 | pub fn add_circle( 72 | &mut self, 73 | center: glam::Vec2, 74 | radius: f32, 75 | color: glam::Vec4, 76 | ) { 77 | let point_count = 12; 78 | 79 | let mut points = Vec::with_capacity(point_count); 80 | for index in 0..point_count { 81 | let fraction = (index as f32 / point_count as f32) * std::f32::consts::PI * 2.0; 82 | 83 | points.push(glam::Vec2::new(fraction.sin() * radius, fraction.cos() * radius) + center); 84 | } 85 | 86 | self.add_line_loop(points, color); 87 | } 88 | 89 | pub fn add_rect( 90 | &mut self, 91 | p0: glam::Vec2, 92 | p1: glam::Vec2, 93 | color: glam::Vec4, 94 | ) { 95 | let points = vec![ 96 | p0, 97 | glam::vec2(p0.x(), p1.y()), 98 | p1, 99 | glam::vec2(p1.x(), p0.y()), 100 | p0, 101 | ]; 102 | 103 | self.add_line_loop(points, color); 104 | } 105 | 106 | // Returns the draw data, leaving this object in an empty state 107 | pub fn take_line_lists(&mut self) -> Vec { 108 | std::mem::replace(&mut self.line_lists, vec![]) 109 | } 110 | 111 | // Recommended to call every frame to ensure that this doesn't grow unbounded 112 | pub fn clear(&mut self) { 113 | self.line_lists.clear(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /minimum-game/src/resources/imgui.rs: -------------------------------------------------------------------------------- 1 | use crate::ImguiManager; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | pub struct ImguiResource { 5 | manager: ImguiManager, 6 | } 7 | 8 | impl ImguiResource { 9 | pub fn new(manager: ImguiManager) -> Self { 10 | ImguiResource { manager } 11 | } 12 | } 13 | 14 | impl Deref for ImguiResource { 15 | type Target = ImguiManager; 16 | 17 | fn deref(&self) -> &ImguiManager { 18 | &self.manager 19 | } 20 | } 21 | 22 | impl DerefMut for ImguiResource { 23 | fn deref_mut(&mut self) -> &mut ImguiManager { 24 | &mut self.manager 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /minimum-game/src/resources/input.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::input::InputState; 4 | 5 | // For now just wrap the input helper that skulpin provides 6 | pub struct InputResource { 7 | input_state: InputState, 8 | } 9 | 10 | impl InputResource { 11 | pub fn new() -> Self { 12 | InputResource { 13 | input_state: InputState::new(), 14 | } 15 | } 16 | 17 | pub fn input_state(&self) -> &InputState { 18 | &self.input_state 19 | } 20 | 21 | pub fn input_state_mut(&mut self) -> &mut InputState { 22 | &mut self.input_state 23 | } 24 | } 25 | 26 | impl Deref for InputResource { 27 | type Target = InputState; 28 | 29 | fn deref(&self) -> &Self::Target { 30 | self.input_state() 31 | } 32 | } 33 | 34 | impl DerefMut for InputResource { 35 | fn deref_mut(&mut self) -> &mut Self::Target { 36 | self.input_state_mut() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /minimum-game/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod time; 2 | pub use time::TimeResource; 3 | pub use time::SimulationTimePauseReason; 4 | pub use time::TimeState; 5 | 6 | mod app_control; 7 | pub use app_control::AppControlResource; 8 | 9 | mod debug_draw_2d; 10 | pub use debug_draw_2d::DebugDraw2DResource; 11 | pub use debug_draw_2d::LineList2D; 12 | 13 | mod debug_draw_3d; 14 | pub use debug_draw_3d::DebugDraw3DResource; 15 | pub use debug_draw_3d::LineList3D; 16 | pub use debug_draw_3d::DebugDraw3DDepthBehavior; 17 | 18 | mod viewport; 19 | pub use viewport::ViewportResource; 20 | 21 | mod input; 22 | pub use input::InputResource; 23 | 24 | mod camera; 25 | pub use camera::CameraResource; 26 | 27 | mod imgui; 28 | pub use crate::resources::imgui::ImguiResource; 29 | -------------------------------------------------------------------------------- /minimum-game/src/systems/input_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use crate::resources::InputResource; 4 | 5 | // Call this to process input state 6 | pub fn update_input_resource(schedule: &mut legion::systems::Builder) { 7 | schedule.add_system( 8 | SystemBuilder::new("input end frame") 9 | .write_resource::() 10 | .build(|_, _, _input, _| { 11 | //input.end_frame(); 12 | }), 13 | ); 14 | } 15 | 16 | // Call this to mark the start of the next frame (i.e. "key just down" will return false). This goes 17 | // at the end of the frame, winit will fire events after we exit the frame, and then 18 | // update_input_resource will be called at the start of the next frame 19 | pub fn input_reset_for_next_frame(schedule: &mut legion::systems::Builder) { 20 | schedule.add_system( 21 | SystemBuilder::new("input end frame") 22 | .write_resource::() 23 | .build(|_, _, input, _| { 24 | input.end_frame(); 25 | }), 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /minimum-game/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod input_systems; 2 | pub use input_systems::update_input_resource; 3 | pub use input_systems::input_reset_for_next_frame; 4 | 5 | mod time_systems; 6 | pub use time_systems::advance_time; 7 | -------------------------------------------------------------------------------- /minimum-game/src/systems/time_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | use crate::resources::TimeResource; 4 | 5 | pub fn advance_time(schedule: &mut legion::systems::Builder) { 6 | schedule.add_system( 7 | SystemBuilder::new("advance_time") 8 | .write_resource::() 9 | .build(|_, _, time_resource, _| { 10 | time_resource.process_time_ops(); 11 | time_resource.advance_time(); 12 | 13 | let now = time_resource.time_state.current_instant(); 14 | if time_resource 15 | .log_fps_event 16 | .try_take_event(now, std::time::Duration::from_secs(1)) 17 | { 18 | log::debug!("fps: {}", time_resource.time_state.updates_per_second()); 19 | } 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /minimum-kernel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-kernel" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | futures-executor = "0.3.5" 12 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3", features = ["serde_importers"] } 13 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 14 | 15 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 16 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 17 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 18 | 19 | structopt = "0.3" 20 | serde = "1" 21 | uuid = "0.8" 22 | type-uuid = "0.1" 23 | image2 = { version = "0.11", features = [ "ser" ] } 24 | inventory = "0.1" 25 | 26 | # We need this PR (https://github.com/servo/bincode/pull/288) but it's not published yet 27 | bincode = "1.3.1" 28 | mopa = "0.2" 29 | itertools = "0.8" 30 | 31 | serde-diff = "0.3" 32 | 33 | ron = "0.5" 34 | erased-serde = "0.3" 35 | fnv = "1.0" 36 | 37 | crossbeam-channel = "0.4" 38 | 39 | log="0.4" -------------------------------------------------------------------------------- /minimum-kernel/src/component_registry.rs: -------------------------------------------------------------------------------- 1 | use legion_prefab::ComponentRegistration; 2 | use prefab_format::ComponentTypeUuid; 3 | use legion::storage::{ComponentTypeId, Archetype, Components, ComponentWriter}; 4 | use std::collections::HashMap; 5 | 6 | use legion_prefab::{CopyCloneImpl, SpawnCloneImpl, SpawnCloneImplHandlerSet, SpawnInto}; 7 | use legion::storage::{Component}; 8 | use legion::*; 9 | use std::ops::Range; 10 | 11 | use fnv::{FnvHashMap, FnvBuildHasher}; 12 | use legion::world::EntityHasher; 13 | 14 | pub struct ComponentRegistryBuilder { 15 | components: FnvHashMap, 16 | components_by_uuid: FnvHashMap, 17 | spawn_handler_set: SpawnCloneImplHandlerSet, 18 | } 19 | 20 | impl ComponentRegistryBuilder { 21 | pub fn new() -> Self { 22 | ComponentRegistryBuilder { 23 | components: Default::default(), 24 | components_by_uuid: Default::default(), 25 | spawn_handler_set: SpawnCloneImplHandlerSet::new(), 26 | } 27 | } 28 | 29 | pub fn auto_register_components(mut self) -> Self { 30 | let comp_registrations = legion_prefab::iter_component_registrations(); 31 | 32 | for registration in comp_registrations { 33 | self = self.register_component(registration); 34 | } 35 | 36 | self 37 | } 38 | 39 | pub fn register_component( 40 | mut self, 41 | registration: &ComponentRegistration, 42 | ) -> Self { 43 | self.components 44 | .insert(registration.component_type_id(), registration.clone()); 45 | self.components_by_uuid 46 | .insert(*registration.uuid(), registration.clone()); 47 | self 48 | } 49 | 50 | pub fn add_spawn_mapping_into, IntoT: Component>( 51 | mut self 52 | ) -> Self { 53 | self.spawn_handler_set.add_mapping_into::(); 54 | self 55 | } 56 | 57 | pub fn add_spawn_mapping, IntoT: Component>( 58 | mut self 59 | ) -> Self { 60 | self.spawn_handler_set.add_mapping::(); 61 | self 62 | } 63 | 64 | pub fn add_spawn_mapping_closure( 65 | &mut self, 66 | clone_fn: F, 67 | ) where 68 | FromT: Component, 69 | IntoT: Component, 70 | F: Fn( 71 | &Resources, // resources 72 | Range, // src_entity_range 73 | &Archetype, // src_arch 74 | &Components, // src_components 75 | &mut ComponentWriter, // dst 76 | fn(&mut ComponentWriter, IntoT), // push_fn 77 | ) + Send 78 | + Sync 79 | + 'static, 80 | { 81 | self.spawn_handler_set 82 | .add_mapping_closure::(clone_fn); 83 | } 84 | 85 | pub fn build(self) -> ComponentRegistry { 86 | ComponentRegistry { 87 | components: self.components, 88 | components_by_uuid: self.components_by_uuid, 89 | spawn_handler_set: self.spawn_handler_set, 90 | } 91 | } 92 | } 93 | 94 | pub struct ComponentRegistry { 95 | components: FnvHashMap, 96 | components_by_uuid: FnvHashMap, 97 | spawn_handler_set: SpawnCloneImplHandlerSet, 98 | } 99 | 100 | impl ComponentRegistry { 101 | pub fn components(&self) -> &FnvHashMap { 102 | &self.components 103 | } 104 | 105 | pub fn components_by_uuid(&self) -> &FnvHashMap { 106 | &self.components_by_uuid 107 | } 108 | 109 | pub fn copy_clone_impl(&self) -> CopyCloneImpl { 110 | CopyCloneImpl::new(&self.components) 111 | } 112 | 113 | pub fn spawn_clone_impl<'a, 'b, 'c>( 114 | &'a self, 115 | resources: &'b Resources, 116 | entity_map: &'c HashMap, 117 | ) -> SpawnCloneImpl<'a, 'a, 'b, 'c, fnv::FnvBuildHasher> { 118 | SpawnCloneImpl::new( 119 | &self.spawn_handler_set, 120 | &self.components, 121 | resources, 122 | entity_map, 123 | ) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /minimum-kernel/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod asset_storage; 5 | pub use asset_storage::AssetStorageSet; 6 | pub use asset_storage::DynAssetLoader; 7 | pub use asset_storage::UpdateAssetResult; 8 | 9 | mod component_registry; 10 | pub use component_registry::ComponentRegistryBuilder; 11 | pub use component_registry::ComponentRegistry; 12 | 13 | pub mod util; 14 | 15 | pub mod prefab_cooking; 16 | 17 | pub mod resources; 18 | pub mod pipeline; 19 | pub mod systems; 20 | -------------------------------------------------------------------------------- /minimum-kernel/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | mod prefab; 2 | pub use prefab::PrefabAsset; 3 | pub use prefab::PrefabImporter; 4 | -------------------------------------------------------------------------------- /minimum-kernel/src/pipeline/prefab/assets.rs: -------------------------------------------------------------------------------- 1 | use atelier_assets::importer::{typetag, SerdeImportable}; 2 | use serde::{Deserialize, Serialize}; 3 | use type_uuid::TypeUuid; 4 | 5 | use atelier_assets::importer as atelier_importer; 6 | 7 | #[derive(TypeUuid, Serialize, Deserialize, SerdeImportable)] 8 | #[uuid = "5e751ea4-e63b-4192-a008-f5bf8674e45b"] 9 | pub struct PrefabAsset { 10 | pub prefab: legion_prefab::Prefab, 11 | } 12 | -------------------------------------------------------------------------------- /minimum-kernel/src/pipeline/prefab/importers.rs: -------------------------------------------------------------------------------- 1 | use atelier_assets::importer::{ImportedAsset, Importer, ImporterValue}; 2 | use atelier_assets::core::AssetUuid; 3 | use serde::{Deserialize, Serialize}; 4 | use std::io::Read; 5 | use type_uuid::TypeUuid; 6 | 7 | use crate::pipeline::PrefabAsset; 8 | 9 | use legion_prefab::{ComponentRegistration}; 10 | use std::collections::HashMap; 11 | use prefab_format::ComponentTypeUuid; 12 | 13 | #[derive(Default, Deserialize, Serialize, TypeUuid, Clone, Copy)] 14 | #[uuid = "80583980-24d4-4034-8394-ea749b43f55d"] 15 | pub struct PrefabImporterOptions {} 16 | 17 | /// A simple state for Importer to retain the same UUID between imports 18 | /// for all single-asset source files 19 | #[derive(Default, Deserialize, Serialize, TypeUuid)] 20 | #[uuid = "14d89614-7e10-4f59-952f-af32c73bda90"] 21 | pub struct PrefabImporterState { 22 | pub id: Option, 23 | } 24 | 25 | #[derive(Default, TypeUuid)] 26 | #[uuid = "5bdf4d06-a1cb-437b-b182-d6d8cb23512c"] 27 | pub struct PrefabImporter {} 28 | 29 | use atelier_assets::importer as atelier_importer; 30 | 31 | impl Importer for PrefabImporter { 32 | type State = PrefabImporterState; 33 | type Options = PrefabImporterOptions; 34 | 35 | fn version_static() -> u32 { 36 | 1 37 | } 38 | 39 | fn version(&self) -> u32 { 40 | Self::version_static() 41 | } 42 | 43 | fn import( 44 | &self, 45 | source: &mut dyn Read, 46 | _: &Self::Options, 47 | state: &mut Self::State, 48 | ) -> atelier_importer::Result { 49 | /////////////////////////////////////////////////////////////// 50 | // STEP 1: Read in the data 51 | /////////////////////////////////////////////////////////////// 52 | 53 | // Read in the data 54 | let mut bytes = Vec::new(); 55 | source.read_to_end(&mut bytes)?; 56 | 57 | /////////////////////////////////////////////////////////////// 58 | // STEP 2: Deserialize the prefab into a legion world 59 | /////////////////////////////////////////////////////////////// 60 | 61 | // Create a deserializer 62 | let mut de = ron::de::Deserializer::from_bytes(bytes.as_slice()).unwrap(); 63 | 64 | // Create the component registry 65 | let registered_components = { 66 | let comp_registrations = legion_prefab::iter_component_registrations(); 67 | use std::iter::FromIterator; 68 | let component_types: HashMap = 69 | HashMap::from_iter(comp_registrations.map(|reg| (*reg.uuid(), reg.clone()))); 70 | 71 | component_types 72 | }; 73 | 74 | let prefab_serde_context = legion_prefab::PrefabSerdeContext { 75 | registered_components: ®istered_components, 76 | }; 77 | 78 | let prefab_deser = legion_prefab::PrefabFormatDeserializer::new(prefab_serde_context); 79 | prefab_format::deserialize(&mut de, &prefab_deser)?; 80 | let prefab = prefab_deser.prefab(); 81 | 82 | let prefab_asset = PrefabAsset { prefab }; 83 | 84 | /////////////////////////////////////////////////////////////// 85 | // STEP 3: Now we need to save it into an asset 86 | /////////////////////////////////////////////////////////////// 87 | 88 | { 89 | // Print for debug 90 | let legion_world_str = 91 | ron::ser::to_string_pretty(&prefab_asset, ron::ser::PrettyConfig::default()) 92 | .unwrap(); 93 | 94 | log::trace!("Serialized legion world:"); 95 | log::trace!("legion_world_str {}", legion_world_str); 96 | 97 | let mut ron_ser = 98 | ron::ser::Serializer::new(Some(ron::ser::PrettyConfig::default()), true); 99 | let prefab_ser = legion_prefab::PrefabFormatSerializer::new( 100 | prefab_serde_context, 101 | &prefab_asset.prefab, 102 | ); 103 | prefab_format::serialize(&mut ron_ser, &prefab_ser, prefab_asset.prefab.prefab_id()) 104 | .expect("failed to round-trip prefab"); 105 | log::trace!( 106 | "Round-tripped legion world: {}", 107 | ron_ser.into_output_string() 108 | ); 109 | } 110 | 111 | // Add the ID to the .meta 112 | let prefab_id = prefab_asset.prefab.prefab_id(); 113 | state.id = Some(AssetUuid(prefab_id)); 114 | 115 | // { 116 | // //let mut ron_serializer = ron::ser::Serializer::new(Some(ron::ser::PrettyConfig::default()), true); 117 | // let ron_string = ron::ser::to_string_pretty(&prefab_asset, Default::default()).unwrap(); 118 | // println!("{}", ron_string); 119 | // let deser = ron::de::from_str::(&ron_string).unwrap(); 120 | // println!("ron deser complete"); 121 | // println!("read {} entities", deser.prefab.world.len()); 122 | // } 123 | // 124 | // { 125 | // println!("start bincode ser"); 126 | // let s = bincode::serialize(&prefab_asset).unwrap(); 127 | // let d = bincode::deserialize::(&s).unwrap(); 128 | // println!("read {} entities", d.prefab.world.len()); 129 | // } 130 | 131 | Ok(ImporterValue { 132 | assets: vec![ImportedAsset { 133 | id: state.id.expect("AssetUuid not generated"), 134 | search_tags: Vec::new(), 135 | build_deps: Vec::new(), 136 | load_deps: Vec::new(), 137 | asset_data: Box::new(prefab_asset), 138 | build_pipeline: None, 139 | }], 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /minimum-kernel/src/pipeline/prefab/mod.rs: -------------------------------------------------------------------------------- 1 | mod importers; 2 | pub use importers::PrefabImporter; 3 | 4 | mod assets; 5 | pub use assets::PrefabAsset; 6 | -------------------------------------------------------------------------------- /minimum-kernel/src/prefab_cooking.rs: -------------------------------------------------------------------------------- 1 | use crate::resources::AssetResource; 2 | 3 | use atelier_assets::loader::{ 4 | handle::{AssetHandle, Handle}, 5 | rpc_loader::RpcLoader, 6 | LoadStatus, Loader, 7 | }; 8 | use std::collections::HashMap; 9 | 10 | use legion::storage::ComponentTypeId; 11 | use prefab_format::{ComponentTypeUuid, PrefabUuid}; 12 | use legion_prefab::{ComponentRegistration, CookedPrefab}; 13 | use crate::pipeline::PrefabAsset; 14 | use atelier_assets::core::AssetUuid; 15 | use std::hash::BuildHasher; 16 | 17 | pub fn cook_prefab( 18 | asset_manager: &mut AssetResource, 19 | registered_components: &HashMap, 20 | registered_components_by_uuid: &HashMap, 21 | prefab_uuid: AssetUuid, 22 | update_fn: &F, 23 | ) -> CookedPrefab { 24 | // This will allow us to look up prefab handles by AssetUuid 25 | let mut prefab_handle_lookup = HashMap::new(); 26 | 27 | // This will hold the asset IDs sorted with dependencies first. This ensures that 28 | // prefab_lookup and entity_lookup are populated with all dependent prefabs/entities 29 | let mut prefab_cook_order = vec![]; 30 | 31 | // Recursively do a blocking load on the prefab and the other prefabs it depends on. This 32 | // populates prefab_handle_lookup and prefab_cook_order 33 | request_prefab_dependencies( 34 | asset_manager, 35 | prefab_uuid, 36 | &mut prefab_handle_lookup, 37 | &mut prefab_cook_order, 38 | update_fn, 39 | ); 40 | 41 | // This will allowus to look up prefab references by AssetUuid 42 | let mut prefab_lookup = HashMap::new(); 43 | 44 | for prefab_handle in prefab_handle_lookup.values() { 45 | let prefab_asset: &PrefabAsset = prefab_handle.asset(asset_manager.storage()).unwrap(); 46 | prefab_lookup.insert(prefab_asset.prefab.prefab_meta.id, &prefab_asset.prefab); 47 | } 48 | 49 | legion_prefab::cook_prefab( 50 | registered_components, 51 | registered_components_by_uuid, 52 | prefab_cook_order.as_slice(), 53 | &prefab_lookup, 54 | ) 55 | } 56 | 57 | // This function does a recursive blocking load on the provided prefab asset and all prefabs 58 | // that it references. As it does this, prefab_lookup and prefab_cook_order are populated 59 | fn request_prefab_dependencies( 60 | asset_manager: &mut AssetResource, 61 | id: AssetUuid, 62 | prefab_lookup: &mut HashMap>, 63 | prefab_cook_order: &mut Vec, 64 | update_fn: &F, 65 | ) { 66 | // Request the asset 67 | let load_handle = asset_manager.loader().add_ref(id); 68 | let handle = Handle::::new(asset_manager.tx().clone(), load_handle); 69 | 70 | // Block until it loads 71 | loop { 72 | (update_fn)(asset_manager); 73 | if let LoadStatus::Loaded = handle.load_status::(asset_manager.loader()) { 74 | break; 75 | } 76 | } 77 | 78 | // Grab a reference to the asset 79 | let prefab_asset: &PrefabAsset = handle.asset(asset_manager.storage()).unwrap(); 80 | 81 | // Get a list of prefabs this asset references. We clone these into a new list due to borrowing restrictions 82 | let other_prefab_ids: Vec<_> = prefab_asset 83 | .prefab 84 | .prefab_meta 85 | .prefab_refs 86 | .iter() 87 | .map(|(other_prefab_id, _)| AssetUuid(*other_prefab_id)) 88 | .collect(); 89 | 90 | // Use recursion to visit the tree ensuring that ancestor prefab data gets processed first 91 | for other_prefab_id in other_prefab_ids { 92 | if !prefab_lookup.contains_key(&other_prefab_id.0) { 93 | request_prefab_dependencies( 94 | asset_manager, 95 | other_prefab_id, 96 | prefab_lookup, 97 | prefab_cook_order, 98 | update_fn, 99 | ); 100 | } 101 | } 102 | 103 | // Write data.. this needs to happen after we visit prefabs that we reference 104 | prefab_lookup.insert(id.0, handle); 105 | prefab_cook_order.push(id.0); 106 | } 107 | -------------------------------------------------------------------------------- /minimum-kernel/src/resources/asset.rs: -------------------------------------------------------------------------------- 1 | use atelier_assets::loader::{handle::RefOp, rpc_loader::RpcLoader, Loader}; 2 | 3 | use crate::AssetStorageSet; 4 | use crate::DynAssetLoader; 5 | 6 | use type_uuid::TypeUuid; 7 | 8 | use atelier_assets::loader as atelier_loader; 9 | use legion::Resources; 10 | use crossbeam_channel::{Receiver, Sender}; 11 | 12 | pub trait AssetResourceUpdateCallback: Send + Sync { 13 | fn update( 14 | &self, 15 | resources: &Resources, 16 | asset_resource: &mut AssetResource, 17 | ); 18 | } 19 | 20 | pub struct DefaultAssetResourceUpdateCallback; 21 | 22 | impl AssetResourceUpdateCallback for DefaultAssetResourceUpdateCallback { 23 | fn update( 24 | &self, 25 | _resources: &Resources, 26 | asset_resource: &mut AssetResource, 27 | ) { 28 | asset_resource.do_update(); 29 | } 30 | } 31 | 32 | pub struct AssetResource { 33 | loader: RpcLoader, 34 | storage: AssetStorageSet, 35 | tx: Sender, 36 | rx: Receiver, 37 | update_callback: Option>, 38 | } 39 | 40 | impl AssetResource { 41 | pub fn new(loader: RpcLoader) -> Self { 42 | let (tx, rx) = atelier_loader::crossbeam_channel::unbounded(); 43 | let storage = AssetStorageSet::new(tx.clone()); 44 | 45 | AssetResource { 46 | loader, 47 | storage, 48 | tx, 49 | rx, 50 | update_callback: Some(Box::new(DefaultAssetResourceUpdateCallback)), 51 | } 52 | } 53 | } 54 | 55 | impl AssetResource { 56 | pub fn add_storage serde::Deserialize<'a> + 'static + Send>(&mut self) { 57 | self.storage.add_storage::(); 58 | } 59 | 60 | pub fn add_storage_with_loader( 61 | &mut self, 62 | loader: Box, 63 | ) where 64 | AssetDataT: TypeUuid + for<'a> serde::Deserialize<'a> + 'static, 65 | AssetT: TypeUuid + 'static + Send, 66 | LoaderT: DynAssetLoader + 'static, 67 | { 68 | self.storage 69 | .add_storage_with_loader::(loader); 70 | } 71 | 72 | pub fn update( 73 | &mut self, 74 | resources: &Resources, 75 | ) { 76 | // This take allows us to pass mutable self to the update callback 77 | let cb = self.update_callback.take().unwrap(); 78 | cb.update(resources, self); 79 | self.update_callback = Some(cb); 80 | } 81 | 82 | pub fn do_update(&mut self) { 83 | atelier_loader::handle::process_ref_ops(&self.loader, &self.rx); 84 | self.loader 85 | .process(&self.storage) 86 | .expect("failed to process loader"); 87 | } 88 | 89 | pub fn set_update_fn( 90 | &mut self, 91 | update_callback: Box, 92 | ) { 93 | self.update_callback = Some(update_callback); 94 | } 95 | 96 | pub fn loader(&self) -> &RpcLoader { 97 | &self.loader 98 | } 99 | 100 | pub fn storage(&self) -> &AssetStorageSet { 101 | &self.storage 102 | } 103 | 104 | pub fn tx(&self) -> &atelier_loader::crossbeam_channel::Sender { 105 | &self.tx 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /minimum-kernel/src/resources/component_registry.rs: -------------------------------------------------------------------------------- 1 | use crate::ComponentRegistry; 2 | use std::ops::Deref; 3 | use std::sync::Arc; 4 | 5 | pub struct ComponentRegistryResource { 6 | component_registry: Arc, 7 | } 8 | 9 | impl ComponentRegistryResource { 10 | pub fn new(component_registry: ComponentRegistry) -> Self { 11 | ComponentRegistryResource { 12 | component_registry: Arc::new(component_registry), 13 | } 14 | } 15 | } 16 | 17 | impl Deref for ComponentRegistryResource { 18 | type Target = ComponentRegistry; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.component_registry 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /minimum-kernel/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod asset; 2 | pub use asset::AssetResource; 3 | pub use asset::AssetResourceUpdateCallback; 4 | pub use asset::DefaultAssetResourceUpdateCallback; 5 | 6 | mod component_registry; 7 | pub use component_registry::ComponentRegistryResource; 8 | -------------------------------------------------------------------------------- /minimum-kernel/src/systems/asset_manager_systems.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use crate::resources::AssetResource; 3 | 4 | pub fn update_asset_manager( 5 | _world: &mut World, 6 | resources: &mut Resources, 7 | ) { 8 | resources 9 | .get_mut::() 10 | .unwrap() 11 | .update(resources); 12 | } 13 | -------------------------------------------------------------------------------- /minimum-kernel/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod asset_manager_systems; 2 | pub use asset_manager_systems::update_asset_manager; 3 | -------------------------------------------------------------------------------- /minimum-kernel/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Handy utilities 2 | 3 | /// Records time when created and logs amount of time passed when dropped 4 | pub struct ScopeTimer<'a> { 5 | start_time: std::time::Instant, 6 | name: &'a str, 7 | } 8 | 9 | impl<'a> ScopeTimer<'a> { 10 | /// Records the current time. When dropped, the amount of time passed will be logged. 11 | #[allow(unused_must_use)] 12 | pub fn new(name: &'a str) -> Self { 13 | ScopeTimer { 14 | start_time: std::time::Instant::now(), 15 | name, 16 | } 17 | } 18 | } 19 | 20 | impl<'a> Drop for ScopeTimer<'a> { 21 | fn drop(&mut self) { 22 | let end_time = std::time::Instant::now(); 23 | trace!( 24 | "ScopeTimer {}: {}", 25 | self.name, 26 | (end_time - self.start_time).as_micros() as f64 / 1000.0 27 | ) 28 | } 29 | } 30 | 31 | /// Useful for cases where you want to do something once per time interval. 32 | #[derive(Default)] 33 | pub struct PeriodicEvent { 34 | last_time_triggered: Option, 35 | } 36 | 37 | impl PeriodicEvent { 38 | /// Call try_take_event to see if the required time has elapsed. It will return true only once 39 | /// enough time has passed since it last returned true. 40 | pub fn try_take_event( 41 | &mut self, 42 | current_time: std::time::Instant, 43 | wait_duration: std::time::Duration, 44 | ) -> bool { 45 | match self.last_time_triggered { 46 | None => { 47 | self.last_time_triggered = Some(current_time); 48 | true 49 | } 50 | Some(last_time_triggered) => { 51 | if current_time - last_time_triggered >= wait_duration { 52 | self.last_time_triggered = Some(current_time); 53 | true 54 | } else { 55 | false 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /minimum-math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-math" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | serde = "1" 11 | 12 | glam = { version = "0.8.5", features = ["serde"] } 13 | 14 | imgui = "0.5" 15 | 16 | imgui-inspect-derive = "0.6" 17 | imgui-inspect = "0.6" 18 | 19 | log="0.4" 20 | assert_approx_eq = "1.1" 21 | 22 | nalgebra = { version = "0.18", features = [ "serde-serialize" ], optional = true } 23 | nalgebra-glm = { version = "0.4", optional = true } 24 | 25 | [features] 26 | default = [] 27 | na_conversion = ["nalgebra", "nalgebra-glm"] -------------------------------------------------------------------------------- /minimum-math/src/bounds.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(Serialize, Deserialize, Clone, Debug)] 4 | pub struct BoundingSphere { 5 | pub center: glam::Vec3, 6 | pub radius: f32, 7 | } 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug)] 10 | pub struct BoundingAabb { 11 | pub min: glam::Vec3, 12 | pub max: glam::Vec3, 13 | } 14 | 15 | impl BoundingAabb { 16 | pub fn new(initial_point: glam::Vec3) -> Self { 17 | BoundingAabb { 18 | min: initial_point, 19 | max: initial_point, 20 | } 21 | } 22 | 23 | pub fn expand( 24 | &mut self, 25 | p: glam::Vec3, 26 | ) { 27 | self.max = self.max.max(p); 28 | self.min = self.min.min(p); 29 | } 30 | 31 | pub fn calculate_bounding_sphere(&self) -> BoundingSphere { 32 | let center = (self.min + self.max) / 2.0; 33 | let radius = (self.min - self.max).length() / 2.0; 34 | 35 | BoundingSphere { center, radius } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /minimum-math/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | pub mod math; 6 | 7 | pub mod matrix; 8 | 9 | pub use math::Vec2; 10 | pub use math::Vec3; 11 | pub use math::Vec4; 12 | pub use math::Quat; 13 | 14 | pub use matrix::Mat4; 15 | 16 | pub mod functions; 17 | pub use functions::Segment; 18 | pub use functions::NormalizedRay; 19 | 20 | pub mod bounds; 21 | pub use bounds::BoundingSphere; 22 | pub use bounds::BoundingAabb; 23 | 24 | #[cfg(feature = "na_conversion")] 25 | pub mod na_convert; 26 | -------------------------------------------------------------------------------- /minimum-math/src/matrix.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use serde::{Deserialize, Serialize}; 3 | use imgui_inspect::InspectArgsDefault; 4 | use imgui_inspect::InspectRenderDefault; 5 | 6 | #[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Default)] 7 | #[repr(transparent)] 8 | #[serde(transparent)] 9 | pub struct Mat4 { 10 | value: glam::Mat4, 11 | } 12 | 13 | impl Mat4 { 14 | pub fn zero() -> Self { 15 | Mat4 { 16 | value: glam::Mat4::zero(), 17 | } 18 | } 19 | } 20 | 21 | impl From for Mat4 { 22 | fn from(value: glam::Mat4) -> Self { 23 | Mat4 { value } 24 | } 25 | } 26 | 27 | impl Into for Mat4 { 28 | fn into(self) -> glam::Mat4 { 29 | *self 30 | } 31 | } 32 | 33 | impl Deref for Mat4 { 34 | type Target = glam::Mat4; 35 | 36 | #[inline] 37 | fn deref(&self) -> &Self::Target { 38 | &self.value 39 | } 40 | } 41 | 42 | impl DerefMut for Mat4 { 43 | #[inline] 44 | fn deref_mut(&mut self) -> &mut Self::Target { 45 | &mut self.value 46 | } 47 | } 48 | 49 | fn render_row( 50 | ui: &imgui::Ui, 51 | label: &'static str, 52 | axis: &glam::Vec4, 53 | ) { 54 | ui.text(&imgui::im_str!( 55 | "{}: {} {} {} {}", 56 | label, 57 | axis.x(), 58 | axis.y(), 59 | axis.z(), 60 | axis.w() 61 | )); 62 | } 63 | 64 | fn render_row_mut( 65 | ui: &imgui::Ui, 66 | label: &'static str, 67 | data: &mut [&mut Mat4], 68 | get_axis_fn: GetF, 69 | set_axis_fn: SetF, 70 | ) -> bool 71 | where 72 | GetF: Fn(&mut glam::Mat4) -> glam::Vec4, 73 | SetF: Fn(&mut glam::Mat4, glam::Vec4), 74 | { 75 | let mut changed = false; 76 | let axis = (get_axis_fn)(data[0]); 77 | let mut val = [axis.x(), axis.y(), axis.z(), axis.w()]; 78 | if ui 79 | .input_float4(&imgui::im_str!("{}", label), &mut val) 80 | .build() 81 | { 82 | changed = true; 83 | let val: glam::Vec4 = val.into(); 84 | for d in data { 85 | set_axis_fn(*d, val); 86 | } 87 | } 88 | 89 | changed 90 | } 91 | 92 | impl InspectRenderDefault for Mat4 { 93 | fn render( 94 | data: &[&Mat4], 95 | label: &'static str, 96 | ui: &imgui::Ui, 97 | _args: &InspectArgsDefault, 98 | ) { 99 | if data.is_empty() { 100 | return; 101 | } 102 | 103 | render_row(ui, label, &data[0].value.x_axis()); 104 | render_row(ui, "", &data[0].value.y_axis()); 105 | render_row(ui, "", &data[0].value.z_axis()); 106 | render_row(ui, "", &data[0].value.w_axis()); 107 | } 108 | 109 | fn render_mut( 110 | data: &mut [&mut Mat4], 111 | label: &'static str, 112 | ui: &imgui::Ui, 113 | _args: &InspectArgsDefault, 114 | ) -> bool { 115 | if data.is_empty() { 116 | return false; 117 | } 118 | 119 | let mut changed = false; 120 | changed |= render_row_mut(ui, label, data, |m| m.x_axis(), |m, v| m.set_x_axis(v)); 121 | changed |= render_row_mut(ui, label, data, |m| m.y_axis(), |m, v| m.set_y_axis(v)); 122 | changed |= render_row_mut(ui, label, data, |m| m.z_axis(), |m, v| m.set_z_axis(v)); 123 | changed |= render_row_mut(ui, label, data, |m| m.w_axis(), |m, v| m.set_w_axis(v)); 124 | changed 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /minimum-math/src/na_convert.rs: -------------------------------------------------------------------------------- 1 | use nalgebra_glm as glm; 2 | 3 | pub fn vec2_glam_to_glm(value: glam::Vec2) -> glm::Vec2 { 4 | glm::Vec2::new(value.x(), value.y()) 5 | } 6 | 7 | pub fn vec3_glam_to_glm(value: glam::Vec3) -> glm::Vec3 { 8 | glm::Vec3::new(value.x(), value.y(), value.z()) 9 | } 10 | 11 | pub fn vec4_glam_to_glm(value: glam::Vec4) -> glm::Vec4 { 12 | glm::Vec4::new(value.x(), value.y(), value.z(), value.w()) 13 | } 14 | 15 | pub fn vec2_glm_to_glam(value: glm::Vec2) -> glam::Vec2 { 16 | glam::Vec2::new(value.x, value.y) 17 | } 18 | 19 | pub fn vec3_glm_to_glam(value: glm::Vec3) -> glam::Vec3 { 20 | glam::Vec3::new(value.x, value.y, value.z) 21 | } 22 | 23 | pub fn vec4_glm_to_glam(value: glm::Vec4) -> glam::Vec4 { 24 | glam::Vec4::new(value.x, value.y, value.z, value.w) 25 | } 26 | 27 | pub fn quat_glm_to_glam(value: glm::Quat) -> glam::Quat { 28 | glam::Quat::from_xyzw( 29 | value.coords.x, 30 | value.coords.y, 31 | value.coords.z, 32 | value.coords.w, 33 | ) 34 | } 35 | 36 | pub fn quat_glam_to_glm(value: glam::Quat) -> glm::Quat { 37 | glm::Quat::new(value.w(), value.x(), value.y(), value.z()) 38 | } 39 | -------------------------------------------------------------------------------- /minimum-transform/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum-transform" 3 | version = "0.1.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minimum-math = { path = "../minimum-math" } 11 | 12 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 13 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 14 | 15 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 16 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 17 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 18 | 19 | glam = { version = "0.8.5", features = ["serde"] } 20 | 21 | imgui = "0.5" 22 | 23 | imgui-inspect-derive = "0.6" 24 | imgui-inspect = "0.6" 25 | 26 | structopt = "0.3" 27 | serde = "1" 28 | uuid = "0.8" 29 | type-uuid = "0.1" 30 | inventory = "0.1" 31 | serde-diff = "0.3" 32 | 33 | log="0.4" -------------------------------------------------------------------------------- /minimum-transform/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | // mod position; 2 | // pub use position::PositionComponent; 3 | // 4 | // mod scale; 5 | // pub use scale::UniformScaleComponent; 6 | // pub use scale::NonUniformScaleComponent; 7 | // 8 | // mod rotation; 9 | // pub use rotation::Rotation2DComponent; 10 | // pub use rotation::RotationComponent; 11 | 12 | mod transform; 13 | pub use transform::TransformComponentDef; 14 | pub use transform::TransformComponent; 15 | -------------------------------------------------------------------------------- /minimum-transform/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | pub mod components; 6 | pub use components::*; 7 | -------------------------------------------------------------------------------- /minimum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimum" 3 | version = "0.3.0" 4 | authors = ["Philip Degarmo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | minimum-kernel = { path = "../minimum-kernel" } 12 | minimum-math = { path = "../minimum-math" } 13 | minimum-transform = { path = "../minimum-transform" } 14 | minimum-editor = { path = "../minimum-editor" } 15 | minimum-game = { path = "../minimum-game" } 16 | 17 | imgui-inspect-derive = "0.6" 18 | imgui-inspect = "0.6" 19 | 20 | imgui = "0.5" 21 | 22 | atelier-assets = { git = "https://github.com/aclysma/atelier-assets", branch = "minimum-0.3" } 23 | 24 | legion-transaction = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 25 | legion-prefab = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 26 | prefab-format = { git = "https://github.com/aclysma/prefab", branch="minimum-legion-0.3" } 27 | 28 | legion = { version = "0.3", default-features = false, features = ["serialize"] } 29 | 30 | glam = { version = "0.8.5", features = ["serde"] } 31 | 32 | structopt = "0.3" 33 | serde = "1" 34 | uuid = "0.8" 35 | type-uuid = "0.1" 36 | image2 = { version = "0.11", features = [ "ser" ] } 37 | inventory = "0.1" 38 | 39 | # We need this PR (https://github.com/servo/bincode/pull/288) but it's not published yet 40 | bincode = "1.3.1" 41 | mopa = "0.2" 42 | itertools = "0.8" 43 | 44 | serde-diff = "0.3" 45 | 46 | ron = "0.5" 47 | erased-serde = "0.3" 48 | 49 | log="0.4" 50 | env_logger = "0.6" 51 | 52 | [features] 53 | default = ["minimum-math/na_conversion"] -------------------------------------------------------------------------------- /minimum/src/daemon.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{AddrParseError, SocketAddr}, 3 | path::PathBuf, 4 | }; 5 | 6 | use atelier_assets::daemon::AssetDaemon; 7 | use structopt::StructOpt; 8 | 9 | /// Parameters to the asset daemon. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ```bash 14 | /// asset_daemon --db .assets_db --address "127.0.0.1:9999" assets 15 | /// ``` 16 | #[derive(StructOpt)] 17 | pub struct AssetDaemonOpt { 18 | /// Path to the asset metadata database directory. 19 | #[structopt(name = "db", long, parse(from_os_str), default_value = ".assets_db")] 20 | pub db_dir: PathBuf, 21 | /// Socket address for the daemon to listen for connections, e.g. "127.0.0.1:9999". 22 | #[structopt( 23 | short, 24 | long, 25 | parse(try_from_str = parse_socket_addr), 26 | default_value = "127.0.0.1:9999" 27 | )] 28 | pub address: SocketAddr, 29 | /// Directories to watch for assets. 30 | #[structopt(parse(from_os_str), default_value = "assets")] 31 | pub asset_dirs: Vec, 32 | } 33 | 34 | /// Parses a string as a socket address. 35 | fn parse_socket_addr(s: &str) -> std::result::Result { 36 | s.parse() 37 | } 38 | 39 | pub fn create_default_asset_daemon() -> AssetDaemon { 40 | let opt = AssetDaemonOpt::from_args(); 41 | 42 | AssetDaemon::default() 43 | .with_db_path(opt.db_dir) 44 | .with_address(opt.address) 45 | .with_asset_dirs(opt.asset_dirs) 46 | } 47 | -------------------------------------------------------------------------------- /minimum/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod daemon; 2 | 3 | pub mod components { 4 | pub use minimum_transform::components::*; 5 | pub use minimum_editor::components::*; 6 | } 7 | 8 | pub mod pipeline { 9 | pub use minimum_kernel::pipeline::*; 10 | } 11 | 12 | pub mod resources { 13 | pub use minimum_kernel::resources::*; 14 | pub use minimum_game::resources::*; 15 | 16 | pub mod editor { 17 | pub use minimum_editor::resources::*; 18 | } 19 | } 20 | pub mod systems { 21 | pub use minimum_kernel::systems::*; 22 | pub use minimum_game::systems::*; 23 | 24 | mod editor { 25 | pub use minimum_editor::systems::*; 26 | } 27 | } 28 | 29 | pub use minimum_kernel::prefab_cooking; 30 | pub use minimum_kernel::ComponentRegistry; 31 | pub use minimum_kernel::ComponentRegistryBuilder; 32 | pub use minimum_kernel::DynAssetLoader; 33 | pub use minimum_kernel::UpdateAssetResult; 34 | pub use minimum_kernel::AssetStorageSet; 35 | 36 | pub mod util { 37 | pub use minimum_kernel::util::*; 38 | } 39 | 40 | pub use minimum_math as math; 41 | pub use minimum_transform as transform; 42 | pub use minimum_kernel as kernel; 43 | pub use minimum_game as game; 44 | pub use minimum_editor as editor; 45 | 46 | pub use minimum_game::ImguiManager; 47 | 48 | pub use minimum_game::input; 49 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | # Many of the imports and modules are in "conceptual" order, for example 3 | # similar imports/modules are next to each other in order. 4 | reorder_imports = false 5 | reorder_modules = false 6 | 7 | # This makes merge conflicts less likely to occur, and makes it easier to see 8 | # symmetry between parameters. 9 | fn_args_layout = "Vertical" 10 | 11 | # Once TODOs are cleared we can turn this on 12 | # report_todo = "Unnumbered" 13 | # report_fixme = "Unnumbered" 14 | 15 | # Things I'd like to turn on if it's ever supported in cargo fmt 16 | # 17 | # ----- Force unsafe blocks to be multi-line ----- 18 | # 19 | # I'd like for unsafe blocks to be forced onto new lines, both before 20 | # the unsafe and after the "unsafe {". New line before the unsafe 21 | # could be omitted in simple let statements 22 | # 23 | # CURRENT BEHAVIOR 24 | # 25 | # semaphors.push(unsafe { vkCreateSemaphore() }); 26 | # 27 | # let x = unsafe { get_x() }; 28 | # 29 | # DESIRED BEHAVIOR 30 | # 31 | # semaphors.push( 32 | # unsafe { 33 | # vkCreateSemaphore(); 34 | # } 35 | # ) 36 | # 37 | # let x = unsafe { 38 | # get_x(); 39 | # } 40 | # 41 | # RATIONALE 42 | # 43 | # This would make unsafe code more conspicuous. One workaround for 44 | # particularly bad cases like the first example is to break it to 45 | # multiple lines. 46 | # 47 | # ----- fn_args_layout for callsites ----- 48 | # 49 | # I'd like the "vetical" behavior for fn_args_layout applied at callsites 50 | # as well 51 | # 52 | # CURRENT BEHAVIOR 53 | # 54 | # draw_circle(radius, position, color); 55 | # 56 | # LogicalPosition::new(p0.x - p1.x, p0.y - p1.y) 57 | # 58 | # DESIRED BEHAVIOR 59 | # 60 | # draw_circle 61 | # radius, 62 | # position, 63 | # color 64 | # ); 65 | # 66 | # LogicalPosition::new( 67 | # p0.x - p1.x, 68 | # p0.y - p1.y 69 | # ); 70 | # 71 | # RATIONALE 72 | # 73 | # It's not uncommon to insert new parameters, or change parameters, 74 | # being passed into a function. Merge conflicts are less likely to 75 | # happen if each argument gets its own line. 76 | # 77 | # In some cases there is symmetry that is helpful to see. When the 78 | # the symmetry is broken, it's more conspicuous. This can make bugs 79 | # easier to spot, or if it's not a bug, help the reader to see this. 80 | # 81 | # As a bonus it's also more consistent with fn_args_layout 82 | # --------------------------------------------------------------------------------