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
pub trait SimpleSystem {
fn setup(&mut self, world: &mut World) -> anyhow::Result<()> {
Ok(())
}
// todo: make async?
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>;
}
pub type SystemFn = dyn Fn(&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 {
impl<S> SimpleSystem for S
where S: FnMut(&mut World) -> anyhow::Result<()>
{
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
for system in self.systems.iter() {
let system_fn = system.1.as_ref();
let res = system_fn(world);
self(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(())
@ -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>,
node_refs: HashMap<String, NodeIndex>,
}
impl Default for SystemGraphExecutor {
impl Default for SystemDispatcher {
fn default() -> Self {
Self {
graph: StableDiGraph::new(),
@ -81,7 +77,7 @@ impl Default for SystemGraphExecutor {
}
}
impl SystemGraphExecutor {
impl SystemDispatcher {
pub fn new() -> Self {
Self::default()
}
@ -92,11 +88,11 @@ impl SystemGraphExecutor {
/// 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.
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 =
self.graph.add_node(SystemGraphNode {
name: name.clone(),
dependent_on: dependencies.clone().to_vec(),
dependent_on: dependencies.clone(),
system,
});
@ -106,7 +102,7 @@ impl SystemGraphExecutor {
// find the dependencies in node_refs and add edges directed towards the new node
for depend in dependencies.into_iter() {
let idx = self.node_refs
.get(depend)
.get(&depend)
.expect("Dependency not found!");
self.graph.add_edge(idx.clone(), added_index, SystemGraphEdge { });
@ -115,7 +111,7 @@ impl SystemGraphExecutor {
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);
while let Some(nx) = topo.next(&self.graph) {
@ -125,6 +121,10 @@ impl SystemGraphExecutor {
Ok(()) => {},
Err(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;
}
}
@ -132,9 +132,9 @@ impl SystemGraphExecutor {
}
}
impl SimpleSystem for SystemGraphExecutor {
impl SimpleSystem for SystemDispatcher {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
SystemGraphExecutor::execute_mut(self, world);
self.execute_systems(world);
Ok(())
}

View File

@ -4,7 +4,7 @@ use async_std::{task::block_on, sync::Mutex};
use hecs::World;
use instant::Instant;
use tracing::{metadata::LevelFilter, info, debug, warn};
use tracing::{metadata::LevelFilter, info, debug, warn, error};
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
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 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 {
counter: u32,
@ -76,20 +76,20 @@ struct GameLoop {
world: Arc<Mutex<World>>,
/// higher priority systems
engine_systems: Vec<Box<dyn SimpleSystem>>,
systems: Vec<Box<dyn SimpleSystem>>,
engine_sys_dispatcher: SystemDispatcher,
user_sys_dispatcher: SystemDispatcher,
fps_counter: TickCounter,
}
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 {
window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(window).await),
world,
engine_systems: vec![],
systems: user_systems,
engine_sys_dispatcher: SystemDispatcher::new(),
user_sys_dispatcher: user_systems,
fps_counter: TickCounter::new(),
}
}
@ -105,13 +105,20 @@ impl GameLoop {
async fn update(&mut self) {
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();
}
for system in self.systems.iter_mut() {
system.execute_mut(&mut world).unwrap();
}
} */
}
async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> {
@ -218,14 +225,14 @@ impl GameLoop {
pub struct Game {
world: Option<Arc<Mutex<World>>>,
systems: Option<Vec<Box<dyn SimpleSystem>>>
system_dispatcher: Option<SystemDispatcher>
}
impl Default for Game {
fn default() -> Self {
Self {
world: None,
systems: Some(vec![]),
system_dispatcher: Some(SystemDispatcher::new()),
}
}
}
@ -259,12 +266,18 @@ impl Game {
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
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
}
@ -275,7 +288,7 @@ impl Game {
let event_loop = EventLoop::new();
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;

View File

@ -12,12 +12,10 @@ use game::Game;
use hecs::World;
use tracing::debug;
use crate::ecs::SystemFnExecutor;
use crate::render::material::Material;
use crate::render::texture::Texture;
use crate::ecs::components::camera::CameraComponent;
use crate::math::{Angle, Transform};
//use specs::*;
#[derive(Debug, Default)]
struct Point2d {
@ -100,33 +98,9 @@ async fn main() {
Ok(())
};
let mut fn_exec = SystemFnExecutor::new();
fn_exec.add_system("jiggle", Box::new(jiggle_system));
Game::initialize().await
.with_world(world)
.with_system(fn_exec)
.with_system("jiggle", jiggle_system, &[])
.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; */
}