Add system criteria
ci/woodpecker/push/build Pipeline was successful Details

This commit is contained in:
SeanOMik 2023-10-29 17:54:04 -04:00
parent 927566ca3d
commit 6b935739ef
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
7 changed files with 308 additions and 256 deletions

View File

@ -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, &[]);
} }
} }

View File

@ -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;
} }

View File

@ -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, &[]);
} }
} }

View File

@ -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(())
}
}

75
src/ecs/system/batched.rs Normal file
View File

@ -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(())
}
}

View File

@ -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)
}
}

127
src/ecs/system/mod.rs Normal file
View File

@ -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(())
}
}