Skip to content

Commit

Permalink
feat!: implement widget systems and system input parameters. (#193)
Browse files Browse the repository at this point in the history
- This also removes the `SystemResult` and `EcsError` types from
`bones_ecs`. We don't really need systems to return results, and
`EcsError` was nearly unused.
  • Loading branch information
zicklag committed Sep 1, 2023
1 parent 27ac744 commit 6bee45d
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 268 deletions.
2 changes: 1 addition & 1 deletion demos/assets_minimal/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn main() {

/// System to render the home menu.
fn menu_system(
egui_ctx: Res<EguiCtx>,
egui_ctx: Egui,
// We can access our root asset by using the Root parameter.
meta: Root<GameMeta>,
) {
Expand Down
32 changes: 24 additions & 8 deletions demos/features/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,16 @@ fn menu_startup(
/// Our main menu system.
fn menu_system(
meta: Root<GameMeta>,
egui_ctx: ResMut<EguiCtx>,
ctx: Egui,
mut sessions: ResMut<Sessions>,
mut session_options: ResMut<SessionOptions>,
egui_textures: Res<EguiTextures>,
// Get the localization field from our `GameMeta`
localization: Localization<GameMeta>,
) {
// Render the menu.
egui::CentralPanel::default()
.frame(egui::Frame::none())
.show(&egui_ctx, |ui| {
.show(&ctx, |ui| {
BorderedFrame::new(&meta.menu_border).show(ui, |ui| {
ui.vertical_centered(|ui| {
ui.add_space(20.0);
Expand Down Expand Up @@ -207,9 +206,11 @@ fn menu_system(

ui.add_space(30.0);

// When using a bones image in egui, we have to get it's corresponding egui texture
// from the egui textures resource.
ui.image(egui_textures.get(meta.menu_image), [100., 100.]);
// We can use the `widget()` method on the `Egui` to conveniently run bones
// systems that can modify the `egui::Ui` and return an `egui::Response`.
//
// This makes it easier to compose widgets that have differing access to the bones world.
ctx.widget(demo_widget, ui);

ui.add_space(30.0);
});
Expand Down Expand Up @@ -378,14 +379,14 @@ fn path2d_demo_startup(
/// Simple UI system that shows a button at the bottom of the screen to delete the current session
/// and go back to the main menu.
fn back_to_menu_ui(
egui_ctx: ResMut<EguiCtx>,
ctx: Egui,
mut sessions: ResMut<Sessions>,
mut session_options: ResMut<SessionOptions>,
localization: Localization<GameMeta>,
) {
egui::CentralPanel::default()
.frame(egui::Frame::none())
.show(&egui_ctx, |ui| {
.show(&ctx, |ui| {
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
ui.add_space(20.0);
if ui.button(localization.get("back-to-menu")).clicked() {
Expand All @@ -395,3 +396,18 @@ fn back_to_menu_ui(
});
});
}

/// This is an example widget system.
fn demo_widget(
// Widget systems must have an `In<&mut egui::Ui>` parameter as their first argument.
mut ui: In<&mut egui::Ui>,
// They can have any normal bones system parameters
meta: Root<GameMeta>,
egui_textures: Res<EguiTextures>,
// And they may return an `egui::Response` or any other value.
) -> egui::Response {
ui.label("Demo Widget");
// When using a bones image in egui, we have to get it's corresponding egui texture
// from the egui textures resource.
ui.image(egui_textures.get(meta.menu_image), [100., 100.])
}
4 changes: 2 additions & 2 deletions demos/hello_world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ fn main() {
}

/// System to render the home menu.
fn menu_system(egui_ctx: Res<EguiCtx>) {
egui::CentralPanel::default().show(&egui_ctx, |ui| {
fn menu_system(ctx: Egui) {
egui::CentralPanel::default().show(&ctx, |ui| {
ui.label("Hello World");
});
}
10 changes: 4 additions & 6 deletions framework_crates/bones_ecs/examples/pos_vel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ fn main() {
// Initialize an empty world
let mut world = World::new();

// Run our setup system once to spawn our entities
world.run_system(setup_system).ok();

// Create a SystemStages to store the systems that we will run more than once.
let mut stages = SystemStages::with_core_stages();

// Add our systems to the dispatcher
// Add our systems to the system stages
stages
.add_startup_system(startup_system)
.add_system_to_stage(CoreStage::Update, pos_vel_system)
.add_system_to_stage(CoreStage::PostUpdate, print_system);

Expand All @@ -38,12 +36,12 @@ fn main() {

// Run our game loop for 10 frames
for _ in 0..10 {
stages.run(&mut world).unwrap();
stages.run(&mut world);
}
}

/// Setup system that spawns two entities with a Pos and a Vel component.
fn setup_system(
fn startup_system(
mut entities: ResMut<Entities>,
mut positions: CompMut<Pos>,
mut velocities: CompMut<Vel>,
Expand Down
102 changes: 50 additions & 52 deletions framework_crates/bones_ecs/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ pub struct ComponentStores {
pub(crate) components: HashMap<SchemaId, Arc<AtomicCell<UntypedComponentStore>>>,
}

/// An error returned when trying to access an uninitialized component.
#[derive(Debug)]
pub struct NotInitialized;

impl std::fmt::Display for NotInitialized {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Component not initialized")
}
}
impl std::error::Error for NotInitialized {}

// SOUND: all of the functions for ComponentStores requires that the types stored implement Sync +
// Send.
unsafe impl Sync for ComponentStores {}
Expand Down Expand Up @@ -52,7 +63,7 @@ impl ComponentStores {
}

/// Get the components of a certain type
pub fn get_cell<T: HasSchema>(&self) -> Result<AtomicComponentStore<T>, EcsError> {
pub fn get_cell<T: HasSchema>(&self) -> Result<AtomicComponentStore<T>, NotInitialized> {
let untyped = self.get_cell_by_schema_id(T::schema().id())?;

// Safe: We know the schema matches, and `ComponentStore<T>` is repr(transparent) over
Expand All @@ -68,13 +79,9 @@ impl ComponentStores {
/// Borrow a component store.
/// # Errors
/// Errors if the component store has not been initialized yet.
pub fn get<T: HasSchema>(&self) -> Result<Ref<ComponentStore<T>>, EcsError> {
pub fn get<T: HasSchema>(&self) -> Result<Ref<ComponentStore<T>>, NotInitialized> {
let id = T::schema().id();
let atomicref = self
.components
.get(&id)
.ok_or(EcsError::NotInitialized)?
.borrow();
let atomicref = self.components.get(&id).ok_or(NotInitialized)?.borrow();

// SOUND: ComponentStore<T> is repr(transparent) over UntypedComponent store.
let atomicref = Ref::map(atomicref, |x| unsafe {
Expand All @@ -87,13 +94,9 @@ impl ComponentStores {
/// Borrow a component store.
/// # Errors
/// Errors if the component store has not been initialized yet.
pub fn get_mut<T: HasSchema>(&self) -> Result<RefMut<ComponentStore<T>>, EcsError> {
pub fn get_mut<T: HasSchema>(&self) -> Result<RefMut<ComponentStore<T>>, NotInitialized> {
let id = T::schema().id();
let atomicref = self
.components
.get(&id)
.ok_or(EcsError::NotInitialized)?
.borrow_mut();
let atomicref = self.components.get(&id).ok_or(NotInitialized)?.borrow_mut();

// SOUND: ComponentStore<T> is repr(transparent) over UntypedComponent store.
let atomicref = RefMut::map(atomicref, |x| unsafe {
Expand All @@ -107,11 +110,8 @@ impl ComponentStores {
pub fn get_cell_by_schema_id(
&self,
id: SchemaId,
) -> Result<Arc<AtomicCell<UntypedComponentStore>>, EcsError> {
self.components
.get(&id)
.cloned()
.ok_or(EcsError::NotInitialized)
) -> Result<Arc<AtomicCell<UntypedComponentStore>>, NotInitialized> {
self.components.get(&id).cloned().ok_or(NotInitialized)
}
}

Expand All @@ -125,44 +125,42 @@ mod test {

#[test]
fn borrow_many_mut() {
World::new()
.run_system(
|mut entities: ResMut<Entities>, mut my_datas: CompMut<MyData>| {
let ent1 = entities.create();
let ent2 = entities.create();

my_datas.insert(ent1, MyData(7));
my_datas.insert(ent2, MyData(8));

{
let [data2, data1] = my_datas.get_many_mut([ent2, ent1]).unwrap_many();

data1.0 = 0;
data2.0 = 1;
}

assert_eq!(my_datas.get(ent1).unwrap().0, 0);
assert_eq!(my_datas.get(ent2).unwrap().0, 1);
},
)
.unwrap();
World::new().run_system(
|mut entities: ResMut<Entities>, mut my_datas: CompMut<MyData>| {
let ent1 = entities.create();
let ent2 = entities.create();

my_datas.insert(ent1, MyData(7));
my_datas.insert(ent2, MyData(8));

{
let [data2, data1] = my_datas.get_many_mut([ent2, ent1]).unwrap_many();

data1.0 = 0;
data2.0 = 1;
}

assert_eq!(my_datas.get(ent1).unwrap().0, 0);
assert_eq!(my_datas.get(ent2).unwrap().0, 1);
},
(),
);
}

#[test]
#[should_panic = "must be unique"]
fn borrow_many_overlapping_mut() {
World::new()
.run_system(
|mut entities: ResMut<Entities>, mut my_datas: CompMut<MyData>| {
let ent1 = entities.create();
let ent2 = entities.create();

my_datas.insert(ent1, MyData(1));
my_datas.insert(ent2, MyData(2));

my_datas.get_many_mut([ent1, ent2, ent1]).unwrap_many();
},
)
.unwrap();
World::new().run_system(
|mut entities: ResMut<Entities>, mut my_datas: CompMut<MyData>| {
let ent1 = entities.create();
let ent2 = entities.create();

my_datas.insert(ent1, MyData(1));
my_datas.insert(ent2, MyData(2));

my_datas.get_many_mut([ent1, ent2, ent1]).unwrap_many();
},
(),
)
}
}
36 changes: 0 additions & 36 deletions framework_crates/bones_ecs/src/error.rs

This file was deleted.

6 changes: 1 addition & 5 deletions framework_crates/bones_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ pub mod system;
pub use bones_schema as schema;
pub use bones_utils as utils;

mod error;
pub use error::EcsError;

mod world;
pub use world::{FromWorld, World};

Expand All @@ -37,7 +34,6 @@ pub mod prelude {
bitset::*,
components::*,
entities::*,
error::*,
resources::*,
stage::{CoreStage::*, *},
system::*,
Expand Down Expand Up @@ -96,7 +92,7 @@ mod test {
let e = entities.create();
comps.insert(e, default());
},
(),
)
.unwrap();
}
}
Loading

0 comments on commit 6bee45d

Please sign in to comment.