azalea_ecs/
lib.rs

1#![feature(trait_alias)]
2
3//! Re-export important parts of [`bevy_ecs`] and [`bevy_app`] and make them
4//! more compatible with Azalea.
5//!
6//! This is completely compatible with `bevy_ecs`, so it won't cause issues if
7//! you use plugins meant for Bevy.
8//!
9//! Changes:
10//! - Add [`TickPlugin`], [`TickStage`] and [`AppTickExt`] (which adds
11//!   `app.add_tick_system` and `app.add_tick_system_set`).
12//! - Change the macros to use azalea/azalea_ecs instead of bevy/bevy_ecs
13//! - Rename `world::World` to [`ecs::Ecs`]
14//! - Re-export `bevy_app` in the [`app`] module.
15//!
16//! [`bevy_ecs`]: https://docs.rs/bevy_ecs
17//! [`bevy_app`]: https://docs.rs/bevy_app
18
19use std::time::{Duration, Instant};
20
21pub mod ecs {
22    pub use bevy_ecs::world::World as Ecs;
23    pub use bevy_ecs::world::{EntityMut, EntityRef, Mut};
24}
25pub mod component {
26    pub use azalea_ecs_macros::Component;
27    pub use bevy_ecs::component::{ComponentId, ComponentStorage, Components, TableStorage};
28
29    // we do this because re-exporting Component would re-export the macro as well,
30    // which is bad (since we have our own Component macro)
31    // instead, we have to do this so Component is a trait alias and the original
32    // impl-able trait is still available as BevyComponent
33    pub trait Component = bevy_ecs::component::Component;
34    pub use bevy_ecs::component::Component as BevyComponent;
35}
36pub mod bundle {
37    pub use azalea_ecs_macros::Bundle;
38    pub trait Bundle = bevy_ecs::bundle::Bundle;
39    pub use bevy_ecs::bundle::Bundle as BevyBundle;
40}
41pub mod system {
42    pub use azalea_ecs_macros::Resource;
43    pub use bevy_ecs::system::{
44        Command, Commands, EntityCommands, Query, Res, ResMut, SystemState,
45    };
46    pub trait Resource = bevy_ecs::system::Resource;
47    pub use bevy_ecs::system::Resource as BevyResource;
48}
49pub use bevy_app as app;
50pub use bevy_ecs::{entity, event, ptr, query, schedule, storage};
51
52use app::{App, CoreStage, Plugin};
53use bevy_ecs::schedule::*;
54use ecs::Ecs;
55
56pub struct TickPlugin {
57    /// How often a tick should happen. 50 milliseconds by default. Set to 0 to
58    /// tick every update.
59    pub tick_interval: Duration,
60}
61impl Plugin for TickPlugin {
62    fn build(&self, app: &mut App) {
63        app.add_stage_before(
64            CoreStage::Update,
65            TickLabel,
66            TickStage {
67                interval: self.tick_interval,
68                next_tick: Instant::now(),
69                stage: Box::new(SystemStage::parallel()),
70            },
71        );
72    }
73}
74impl Default for TickPlugin {
75    fn default() -> Self {
76        Self {
77            tick_interval: Duration::from_millis(50),
78        }
79    }
80}
81
82#[derive(StageLabel)]
83struct TickLabel;
84
85/// A [`Stage`] that runs every 50 milliseconds.
86pub struct TickStage {
87    pub interval: Duration,
88    pub next_tick: Instant,
89    stage: Box<dyn Stage>,
90}
91
92impl Stage for TickStage {
93    fn run(&mut self, ecs: &mut Ecs) {
94        // if the interval is 0, that means it runs every tick
95        if self.interval.is_zero() {
96            self.stage.run(ecs);
97            return;
98        }
99        // keep calling run until it's caught up
100        // TODO: Minecraft bursts up to 10 ticks and then skips, we should too (but
101        // check the source so we do it right)
102        while Instant::now() > self.next_tick {
103            self.next_tick += self.interval;
104            self.stage.run(ecs);
105        }
106    }
107}
108
109pub trait AppTickExt {
110    fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App;
111    fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App;
112}
113
114impl AppTickExt for App {
115    /// Adds a set of ECS systems that will run every 50 milliseconds.
116    ///
117    /// Note that you should NOT have `EventReader`s in tick systems, as this
118    /// will make them sometimes be missed.
119    fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App {
120        let tick_stage = self
121            .schedule
122            .get_stage_mut::<TickStage>(TickLabel)
123            .expect("Tick Stage not found");
124        let stage = tick_stage
125            .stage
126            .downcast_mut::<SystemStage>()
127            .expect("Fixed Timestep sub-stage is not a SystemStage");
128        stage.add_system_set(system_set);
129        self
130    }
131
132    /// Adds a new ECS system that will run every 50 milliseconds.
133    ///
134    /// Note that you should NOT have `EventReader`s in tick systems, as this
135    /// will make them sometimes be missed.
136    fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App {
137        let tick_stage = self
138            .schedule
139            .get_stage_mut::<TickStage>(TickLabel)
140            .expect("Tick Stage not found");
141        let stage = tick_stage
142            .stage
143            .downcast_mut::<SystemStage>()
144            .expect("Fixed Timestep sub-stage is not a SystemStage");
145        stage.add_system(system);
146        self
147    }
148}