Implement staged system execution, make it easier to add systems, remove some compiler warnings
This commit is contained in:
parent
0a97cf7617
commit
9307265a5a
|
@ -1453,6 +1453,7 @@ dependencies = [
|
|||
"lyra-resource",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-log 0.1.4",
|
||||
|
|
|
@ -4,7 +4,7 @@ use lyra_engine::{
|
|||
game::Game,
|
||||
input::{InputButtons, KeyCode, MouseMotion},
|
||||
math::{Quat, Vec3, EulerRot},
|
||||
plugin::Plugin, ecs::{system::System, world::World, Access, Component}, DeltaTime, EventQueue, scene::CameraComponent,
|
||||
plugin::Plugin, ecs::{system::{System, IntoSystem}, world::World, Access, Component}, DeltaTime, EventQueue, scene::CameraComponent,
|
||||
};
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
|
@ -159,6 +159,14 @@ impl System for FreeFlyCameraPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoSystem<()> for FreeFlyCameraPlugin {
|
||||
type System = Self;
|
||||
|
||||
fn into_system(self) -> Self::System {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for FreeFlyCameraPlugin {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
game.with_system("free_fly_camera_system", FreeFlyCameraPlugin, &[]);
|
||||
|
|
|
@ -13,6 +13,7 @@ struct FixedTimestep {
|
|||
accumulator: f32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl FixedTimestep {
|
||||
pub fn new(max_tps: u32) -> Self {
|
||||
Self {
|
||||
|
@ -185,6 +186,7 @@ async fn main() {
|
|||
Ok(())
|
||||
};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let fps_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<fps_counter::FPSCounter>();
|
||||
|
||||
|
@ -245,6 +247,7 @@ async fn main() {
|
|||
.finish()
|
||||
);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let test_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
let handler = world.get_resource::<ActionHandler>();
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ impl ComponentColumn {
|
|||
/// # Safety
|
||||
///
|
||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, is_dynamic: bool, tick: Tick) {
|
||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||
assert!(entity_index < self.capacity);
|
||||
|
||||
let mut data = self.data.borrow_mut();
|
||||
|
@ -67,16 +67,6 @@ impl ComponentColumn {
|
|||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout.size));
|
||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), self.info.layout.size);
|
||||
|
||||
// I thought there was a point for why I added the dealloc, but now the it
|
||||
// only seems to cause invalid pointers crashes.
|
||||
//
|
||||
// if is_dynamic {
|
||||
// unsafe {
|
||||
// let layout = self.info.layout.into_layout().unwrap();
|
||||
// std::alloc::dealloc(comp_src.as_ptr(), layout);
|
||||
// }
|
||||
// }
|
||||
|
||||
// check if a component spot is being set twice and that the entity's tick is
|
||||
// already stored
|
||||
if self.entity_ticks.len() > entity_index {
|
||||
|
@ -258,11 +248,10 @@ impl Archetype {
|
|||
|
||||
let entity_index = self.entities.len();
|
||||
self.entities.insert(entity, ArchetypeEntityId(entity_index as u64));
|
||||
let is_dynamic = bundle.is_dynamic();
|
||||
|
||||
bundle.take(|data, type_id, _size| {
|
||||
let col = self.get_column_mut(type_id).unwrap();
|
||||
unsafe { col.set_at(entity_index, data, is_dynamic, *tick); }
|
||||
unsafe { col.set_at(entity_index, data, *tick); }
|
||||
col.len += 1;
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use lyra_ecs::world::World;
|
|||
|
||||
use crate::Access;
|
||||
|
||||
use super::{System, Criteria};
|
||||
use super::{System, Criteria, IntoSystem};
|
||||
|
||||
/// A system that executes a batch of systems in order that they were given.
|
||||
/// You can optionally add criteria that must pass before the systems are
|
||||
|
@ -80,3 +80,11 @@ impl System for BatchedSystem {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystem<()> for BatchedSystem {
|
||||
type System = Self;
|
||||
|
||||
fn into_system(self) -> Self::System {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -166,20 +166,19 @@ impl World {
|
|||
|
||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
||||
let res_index = arch.reserve_one(entity);
|
||||
let is_dynamic = bundle.is_dynamic();
|
||||
|
||||
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
||||
unsafe {
|
||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||
.add(res_index.0 as usize * col_info.layout.size));
|
||||
let col = arch.get_column_mut(col_type).unwrap();
|
||||
col.set_at(res_index.0 as _, ptr, is_dynamic, tick);
|
||||
col.set_at(res_index.0 as _, ptr, tick);
|
||||
}
|
||||
}
|
||||
|
||||
bundle.take(|data, type_id, _size| {
|
||||
let col = arch.get_column_mut(type_id).unwrap();
|
||||
unsafe { col.set_at(res_index.0 as _, data, is_dynamic, tick); }
|
||||
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
||||
col.len += 1;
|
||||
});
|
||||
|
||||
|
|
|
@ -27,3 +27,4 @@ syn = "2.0.26"
|
|||
quote = "1.0.29"
|
||||
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }
|
||||
itertools = "0.11.0"
|
||||
thiserror = "1.0.56"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use instant::Instant;
|
||||
use lyra_ecs::{Component, world::World, system::IntoSystem};
|
||||
use lyra_ecs::{Component, world::World};
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
|
@ -34,6 +34,6 @@ pub struct DeltaTimePlugin;
|
|||
impl Plugin for DeltaTimePlugin {
|
||||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
game.world().add_resource(DeltaTime(0.0, None));
|
||||
game.with_system("delta_time", delta_time_system.into_system(), &[]);
|
||||
game.with_system("delta_time", delta_time_system, &[]);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
|
|||
|
||||
use async_std::task::block_on;
|
||||
|
||||
use lyra_ecs::{world::World, system::{GraphExecutor, System}};
|
||||
use lyra_ecs::{world::World, system::{System, IntoSystem}};
|
||||
use tracing::{info, error, Level};
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_subscriber::{
|
||||
|
@ -13,7 +13,15 @@ use tracing_subscriber::{
|
|||
|
||||
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}};
|
||||
|
||||
use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, plugin::Plugin, change_tracker::Ct, EventQueue};
|
||||
use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, plugin::Plugin, change_tracker::Ct, EventQueue, StagedExecutor, Stage};
|
||||
|
||||
#[derive(Clone, Copy, Hash, Debug)]
|
||||
pub enum GameStages {
|
||||
Core,
|
||||
User,
|
||||
}
|
||||
|
||||
impl Stage for GameStages {}
|
||||
|
||||
pub struct Controls<'a> {
|
||||
pub world: &'a mut World,
|
||||
|
@ -36,20 +44,18 @@ struct GameLoop {
|
|||
renderer: Box<dyn Renderer>,
|
||||
|
||||
world: World,
|
||||
/// higher priority systems
|
||||
engine_sys_dispatcher: GraphExecutor,
|
||||
user_sys_dispatcher: GraphExecutor,
|
||||
|
||||
staged_exec: StagedExecutor,
|
||||
}
|
||||
|
||||
impl GameLoop {
|
||||
pub async fn new(window: Arc<Window>, world: World, user_systems: GraphExecutor) -> GameLoop {
|
||||
pub async fn new(window: Arc<Window>, world: World, staged_exec: StagedExecutor) -> GameLoop {
|
||||
Self {
|
||||
window: Arc::clone(&window),
|
||||
renderer: Box::new(BasicRenderer::create_with_window(window).await),
|
||||
|
||||
world,
|
||||
engine_sys_dispatcher: GraphExecutor::new(),
|
||||
user_sys_dispatcher: user_systems,
|
||||
staged_exec,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,12 +74,9 @@ impl GameLoop {
|
|||
|
||||
async fn update(&mut self) {
|
||||
let world_ptr = NonNull::from(&self.world);
|
||||
if let Err(e) = self.engine_sys_dispatcher.execute(world_ptr, true) {
|
||||
error!("Error when executing engine ecs systems: '{}'", e);
|
||||
}
|
||||
|
||||
if let Err(e) = self.user_sys_dispatcher.execute(world_ptr, true) {
|
||||
error!("Error when executing user ecs systems: '{}'", e);
|
||||
if let Err(e) = self.staged_exec.execute(world_ptr, true) {
|
||||
error!("Error when executing staged systems: '{}'", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,16 +209,21 @@ impl GameLoop {
|
|||
pub struct Game {
|
||||
world: Option<World>,
|
||||
plugins: VecDeque<Box<dyn Plugin>>,
|
||||
system_dispatcher: Option<GraphExecutor>,
|
||||
system_exec: Option<StagedExecutor>,
|
||||
startup_systems: VecDeque<Box<dyn System>>,
|
||||
|
||||
}
|
||||
|
||||
impl Default for Game {
|
||||
fn default() -> Self {
|
||||
let mut staged = StagedExecutor::new();
|
||||
staged.add_stage(GameStages::Core);
|
||||
staged.add_stage_after(GameStages::Core, GameStages::User);
|
||||
|
||||
Self {
|
||||
world: Some(World::new()),
|
||||
plugins: VecDeque::new(),
|
||||
system_dispatcher: Some(GraphExecutor::new()),
|
||||
system_exec: Some(staged),
|
||||
startup_systems: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
@ -233,12 +241,47 @@ impl Game {
|
|||
}
|
||||
|
||||
/// Add a system to the ecs world
|
||||
pub fn with_system<S>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
|
||||
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
|
||||
where
|
||||
S: System + 'static
|
||||
S: IntoSystem<A>,
|
||||
<S as IntoSystem<A>>::System: 'static
|
||||
{
|
||||
let system_dispatcher = self.system_dispatcher.as_mut().unwrap();
|
||||
system_dispatcher.insert_system(name, system, depends);
|
||||
let system_dispatcher = self.system_exec.as_mut().unwrap();
|
||||
system_dispatcher.add_system_to_stage(GameStages::User, name, system.into_system(), depends);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stage.
|
||||
///
|
||||
/// This stage could run at any moment if nothing is dependent on it.
|
||||
pub fn add_stage<T: Stage>(&mut self, stage: T) -> &mut Self {
|
||||
let system_dispatcher = self.system_exec.as_mut().unwrap();
|
||||
system_dispatcher.add_stage(stage);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stage that executes after another one.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `before` - The stage that will run before `after`.
|
||||
/// * `after` - The stage that will run after `before`.
|
||||
pub fn add_stage_after<T: Stage, U: Stage>(&mut self, before: T, after: U) -> &mut Self {
|
||||
let system_dispatcher = self.system_exec.as_mut().unwrap();
|
||||
system_dispatcher.add_stage_after(before, after);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a system to an already existing stage.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the stage was not already added to the executor
|
||||
pub fn add_system_to_stage<ST: Stage, I: IntoSystem<()> + 'static>(&mut self, stage: ST,
|
||||
name: &str, system: I, depends: &[&str]) -> &mut Self {
|
||||
let system_dispatcher = self.system_exec.as_mut().unwrap();
|
||||
system_dispatcher.add_system_to_stage(stage, name, system.into_system(), depends);
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -304,7 +347,7 @@ impl Game {
|
|||
let event_loop = EventLoop::new();
|
||||
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
|
||||
|
||||
let system_dispatcher = self.system_dispatcher.take().unwrap();
|
||||
let system_dispatcher = self.system_exec.take().unwrap();
|
||||
let mut g_loop = GameLoop::new(Arc::clone(&window), world, system_dispatcher).await;
|
||||
g_loop.on_init().await;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashMap, ops::Deref};
|
||||
|
||||
use lyra_ecs::{world::World, system::IntoSystem};
|
||||
use lyra_ecs::world::World;
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
|
@ -392,6 +392,6 @@ pub struct InputActionPlugin;
|
|||
|
||||
impl Plugin for InputActionPlugin {
|
||||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
game.with_system("input_actions", actions_system.into_system(), &[]);
|
||||
game.with_system("input_actions", actions_system, &[]);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use std::ptr::NonNull;
|
||||
|
||||
use glam::Vec2;
|
||||
use lyra_ecs::world::World;
|
||||
use lyra_ecs::{world::World, system::IntoSystem};
|
||||
use winit::event::MouseScrollDelta;
|
||||
|
||||
use crate::{EventQueue, plugin::Plugin};
|
||||
|
@ -122,6 +122,14 @@ impl crate::ecs::system::System for InputSystem {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoSystem<()> for InputSystem {
|
||||
type System = Self;
|
||||
|
||||
fn into_system(self) -> Self::System {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin that runs InputSystem
|
||||
#[derive(Default)]
|
||||
pub struct InputPlugin;
|
||||
|
|
|
@ -16,6 +16,9 @@ pub mod change_tracker;
|
|||
pub mod events;
|
||||
pub use events::*;
|
||||
|
||||
pub mod stage;
|
||||
pub use stage::*;
|
||||
|
||||
pub mod delta_time;
|
||||
pub use delta_time::*;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use glam::{Vec2, IVec2};
|
||||
use lyra_ecs::{world::World, system::IntoSystem};
|
||||
use lyra_ecs::world::World;
|
||||
use tracing::{warn, error};
|
||||
use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
|
||||
|
||||
|
@ -374,6 +374,6 @@ impl Plugin for WindowPlugin {
|
|||
let window_options = WindowOptions::default();
|
||||
|
||||
game.world().add_resource(Ct::new(window_options));
|
||||
game.with_system("window_updater", window_updater_system.into_system(), &[]);
|
||||
game.with_system("window_updater", window_updater_system, &[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
use std::{hash::{Hash, DefaultHasher, Hasher}, collections::{HashMap, HashSet, VecDeque}, ptr::NonNull, fmt::Debug};
|
||||
|
||||
use lyra_ecs::{system::{GraphExecutor, GraphExecutorError, System}, World};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum StagedExecutorError {
|
||||
#[error("could not find the stage that {0} depends on")]
|
||||
MissingStage(String, u64),
|
||||
#[error("[stage={0}] could not find a system's dependency named `{1}`")]
|
||||
MissingSystem(String, String),
|
||||
#[error("[stage={0}] system `{1}` returned with an error: `{2}`")]
|
||||
SystemError(String, String, anyhow::Error)
|
||||
}
|
||||
|
||||
impl StagedExecutorError {
|
||||
pub fn from_graph_error(stage: String, value: GraphExecutorError) -> Self {
|
||||
match value {
|
||||
GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s),
|
||||
GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Stage can be used to group the execution of systems together.
|
||||
pub trait Stage: Hash + Debug {
|
||||
fn hash_stage(&self) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
Hash::hash(self, &mut s);
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct StageStorage {
|
||||
hash: u64,
|
||||
name: String,
|
||||
exec: GraphExecutor,
|
||||
depend: Option<u64>,
|
||||
}
|
||||
|
||||
/// A system executor that executes systems in stages.
|
||||
///
|
||||
/// Stages can depend on other stages to ensure that a group of systems run before another group.
|
||||
#[derive(Default)]
|
||||
pub struct StagedExecutor {
|
||||
stages: HashMap<u64, StageStorage>,
|
||||
}
|
||||
|
||||
impl StagedExecutor {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Add a stage that executes after another one.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `before` - The stage that will run before `after`.
|
||||
/// * `after` - The stage that will run after `before`.
|
||||
pub fn add_stage_after<T, U>(&mut self, before: T, after: U)
|
||||
where
|
||||
T: Stage,
|
||||
U: Stage,
|
||||
{
|
||||
let name = format!("{:?}", after);
|
||||
|
||||
let strg = StageStorage {
|
||||
hash: after.hash_stage(),
|
||||
name,
|
||||
exec: GraphExecutor::default(),
|
||||
depend: Some(before.hash_stage()),
|
||||
};
|
||||
self.stages.insert(after.hash_stage(), strg);
|
||||
}
|
||||
|
||||
/// Add a stage.
|
||||
///
|
||||
/// This stage could run at any moment if nothing is dependent on it.
|
||||
pub fn add_stage<T: Stage>(&mut self, stage: T) {
|
||||
let name = format!("{:?}", stage);
|
||||
|
||||
let strg = StageStorage {
|
||||
hash: stage.hash_stage(),
|
||||
name,
|
||||
exec: GraphExecutor::default(),
|
||||
depend: None
|
||||
};
|
||||
self.stages.insert(stage.hash_stage(), strg);
|
||||
}
|
||||
|
||||
/// Add a system to an already existing stage.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the stage was not already added to the executor
|
||||
pub fn add_system_to_stage<T, S>(&mut self, stage: T, name: &str, system: S, depends: &[&str])
|
||||
where
|
||||
T: Stage,
|
||||
S: System + 'static
|
||||
{
|
||||
let hash = stage.hash_stage();
|
||||
|
||||
let stage = self.stages.get_mut(&hash)
|
||||
.expect("Unable to find the stage to add the system into! \
|
||||
Did you add the stage first?");
|
||||
let exec = &mut stage.exec;
|
||||
|
||||
exec.insert_system(name, system, depends);
|
||||
}
|
||||
|
||||
/// Execute the staged systems in order.
|
||||
///
|
||||
/// If `stop_on_error` is false but errors are encountered, those errors will be returned in a Vec.
|
||||
pub fn execute(&mut self, world: NonNull<World>, stop_on_error: bool) -> Result<Vec<StagedExecutorError>, StagedExecutorError> {
|
||||
let mut stack = VecDeque::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
for (_, node) in self.stages.iter() {
|
||||
self.topological_sort(&mut stack, &mut visited, node)?;
|
||||
}
|
||||
|
||||
let mut errors = vec![];
|
||||
while let Some(node) = stack.pop_front() {
|
||||
let stage = self.stages.get_mut(&node).unwrap();
|
||||
|
||||
if let Err(e) = stage.exec.execute(world, stop_on_error) {
|
||||
let e = StagedExecutorError::from_graph_error(stage.name.clone(), e);
|
||||
|
||||
if stop_on_error {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
errors.push(e);
|
||||
unimplemented!("Cannot resume staged execution from error"); // TODO: resume staged execution from error
|
||||
}
|
||||
}
|
||||
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
fn topological_sort<'a>(&'a self, stack: &mut VecDeque<u64>, visited: &mut HashSet<u64>, node: &'a StageStorage) -> Result<(), StagedExecutorError> {
|
||||
if !visited.contains(&node.hash) {
|
||||
visited.insert(node.hash);
|
||||
|
||||
if let Some(depend) = node.depend {
|
||||
let node = self.stages.get(&depend)
|
||||
.ok_or_else(|| StagedExecutorError::MissingStage(node.name.clone(), depend))?;
|
||||
|
||||
if !visited.contains(&node.hash) {
|
||||
self.topological_sort(stack, visited, node)?;
|
||||
}
|
||||
}
|
||||
|
||||
stack.push_back(node.hash);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue