use the graph system dispatcher to execute systems, make it easier to add systems

This commit is contained in:
SeanOMik 2023-06-30 01:17:06 -04:00
parent 4c6edff639
commit f7a455997d
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
3 changed files with 74 additions and 87 deletions

View File

@ -9,48 +9,40 @@ pub mod components;
/// A trait that represents a simple system /// A trait that represents a simple system
pub trait SimpleSystem { pub trait SimpleSystem {
fn setup(&mut self, world: &mut World) -> anyhow::Result<()> {
Ok(())
}
// todo: make async? // todo: make async?
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>; fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>;
} }
pub type SystemFn = dyn Fn(&mut World) -> anyhow::Result<()>; impl<S> SimpleSystem for S
where S: FnMut(&mut World) -> anyhow::Result<()>
pub struct SystemFnExecutor { {
systems: HashMap<String, Box<SystemFn>>,
}
impl SystemFnExecutor {
pub fn new() -> Self {
Self {
systems: HashMap::new(),
}
}
pub fn with_systems(systems: HashMap<String, Box<SystemFn>>) -> Self {
Self {
systems,
}
}
pub fn add_system(&mut self, system_label: &str, system: Box<SystemFn>) {
self.systems.insert(system_label.to_string(), system);
}
pub fn remove_system(&mut self, system_label: &str) {
self.systems.remove(&system_label.to_string());
}
}
impl SimpleSystem for SystemFnExecutor {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
for system in self.systems.iter() { self(world)
let system_fn = system.1.as_ref();
let res = system_fn(world);
// log an error returned by the system
if let Err(e) = res {
warn!("System execution of {} resulted in an error! '{}'", system.0, e);
} }
}
/// A system that executes a batch of systems in order that they were given.
pub struct BatchedSystem {
systems: Vec<Box<dyn SimpleSystem>>,
}
impl BatchedSystem {
/// Create a new BatchedSystems. The systems will be executed in the order given.
pub fn new(systems: Vec<Box<dyn SimpleSystem>>) -> Self {
Self {
systems
}
}
}
impl SimpleSystem for BatchedSystem {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
for system in self.systems.iter_mut() {
system.execute_mut(world)?;
} }
Ok(()) Ok(())
@ -67,12 +59,16 @@ struct SystemGraphEdge {
} }
pub struct SystemGraphExecutor { /// Dispatcher for multiple systems.
///
/// This struct uses a graph for finding executing systems with dependencies.
/// At some point this will be multithreaded.
pub struct SystemDispatcher {
graph: StableDiGraph<SystemGraphNode, SystemGraphEdge>, graph: StableDiGraph<SystemGraphNode, SystemGraphEdge>,
node_refs: HashMap<String, NodeIndex>, node_refs: HashMap<String, NodeIndex>,
} }
impl Default for SystemGraphExecutor { impl Default for SystemDispatcher {
fn default() -> Self { fn default() -> Self {
Self { Self {
graph: StableDiGraph::new(), graph: StableDiGraph::new(),
@ -81,7 +77,7 @@ impl Default for SystemGraphExecutor {
} }
} }
impl SystemGraphExecutor { impl SystemDispatcher {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
@ -92,11 +88,11 @@ impl SystemGraphExecutor {
/// and will cause a panic later on in the engine. /// and will cause a panic later on in the engine.
/// ///
/// TODO: Return false if a cycle was created, making it impossible to know when to dispatch them. This will mean that the System would not of had been added. /// TODO: Return false if a cycle was created, making it impossible to know when to dispatch them. This will mean that the System would not of had been added.
pub fn add_system(&mut self, name: String, system: Box<dyn SimpleSystem>, dependencies: &[String]) -> bool { pub fn add_system(&mut self, name: String, system: Box<dyn SimpleSystem>, dependencies: Vec<String>) -> bool {
let added_index = let added_index =
self.graph.add_node(SystemGraphNode { self.graph.add_node(SystemGraphNode {
name: name.clone(), name: name.clone(),
dependent_on: dependencies.clone().to_vec(), dependent_on: dependencies.clone(),
system, system,
}); });
@ -106,7 +102,7 @@ impl SystemGraphExecutor {
// find the dependencies in node_refs and add edges directed towards the new node // find the dependencies in node_refs and add edges directed towards the new node
for depend in dependencies.into_iter() { for depend in dependencies.into_iter() {
let idx = self.node_refs let idx = self.node_refs
.get(depend) .get(&depend)
.expect("Dependency not found!"); .expect("Dependency not found!");
self.graph.add_edge(idx.clone(), added_index, SystemGraphEdge { }); self.graph.add_edge(idx.clone(), added_index, SystemGraphEdge { });
@ -115,7 +111,7 @@ impl SystemGraphExecutor {
true true
} }
pub(crate) fn execute_mut(&mut self, world: &mut World) { pub(crate) fn execute_systems(&mut self, world: &mut World) {
let mut topo = Topo::new(&self.graph); let mut topo = Topo::new(&self.graph);
while let Some(nx) = topo.next(&self.graph) { while let Some(nx) = topo.next(&self.graph) {
@ -125,6 +121,10 @@ impl SystemGraphExecutor {
Ok(()) => {}, Ok(()) => {},
Err(e) => { Err(e) => {
warn!("System execution of {} resulted in an error! '{}'.", node.name, e); warn!("System execution of {} resulted in an error! '{}'.", node.name, e);
// TODO: Find some way to stop traversing down a topopath in the graph executor and continue down a different one.
// This might require a custom topopath implementation (preferably iterative). It would have to ensure that it
// doesn't run other systems that are dependent on that failed system.
return; return;
} }
} }
@ -132,9 +132,9 @@ impl SystemGraphExecutor {
} }
} }
impl SimpleSystem for SystemGraphExecutor { impl SimpleSystem for SystemDispatcher {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
SystemGraphExecutor::execute_mut(self, world); self.execute_systems(world);
Ok(()) Ok(())
} }

View File

@ -4,7 +4,7 @@ use async_std::{task::block_on, sync::Mutex};
use hecs::World; use hecs::World;
use instant::Instant; use instant::Instant;
use tracing::{metadata::LevelFilter, info, debug, warn}; use tracing::{metadata::LevelFilter, info, debug, warn, error};
use tracing_subscriber::{ use tracing_subscriber::{
layer::{Layer, SubscriberExt}, layer::{Layer, SubscriberExt},
filter::FilterFn, filter::FilterFn,
@ -13,7 +13,7 @@ use tracing_subscriber::{
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}}; use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}};
use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::{components::{mesh::MeshComponent, transform::TransformComponent}, SystemFnExecutor, SimpleSystem, SystemGraphExecutor}}; use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::{components::{mesh::MeshComponent, transform::TransformComponent}, SimpleSystem, SystemDispatcher}};
struct TickCounter { struct TickCounter {
counter: u32, counter: u32,
@ -76,20 +76,20 @@ struct GameLoop {
world: Arc<Mutex<World>>, world: Arc<Mutex<World>>,
/// higher priority systems /// higher priority systems
engine_systems: Vec<Box<dyn SimpleSystem>>, engine_sys_dispatcher: SystemDispatcher,
systems: Vec<Box<dyn SimpleSystem>>, user_sys_dispatcher: SystemDispatcher,
fps_counter: TickCounter, fps_counter: TickCounter,
} }
impl GameLoop { impl GameLoop {
pub async fn new(window: Arc<Window>, world: Arc<Mutex<World>>, user_systems: Vec<Box<dyn SimpleSystem>>) -> GameLoop { pub async fn new(window: Arc<Window>, world: Arc<Mutex<World>>, user_systems: SystemDispatcher) -> GameLoop {
Self { Self {
window: Arc::clone(&window), window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(window).await), renderer: Box::new(BasicRenderer::create_with_window(window).await),
world, world,
engine_systems: vec![], engine_sys_dispatcher: SystemDispatcher::new(),
systems: user_systems, user_sys_dispatcher: user_systems,
fps_counter: TickCounter::new(), fps_counter: TickCounter::new(),
} }
} }
@ -105,13 +105,20 @@ impl GameLoop {
async fn update(&mut self) { async fn update(&mut self) {
let mut world = self.world.lock().await; let mut world = self.world.lock().await;
for esystem in self.engine_systems.iter_mut() { if let Err(e) = self.engine_sys_dispatcher.execute_mut(&mut world) {
error!("Error when executing engine ecs systems: '{}'", e);
}
if let Err(e) = self.user_sys_dispatcher.execute_mut(&mut world) {
error!("Error when executing user ecs systems: '{}'", e);
}
/* for esystem in self.engine_systems.iter_mut() {
esystem.execute_mut(&mut world).unwrap(); esystem.execute_mut(&mut world).unwrap();
} }
for system in self.systems.iter_mut() { for system in self.systems.iter_mut() {
system.execute_mut(&mut world).unwrap(); system.execute_mut(&mut world).unwrap();
} } */
} }
async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> { async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> {
@ -218,14 +225,14 @@ impl GameLoop {
pub struct Game { pub struct Game {
world: Option<Arc<Mutex<World>>>, world: Option<Arc<Mutex<World>>>,
systems: Option<Vec<Box<dyn SimpleSystem>>> system_dispatcher: Option<SystemDispatcher>
} }
impl Default for Game { impl Default for Game {
fn default() -> Self { fn default() -> Self {
Self { Self {
world: None, world: None,
systems: Some(vec![]), system_dispatcher: Some(SystemDispatcher::new()),
} }
} }
} }
@ -259,12 +266,18 @@ impl Game {
self self
} }
pub fn with_system<S>(&mut self, system: S) -> &mut Self pub fn with_system<S>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where where
S: SimpleSystem + 'static S: SimpleSystem + 'static
{ {
let systems = self.systems.as_mut().unwrap();
systems.push(Box::new(system)); let depends: Vec<String> = depends
.iter()
.map(|s| s.to_string())
.collect();
let dispatcher = self.system_dispatcher.as_mut().unwrap();
dispatcher.add_system(name.to_string(), Box::new(system), depends);
self self
} }
@ -275,7 +288,7 @@ impl Game {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
let systems = self.systems.take().unwrap(); let systems = self.system_dispatcher.take().unwrap();
let mut g_loop = GameLoop::new(Arc::clone(&window), world, systems).await; let mut g_loop = GameLoop::new(Arc::clone(&window), world, systems).await;

View File

@ -12,12 +12,10 @@ use game::Game;
use hecs::World; use hecs::World;
use tracing::debug; use tracing::debug;
use crate::ecs::SystemFnExecutor;
use crate::render::material::Material; use crate::render::material::Material;
use crate::render::texture::Texture; use crate::render::texture::Texture;
use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::camera::CameraComponent;
use crate::math::{Angle, Transform}; use crate::math::{Angle, Transform};
//use specs::*;
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Point2d { struct Point2d {
@ -101,32 +99,8 @@ async fn main() {
Ok(()) Ok(())
}; };
let mut fn_exec = SystemFnExecutor::new();
fn_exec.add_system("jiggle", Box::new(jiggle_system));
Game::initialize().await Game::initialize().await
.with_world(world) .with_world(world)
.with_system(fn_exec) .with_system("jiggle", jiggle_system, &[])
.run().await; .run().await;
/* world.register::<Point2d>();
world.register::<Point3d>();
world.create_entity()
.with(Point2d::new(10, 10))
.with(Point3d::new(50, 50, 50))
.build();
let mut dispatcher = DispatcherBuilder::new().with(TestingSystem, "testing_system", &[]).build();
dispatcher.setup(&mut world);
//dispatcher.dispatch(&mut world);
Game::initialize().await
.with_world(world)
.with_dispatcher(dispatcher)
.run().await; */
/* let mut game = Game::initialize().await;
game.run().await; */
} }