Add system criteria
ci/woodpecker/push/build Pipeline was successful
Details
ci/woodpecker/push/build Pipeline was successful
Details
This commit is contained in:
parent
927566ca3d
commit
6b935739ef
|
@ -39,9 +39,9 @@ impl FreeFlyCamera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FreeFlyCameraController;
|
pub struct FreeFlyCameraPlugin;
|
||||||
|
|
||||||
impl SimpleSystem for FreeFlyCameraController {
|
impl SimpleSystem for FreeFlyCameraPlugin {
|
||||||
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||||
let mut camera_rot = Vec3::default();
|
let mut camera_rot = Vec3::default();
|
||||||
|
|
||||||
|
@ -147,8 +147,8 @@ impl SimpleSystem for FreeFlyCameraController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for FreeFlyCameraController {
|
impl Plugin for FreeFlyCameraPlugin {
|
||||||
fn setup(&self, game: &mut Game) {
|
fn setup(&self, game: &mut Game) {
|
||||||
game.with_system("free_fly_camera_controller", FreeFlyCameraController, &[]);
|
game.with_system("free_fly_camera_system", FreeFlyCameraPlugin, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,60 @@
|
||||||
use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent}, EventQueue, SimpleSystem, Component}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct};
|
use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct};
|
||||||
use lyra_engine::assets::{ResourceManager, Model};
|
use lyra_engine::assets::{ResourceManager, Model};
|
||||||
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
mod free_fly_camera;
|
mod free_fly_camera;
|
||||||
use free_fly_camera::{FreeFlyCameraController, FreeFlyCamera};
|
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
|
||||||
|
|
||||||
/* pub const VERTICES: &[Vertex] = &[
|
struct FixedTimestep {
|
||||||
Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A
|
max_tps: u32,
|
||||||
Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B
|
fixed_time: f32,
|
||||||
Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C
|
accumulator: f32,
|
||||||
Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D
|
}
|
||||||
Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E
|
|
||||||
]; */
|
|
||||||
|
|
||||||
pub const INDICES: &[u16] = &[
|
impl FixedTimestep {
|
||||||
0, 1, 4,
|
pub fn new(max_tps: u32) -> Self {
|
||||||
1, 2, 4,
|
Self {
|
||||||
2, 3, 4,
|
max_tps,
|
||||||
];
|
fixed_time: Self::calc_fixed_time(max_tps),
|
||||||
|
accumulator: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_fixed_time(max_tps: u32) -> f32 {
|
||||||
|
1.0 / max_tps as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tps(&mut self, tps: u32) {
|
||||||
|
self.max_tps = tps;
|
||||||
|
self.fixed_time = Self::calc_fixed_time(tps);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tps(&self) -> u32 {
|
||||||
|
self.max_tps
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_time(&self) -> f32 {
|
||||||
|
self.fixed_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Criteria for FixedTimestep {
|
||||||
|
fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule {
|
||||||
|
if check_count == 0 {
|
||||||
|
let delta_time = world.get_resource::<DeltaTime>().unwrap();
|
||||||
|
self.accumulator += **delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.accumulator >= self.fixed_time {
|
||||||
|
self.accumulator -= self.fixed_time;
|
||||||
|
return CriteriaSchedule::YesAndLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
CriteriaSchedule::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TpsAccumulator(f32);
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -54,111 +90,42 @@ async fn main() {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
/* let fps_system = |world: &mut World| -> anyhow::Result<()> {
|
let fps_system = |world: &mut World| -> anyhow::Result<()> {
|
||||||
let mut counter: RefMut<fps_counter::FPSCounter> = world.get_resource_mut().unwrap();
|
let mut counter = world.get_resource_mut::<fps_counter::FPSCounter>().unwrap();
|
||||||
|
|
||||||
let fps = counter.tick();
|
let fps = counter.tick();
|
||||||
|
|
||||||
debug!("FPS: {fps}");
|
println!("FPS: {fps}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let fps_plugin = move |game: &mut Game| {
|
let fps_plugin = move |game: &mut Game| {
|
||||||
let world = game.world();
|
let world = game.world();
|
||||||
world.insert_resource(fps_counter::FPSCounter::new());
|
world.insert_resource(fps_counter::FPSCounter::new());
|
||||||
|
};
|
||||||
|
|
||||||
game.with_system("fps", fps_system, &["input"]);
|
let spin_system = |world: &mut World| -> anyhow::Result<()> {
|
||||||
}; */
|
const SPEED: f32 = 5.0;
|
||||||
|
let delta_time = **world.get_resource::<DeltaTime>().unwrap();
|
||||||
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
|
|
||||||
let keys = world.get_resource::<InputButtons<KeyCode>>();
|
|
||||||
if keys.is_none() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let keys = keys.unwrap();
|
|
||||||
|
|
||||||
let speed = 0.01;
|
|
||||||
let rot_speed = 1.0;
|
|
||||||
|
|
||||||
let mut dir_x = 0.0;
|
|
||||||
let mut dir_y = 0.0;
|
|
||||||
let mut dir_z = 0.0;
|
|
||||||
|
|
||||||
let mut rot_x = 0.0;
|
|
||||||
let mut rot_y = 0.0;
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::A) {
|
|
||||||
dir_x += speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::D) {
|
|
||||||
dir_x -= speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::S) {
|
|
||||||
dir_y += speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::W) {
|
|
||||||
dir_y -= speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::E) {
|
|
||||||
dir_z += speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::Q) {
|
|
||||||
dir_z -= speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::Left) {
|
|
||||||
rot_y -= rot_speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::Right) {
|
|
||||||
rot_y += rot_speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::Up) {
|
|
||||||
rot_x -= rot_speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keys.is_pressed(KeyCode::Down) {
|
|
||||||
rot_x += rot_speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(keys);
|
|
||||||
|
|
||||||
if dir_x == 0.0 && dir_y == 0.0 && dir_z == 0.0 && rot_x == 0.0 && rot_y == 0.0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
//debug!("moving by ({}, {})", dir_x, dir_y);
|
|
||||||
|
|
||||||
for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() {
|
for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() {
|
||||||
let t = &mut transform.transform;
|
let t = &mut transform.transform;
|
||||||
//debug!("Translation: {}", t.translation);
|
t.rotate_y(math::Angle::Degrees(SPEED * delta_time));
|
||||||
//debug!("Rotation: {}", t.rotation);
|
|
||||||
|
|
||||||
/* t.translation += glam::Vec3::new(0.0, 0.001, 0.0);
|
|
||||||
t.translation.x *= -1.0; */
|
|
||||||
t.translation.x += dir_x;
|
|
||||||
t.translation.y += dir_y;
|
|
||||||
t.translation.z += dir_z;
|
|
||||||
t.rotate_x(math::Angle::Degrees(rot_x));
|
|
||||||
t.rotate_y(math::Angle::Degrees(rot_y));
|
|
||||||
}
|
|
||||||
|
|
||||||
let events = world.get_resource_mut::<EventQueue>().unwrap();
|
|
||||||
if let Some(mm) = events.read_events::<MouseMotion>() {
|
|
||||||
debug!("Mouse motion: {:?}", mm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let jiggle_plugin = move |game: &mut Game| {
|
let jiggle_plugin = move |game: &mut Game| {
|
||||||
//game.with_system("jiggle", jiggle_system, &["input"]);
|
game.world().insert_resource(TpsAccumulator(0.0));
|
||||||
|
|
||||||
|
let mut sys = BatchedSystem::new();
|
||||||
|
sys.with_criteria(FixedTimestep::new(60));
|
||||||
|
sys.with_system(spin_system);
|
||||||
|
sys.with_system(fps_system);
|
||||||
|
|
||||||
|
game.with_system("fixed", sys, &[]);
|
||||||
|
fps_plugin(game);
|
||||||
};
|
};
|
||||||
|
|
||||||
Game::initialize().await
|
Game::initialize().await
|
||||||
|
@ -166,6 +133,6 @@ async fn main() {
|
||||||
.with_startup_system(setup_sys)
|
.with_startup_system(setup_sys)
|
||||||
//.with_plugin(fps_plugin)
|
//.with_plugin(fps_plugin)
|
||||||
.with_plugin(jiggle_plugin)
|
.with_plugin(jiggle_plugin)
|
||||||
.with_plugin(FreeFlyCameraController)
|
.with_plugin(FreeFlyCameraPlugin)
|
||||||
.run().await;
|
.run().await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use instant::Instant;
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
|
||||||
#[derive(Clone, Component)]
|
#[derive(Clone, Component)]
|
||||||
pub struct DeltaTime(f32, Instant);
|
pub struct DeltaTime(f32, Option<Instant>);
|
||||||
|
|
||||||
impl std::ops::Deref for DeltaTime {
|
impl std::ops::Deref for DeltaTime {
|
||||||
type Target = f32;
|
type Target = f32;
|
||||||
|
@ -25,8 +25,10 @@ impl std::ops::DerefMut for DeltaTime {
|
||||||
fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
|
fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let mut delta = world.get_resource_mut::<DeltaTime>().unwrap();
|
let mut delta = world.get_resource_mut::<DeltaTime>().unwrap();
|
||||||
delta.0 = delta.1.elapsed().as_secs_f32();
|
delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32();
|
||||||
delta.1 = now;
|
delta.1 = Some(now);
|
||||||
|
|
||||||
|
//println!("delta: {}", delta.0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -35,7 +37,7 @@ pub struct DeltaTimePlugin;
|
||||||
|
|
||||||
impl Plugin for DeltaTimePlugin {
|
impl Plugin for DeltaTimePlugin {
|
||||||
fn setup(&self, game: &mut crate::game::Game) {
|
fn setup(&self, game: &mut crate::game::Game) {
|
||||||
game.world().insert_resource(DeltaTime(0.0, Instant::now()));
|
game.world().insert_resource(DeltaTime(0.0, None));
|
||||||
game.with_system("delta_time", delta_time_system, &[]);
|
game.with_system("delta_time", delta_time_system, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
148
src/ecs/mod.rs
148
src/ecs/mod.rs
|
@ -1,151 +1,9 @@
|
||||||
pub use edict::*;
|
pub use edict::*;
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
pub mod system;
|
||||||
|
pub use system::*;
|
||||||
use petgraph::{stable_graph::{StableDiGraph, NodeIndex}, visit::Topo};
|
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
/// 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<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> SimpleSystem for S
|
|
||||||
where S: FnMut(&mut edict::World) -> anyhow::Result<()>
|
|
||||||
{
|
|
||||||
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
|
||||||
self(world)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SystemGraphNode {
|
|
||||||
name: String,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
dependent_on: Vec<String>, // TODO
|
|
||||||
system: Box<dyn SimpleSystem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SystemGraphEdge {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 SystemDispatcher {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
graph: StableDiGraph::new(),
|
|
||||||
node_refs: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemDispatcher {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a SimpleSystem to the SystemGraph.
|
|
||||||
///
|
|
||||||
/// WARN: Ensure that no cycles are created, this will cause the systems graph to not be sortable by topological sort,
|
|
||||||
/// 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<S>(&mut self, name: &str, system: S, dependencies: &[&str]) -> bool
|
|
||||||
where
|
|
||||||
S: SimpleSystem + 'static
|
|
||||||
{
|
|
||||||
let name = name.to_string();
|
|
||||||
let dependencies: Vec<String> = dependencies.iter().map(|s| s.to_string()).collect();
|
|
||||||
let system = Box::new(system) as Box<dyn SimpleSystem>;
|
|
||||||
|
|
||||||
let added_index =
|
|
||||||
self.graph.add_node(SystemGraphNode {
|
|
||||||
name: name.to_string(),
|
|
||||||
dependent_on: dependencies.clone(),
|
|
||||||
system,
|
|
||||||
});
|
|
||||||
|
|
||||||
// store name ref to the graph node
|
|
||||||
self.node_refs.insert(name, added_index);
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
.expect("Dependency not found!");
|
|
||||||
|
|
||||||
self.graph.add_edge(*idx, added_index, SystemGraphEdge { });
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
let node = self.graph.node_weight_mut(nx).unwrap();
|
|
||||||
|
|
||||||
match node.system.execute_mut(world) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleSystem for SystemDispatcher {
|
|
||||||
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
|
||||||
self.execute_systems(world);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
use super::{SimpleSystem, Criteria};
|
||||||
|
use edict::World;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// executed.
|
||||||
|
pub struct BatchedSystem {
|
||||||
|
systems: Vec<Box<dyn SimpleSystem>>,
|
||||||
|
criteria: Vec<Box<dyn Criteria>>,
|
||||||
|
criteria_checks: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BatchedSystem {
|
||||||
|
/// Create a new BatchedSystem
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
systems: Vec::new(),
|
||||||
|
criteria: Vec::new(),
|
||||||
|
criteria_checks: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_system<S>(&mut self, system: S) -> &mut Self
|
||||||
|
where
|
||||||
|
S: SimpleSystem + 'static
|
||||||
|
{
|
||||||
|
self.systems.push(Box::new(system));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_criteria<C>(&mut self, criteria: C) -> &mut Self
|
||||||
|
where
|
||||||
|
C: Criteria + 'static
|
||||||
|
{
|
||||||
|
self.criteria.push(Box::new(criteria));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleSystem for BatchedSystem {
|
||||||
|
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||||
|
let mut can_run = true;
|
||||||
|
let mut check_again = false;
|
||||||
|
|
||||||
|
for criteria in self.criteria.iter_mut() {
|
||||||
|
match criteria.can_run(world, self.criteria_checks) {
|
||||||
|
super::CriteriaSchedule::Yes => can_run = can_run && true,
|
||||||
|
super::CriteriaSchedule::No => can_run = false,
|
||||||
|
super::CriteriaSchedule::YesAndLoop => {
|
||||||
|
can_run = can_run && true;
|
||||||
|
check_again = can_run && true;
|
||||||
|
},
|
||||||
|
super::CriteriaSchedule::NoAndLoop => {
|
||||||
|
can_run = false;
|
||||||
|
check_again = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if can_run {
|
||||||
|
for system in self.systems.iter_mut() {
|
||||||
|
system.execute_mut(world)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_again {
|
||||||
|
self.criteria_checks += 1;
|
||||||
|
self.execute_mut(world)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.criteria_checks = 0;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
pub enum CriteriaSchedule {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
YesAndLoop,
|
||||||
|
NoAndLoop,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Criteria {
|
||||||
|
/// Checks if this Criteria can run, and if it should check it again.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `world` - The ecs world.
|
||||||
|
/// * `check_count` - The amount of times the Criteria has been checked this tick.
|
||||||
|
fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Criteria for F
|
||||||
|
where F: FnMut(&mut edict::World, u32) -> CriteriaSchedule
|
||||||
|
{
|
||||||
|
fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule {
|
||||||
|
self(world, check_count)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
pub mod batched;
|
||||||
|
pub use batched::*;
|
||||||
|
|
||||||
|
pub mod criteria;
|
||||||
|
pub use criteria::*;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use edict::World;
|
||||||
|
use petgraph::{stable_graph::{StableDiGraph, NodeIndex}, visit::Topo};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
/// 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<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> SimpleSystem for S
|
||||||
|
where S: FnMut(&mut edict::World) -> anyhow::Result<()>
|
||||||
|
{
|
||||||
|
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||||
|
self(world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SystemGraphNode {
|
||||||
|
name: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
dependent_on: Vec<String>, // TODO
|
||||||
|
system: Box<dyn SimpleSystem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SystemGraphEdge {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 SystemDispatcher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
graph: StableDiGraph::new(),
|
||||||
|
node_refs: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemDispatcher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a SimpleSystem to the SystemGraph.
|
||||||
|
///
|
||||||
|
/// WARN: Ensure that no cycles are created, this will cause the systems graph to not be sortable by topological sort,
|
||||||
|
/// 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<S>(&mut self, name: &str, system: S, dependencies: &[&str]) -> bool
|
||||||
|
where
|
||||||
|
S: SimpleSystem + 'static
|
||||||
|
{
|
||||||
|
let name = name.to_string();
|
||||||
|
let dependencies: Vec<String> = dependencies.iter().map(|s| s.to_string()).collect();
|
||||||
|
let system = Box::new(system) as Box<dyn SimpleSystem>;
|
||||||
|
|
||||||
|
let added_index =
|
||||||
|
self.graph.add_node(SystemGraphNode {
|
||||||
|
name: name.to_string(),
|
||||||
|
dependent_on: dependencies.clone(),
|
||||||
|
system,
|
||||||
|
});
|
||||||
|
|
||||||
|
// store name ref to the graph node
|
||||||
|
self.node_refs.insert(name, added_index);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
.expect("Dependency not found!");
|
||||||
|
|
||||||
|
self.graph.add_edge(*idx, added_index, SystemGraphEdge { });
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let node = self.graph.node_weight_mut(nx).unwrap();
|
||||||
|
|
||||||
|
match node.system.execute_mut(world) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleSystem for SystemDispatcher {
|
||||||
|
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||||
|
self.execute_systems(world);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue