Add resource system, early input system, custom hecs world wrapper

This commit is contained in:
SeanOMik 2023-07-11 01:11:35 -04:00
parent a47d5b00ef
commit f5bfa93f63
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
13 changed files with 988 additions and 569 deletions

1068
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,8 @@ tobj = { version = "3.2.1", features = [
]}
instant = "0.1"
async-trait = "0.1.65"
specs = { version = "0.18.0", features = [ "derive" ] }
hecs = "0.10.3"
glam = { version = "0.24.0", features = ["bytemuck"] }
petgraph = "0.6.3"
resources = "1.1.0"
gilrs-core = "0.5.6"

View File

@ -11,6 +11,7 @@ mkShell rec {
valgrind
heaptrack
mold
udev
];
buildInputs = [
udev alsa-lib vulkan-loader

View File

@ -1,3 +0,0 @@
pub struct Controls {
}

View File

@ -1,8 +1,6 @@
use specs::{Component, DenseVecStorage};
use crate::render::{vertex::Vertex, mesh::Mesh, material::Material};
#[derive(Component, Clone)]
#[derive(Clone)]
pub struct MeshComponent {
pub mesh: Mesh,
pub material: Material,

View File

@ -1,8 +1,6 @@
use specs::{Component, DenseVecStorage};
use crate::math::Transform;
#[derive(Component, Clone)]
#[derive(Clone)]
pub struct TransformComponent {
pub transform: Transform,
}

View File

@ -1,27 +1,31 @@
use std::collections::HashMap;
use async_trait::async_trait;
use hecs::World;
use petgraph::{prelude::StableDiGraph, stable_graph::NodeIndex, visit::Topo};
use tracing::warn;
use crate::game::Controls;
pub mod components;
pub mod world;
use world::World;
/// A trait that represents a simple system
pub trait SimpleSystem {
fn setup(&mut self, world: &mut World) -> anyhow::Result<()> {
fn setup(&mut self, controls: &mut Controls) -> anyhow::Result<()> {
Ok(())
}
// todo: make async?
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>;
fn execute_mut(&mut self, controls: &mut Controls) -> anyhow::Result<()>;
}
impl<S> SimpleSystem for S
where S: FnMut(&mut World) -> anyhow::Result<()>
{
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
self(world)
fn execute_mut(&mut self, controls: &mut Controls) -> anyhow::Result<()> {
self(controls.world)
}
}
@ -40,9 +44,9 @@ impl BatchedSystem {
}
impl SimpleSystem for BatchedSystem {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
fn execute_mut(&mut self, controls: &mut Controls) -> anyhow::Result<()> {
for system in self.systems.iter_mut() {
system.execute_mut(world)?;
system.execute_mut(controls)?;
}
Ok(())
@ -111,13 +115,13 @@ impl SystemDispatcher {
true
}
pub(crate) fn execute_systems(&mut self, world: &mut World) {
pub(crate) fn execute_systems(&mut self, controls: &mut Controls) {
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) {
match node.system.execute_mut(controls) {
Ok(()) => {},
Err(e) => {
warn!("System execution of {} resulted in an error! '{}'.", node.name, e);
@ -133,8 +137,8 @@ impl SystemDispatcher {
}
impl SimpleSystem for SystemDispatcher {
fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> {
self.execute_systems(world);
fn execute_mut(&mut self, controls: &mut Controls) -> anyhow::Result<()> {
self.execute_systems(controls);
Ok(())
}

120
src/ecs/world.rs Executable file
View File

@ -0,0 +1,120 @@
use std::{any::{TypeId, Any}, collections::{HashMap, HashSet, VecDeque}, ops::{Deref, DerefMut}};
use winit::event::WindowEvent;
pub trait Resource: Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl Resource for WindowEvent<'static> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl<T: Sync + Send + 'static> Resource for VecDeque<T> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub struct World {
inner: hecs::World,
resources: HashMap<TypeId, Box<dyn Resource>>,
updated_resources: HashSet<TypeId>,
}
impl Deref for World {
type Target = hecs::World;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for World {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[allow(dead_code)]
impl World {
pub fn new() -> Self {
Self::from_hecs(hecs::World::new())
}
pub fn from_hecs(hecs_world: hecs::World) -> Self {
Self {
inner: hecs_world,
resources: HashMap::new(),
updated_resources: HashSet::new(),
}
}
/// Insert a resource into the World. You can only have one resource of the same type.
/// If you attempt to add another resource of the same type, it will be replaced.
///
/// This will also mark the resource as updated in this frame.
pub fn insert_resource<R>(&mut self, res: R)
where
R: Resource
{
let type_id = res.type_id();
self.resources.insert(type_id, Box::new(res));
self.updated_resources.insert(type_id);
}
/// Checks if a resource was updated this frame.
pub fn was_res_updated<R>(&self) -> bool
where
R: Resource
{
self.updated_resources.contains(&TypeId::of::<R>())
}
pub(crate) fn clear_updated_resources(&mut self) {
self.updated_resources.clear();
}
/// Query a resource.
///
/// This is O(1), resources are stored in HashMaps.
pub fn query_res<R>(&self) -> Option<&R>
where
R: Resource
{
self.resources
.get(&TypeId::of::<R>())
.map(|r| r.as_any().downcast_ref())
.flatten()
}
/// Query a resource mutably.
///
/// This will mark the resource as changed, even if it actually wasn't modified when mutably borrowed.
/// This is O(1), resources are stored in HashMaps.
pub fn query_res_mut<R>(&mut self) -> Option<&mut R>
where
R: Resource
{
self.resources
.get_mut(&TypeId::of::<R>())
.map(|r| {
// update resource
self.updated_resources.insert(TypeId::of::<R>());
r.as_any_mut().downcast_mut()
})
.flatten()
}
}

View File

@ -1,9 +1,10 @@
use std::{sync::Arc, cell::RefCell};
use std::{sync::Arc, cell::RefCell, borrow::Borrow, collections::VecDeque};
use async_std::{task::block_on, sync::Mutex};
use hecs::World;
//use hecs::World;
use instant::Instant;
use resources::{Resources, Resource};
use tracing::{metadata::LevelFilter, info, debug, warn, error};
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
@ -13,7 +14,12 @@ 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}, SimpleSystem, SystemDispatcher}};
use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::{components::{mesh::MeshComponent, transform::TransformComponent}, SimpleSystem, SystemDispatcher, world::World}, input::Input};
pub struct Controls<'a> {
pub world: &'a mut World,
pub resources: &'a mut Resources,
}
struct TickCounter {
counter: u32,
@ -75,6 +81,7 @@ struct GameLoop {
renderer: Box<dyn Renderer>,
world: Arc<Mutex<World>>,
resources: Arc<Mutex<Resources>>,
/// higher priority systems
engine_sys_dispatcher: SystemDispatcher,
user_sys_dispatcher: SystemDispatcher,
@ -82,12 +89,13 @@ struct GameLoop {
}
impl GameLoop {
pub async fn new(window: Arc<Window>, world: Arc<Mutex<World>>, user_systems: SystemDispatcher) -> GameLoop {
pub async fn new(window: Arc<Window>, world: Arc<Mutex<World>>, resources: Arc<Mutex<Resources>>, user_systems: SystemDispatcher) -> GameLoop {
Self {
window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(window).await),
world,
resources,
engine_sys_dispatcher: SystemDispatcher::new(),
user_sys_dispatcher: user_systems,
fps_counter: TickCounter::new(),
@ -104,21 +112,20 @@ impl GameLoop {
async fn update(&mut self) {
let mut world = self.world.lock().await;
let mut resources = self.resources.lock().await;
if let Err(e) = self.engine_sys_dispatcher.execute_mut(&mut world) {
let mut controls = Controls {
world: &mut world,
resources: &mut resources
};
if let Err(e) = self.engine_sys_dispatcher.execute_mut(&mut controls) {
error!("Error when executing engine ecs systems: '{}'", e);
}
if let Err(e) = self.user_sys_dispatcher.execute_mut(&mut world) {
if let Err(e) = self.user_sys_dispatcher.execute_mut(&mut controls) {
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> {
@ -138,7 +145,7 @@ impl GameLoop {
},
_ => {
debug!("Got unhandled input event: \"{:?}\"", event);
//debug!("Got unhandled input event: \"{:?}\"", event);
None
}
@ -164,9 +171,27 @@ impl GameLoop {
ref event,
window_id,
} if window_id == self.window.id() => {
// If try_from failed, that means that the WindowEvent is not an
// input related event.
if let Ok(input_event) = InputEvent::try_from(event) {
// Its possible to receive multiple input events before the update event for
// the InputSystem is called, so we must use a queue for the events.
{
let mut world = self.world.lock().await;
let event_queue = match world.query_res_mut::<VecDeque<InputEvent>>() {
Some(i) => i,
None => {
world.insert_resource(VecDeque::<InputEvent>::new());
// must succeed since it was just added
world.query_res_mut::<VecDeque<InputEvent>>().unwrap()
}
};
event_queue.push_back(input_event.clone());
}
if let Some(new_control) = self.input_update(&input_event).await {
*control_flow = new_control;
}
@ -202,6 +227,7 @@ impl GameLoop {
let mut world = self.world.lock().await;
self.renderer.as_mut().prepare(&mut world).await;
world.clear_updated_resources();
drop(world);
match self.renderer.as_mut().render().await {
@ -225,6 +251,7 @@ impl GameLoop {
pub struct Game {
world: Option<Arc<Mutex<World>>>,
resources: Option<Arc<Mutex<Resources>>>,
system_dispatcher: Option<SystemDispatcher>
}
@ -232,6 +259,7 @@ impl Default for Game {
fn default() -> Self {
Self {
world: None,
resources: Some(Arc::new(Mutex::new(Resources::new()))),
system_dispatcher: Some(SystemDispatcher::new()),
}
}
@ -282,15 +310,30 @@ impl Game {
self
}
pub async fn with_res<T>(&mut self, r: T) -> &mut Self
where
T: Resource
{
let resources = self.resources.as_mut().unwrap();
let mut resources = resources.lock().await;
resources.insert(r);
drop(resources);
self
}
pub async fn run(&mut self) {
let world = self.world.take().expect("ECS World was never given to Game!");
let resources = self.resources.take().unwrap();
let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).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, resources, systems).await;
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);

159
src/input.rs Executable file
View File

@ -0,0 +1,159 @@
use std::{any::Any, collections::{HashMap, hash_map::DefaultHasher, VecDeque}, hash::{Hash, Hasher}, sync::{Arc, Mutex}};
use gilrs_core::Gilrs;
use tracing::warn;
use winit::event::{VirtualKeyCode, ElementState};
use crate::{ecs::{SimpleSystem, world::Resource}, input_event::InputEvent};
pub type KeyCode = winit::event::VirtualKeyCode;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ButtonEvent<T: Hash + 'static> {
Pressed(T),
Released(T),
JustPressed(T),
}
pub struct InputSystem;
impl InputSystem {
pub fn new() -> Self {
Self {}
}
}
impl SimpleSystem for InputSystem {
fn execute_mut(&mut self, controls: &mut crate::game::Controls) -> anyhow::Result<()> {
let world = &mut controls.world;
if let Some(queue) = world.query_res_mut::<VecDeque<InputEvent>>() {
let event = queue.pop_front();
if let Some(input) = world.query_res_mut::<Input>() {
input.update(event.as_ref());
}
}
Ok(())
}
}
pub struct Input {
gilrs: Option<Arc<Mutex<Gilrs>>>, // TODO
// the key is a u64. This is a hash of the value stored inside of the Box
events: HashMap<u64, Box<dyn Any + Send + Sync>>,
}
impl Resource for Input {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Input {
pub(crate) fn new() -> Self {
let gilrs = match Gilrs::new() {
Ok(g) => Some(Arc::new(Mutex::new(g))),
Err(e) => {
warn!("Failure to initialize gilrs, gamepads will not work!\n{}", e);
None
}
};
Self {
gilrs,
events: HashMap::new(),
}
}
pub fn update(&mut self, event: Option<&InputEvent>) -> bool {
// Convert JustPressed inputs to Pressed, and remove Released events
self.events.retain(|_, v| {
if let Some(ev) = v.downcast_mut::<ButtonEvent<KeyCode>>() {
match ev {
ButtonEvent::Released(_) => {
return false;
},
ButtonEvent::JustPressed(v) => {
*ev = ButtonEvent::Pressed(*v);
},
_ => {}
}
}
true
});
if let Some(event) = event {
match event {
InputEvent::KeyboardInput { input, .. } => {
if let Some(code) = input.virtual_keycode {
// Get a hash of the input code
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
let code_hash = hasher.finish();
// convert state
let buttonev = Box::new(match input.state {
ElementState::Pressed => ButtonEvent::JustPressed(code),
ElementState::Released => ButtonEvent::Released(code),
});
self.events.insert(code_hash, buttonev);
}
},
/* WindowEvent::CursorMoved { position, .. } => Some(EventType::PositionChanged(position.x, position.y, InputEventCode::CursorMoved)),
WindowEvent::CursorEntered { .. } => Some(EventType::Signal(InputEventCode::CursorEnteredWindow)),
WindowEvent::CursorLeft { .. } => Some(EventType::Signal(InputEventCode::CursorLeftWindow)), */
//WindowEvent::MouseWheel { delta, .. } => Some(EventType::AxisChanged(delta, InputEventCode::MouseWheel)),
/* WindowEvent::MouseWheel { .. } => Some(InputEventCode::MouseWheel),
WindowEvent::MouseInput { .. } => Some(InputEventCode::MouseInput),
WindowEvent::TouchpadMagnify { .. } => Some(InputEventCode::TouchpadMagnify),
WindowEvent::SmartMagnify { .. } => Some(InputEventCode::SmartMagnify),
WindowEvent::TouchpadRotate { .. } => Some(InputEventCode::TouchpadRotate),
WindowEvent::TouchpadPressure { .. } => Some(InputEventCode::TouchpadPressure),
WindowEvent::AxisMotion { .. } => Some(InputEventCode::AxisMotion),
WindowEvent::Touch { .. } => Some(InputEventCode::Touch), */
_ => {},
}
}
false
}
/// TODO: was_just_pressed
pub fn is_pressed(&self, code: VirtualKeyCode) -> bool {
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
let inner_hash = hasher.finish();
if let Some(e) = self.events.get(&inner_hash) {
if let Some(ev) = e.downcast_ref::<ButtonEvent<KeyCode>>() {
match ev {
ButtonEvent::Pressed(v) => {
if v.clone() == code {
return true;
}
},
ButtonEvent::JustPressed(v) => {
if v.clone() == code {
return true;
}
},
ButtonEvent::Released(_) => {
return false;
}
}
}
}
false
}
}

View File

@ -1,6 +1,22 @@
use winit::{event::{DeviceId, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, MouseButton, AxisId, Touch, WindowEvent, ElementState}, dpi::PhysicalPosition};
#[derive(Debug, PartialEq)]
use crate::ecs::world::Resource;
/// Wrapper around events from `winit::WindowEvent` that are specific to input related events.
///
/// The `winit::WindowEvent` enum has many values that are related to inputs.
/// Ex:
/// * winit::WindowEvent::KeyboardInput
/// * winit::WindowEvent::CursorMoved
/// * winit::WindowEvent::CursorEntered
/// * winit::WindowEvent::MouseWheel
/// * winit::WindowEvent::CursorLeft
/// * winit::WindowEvent::MouseInput
/// etc.
///
/// Its easier for these to all be in a single `InputEvent` type enum to check if any input was received.
/// The comments for all the methods were taken from `winit::WindowEvent`
#[derive(Debug, PartialEq, Clone)]
pub enum InputEvent {
/// An event from the keyboard has been received.
KeyboardInput {
@ -216,33 +232,12 @@ impl<'a> TryFrom<&'a WindowEvent<'a>> for InputEvent {
}
}
/* impl<'a> TryFrom<WindowEvent<'a>> for InputEvent {
type Error = InputEventConversionError<'a>;
fn try_from(value: WindowEvent<'a>) -> Result<Self, Self::Error> {
match value {
WindowEvent::KeyboardInput { device_id, input, is_synthetic } =>
Ok(InputEvent::KeyboardInput { device_id, input, is_synthetic }),
WindowEvent::CursorMoved { device_id, position, modifiers } =>
Ok(InputEvent::CursorMoved { device_id, position, modifiers }),
WindowEvent::CursorEntered { device_id } => Ok(InputEvent::CursorEntered { device_id }),
WindowEvent::CursorLeft { device_id } => Ok(InputEvent::CursorLeft { device_id }),
WindowEvent::MouseWheel { device_id, delta, phase, modifiers } =>
Ok(InputEvent::MouseWheel { device_id, delta, phase, modifiers }),
WindowEvent::MouseInput { device_id, state, button, modifiers } =>
Ok(InputEvent::MouseInput { device_id, state, button, modifiers }),
WindowEvent::TouchpadMagnify { device_id, delta, phase } =>
Ok(InputEvent::TouchpadMagnify { device_id, delta, phase }),
WindowEvent::SmartMagnify { device_id } => Ok(InputEvent::SmartMagnify { device_id }),
WindowEvent::TouchpadRotate { device_id, delta, phase } =>
Ok(InputEvent::TouchpadRotate { device_id, delta, phase }),
WindowEvent::TouchpadPressure { device_id, pressure, stage } =>
Ok(InputEvent::TouchpadPressure { device_id, pressure, stage }),
WindowEvent::AxisMotion { device_id, axis, value } =>
Ok(InputEvent::AxisMotion { device_id, axis, value }),
WindowEvent::Touch(t) => Ok(InputEvent::Touch(t)),
_ => Err(InputEventConversionError::FromError(value))
}
impl Resource for InputEvent {
fn as_any(&self) -> &dyn std::any::Any {
self
}
} */
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -4,14 +4,15 @@ mod input_event;
mod resources;
mod ecs;
mod math;
mod input;
use ecs::components::mesh::MeshComponent;
use ecs::components::transform::TransformComponent;
use game::Game;
use hecs::World;
use tracing::debug;
use crate::ecs::world::World;
use crate::input::{Input, KeyCode, InputSystem};
use crate::render::material::Material;
use crate::render::texture::Texture;
use crate::ecs::components::camera::CameraComponent;
@ -84,16 +85,44 @@ async fn main() {
camera.transform.translation += glam::Vec3::new(0.0, 0.0, 2.0);
//camera.transform.rotate_y(Angle::Degrees(-25.0));
camera.transform.rotate_z(Angle::Degrees(-90.0));
world.insert_resource(Input::new());
world.spawn((camera,));
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
let input: &Input = world.query_res().unwrap();
let speed = 0.001;
let mut dir_x = 0.0;
let mut dir_y = 0.0;
if input.is_pressed(KeyCode::A) {
dir_x -= speed;
}
if input.is_pressed(KeyCode::D) {
dir_x += speed;
}
if input.is_pressed(KeyCode::S) {
dir_y -= speed;
}
if input.is_pressed(KeyCode::W) {
dir_y += speed;
}
//debug!("dir: ({}, {})", dir_x, dir_y);
for (_eid, (transform, )) in world.query_mut::<(&mut TransformComponent,)>() {
let t = &mut transform.transform;
debug!("Translation: {}", t.translation);
/* debug!("Translation: {}", t.translation);
t.translation += glam::Vec3::new(0.0, 0.001, 0.0);
t.translation.x *= -1.0
t.translation.x *= -1.0 */
t.translation.x += dir_x;
t.translation.y += dir_y;
}
Ok(())
@ -102,5 +131,6 @@ async fn main() {
Game::initialize().await
.with_world(world)
.with_system("jiggle", jiggle_system, &[])
.with_system("input_system", InputSystem::new(), &[])
.run().await;
}

View File

@ -11,11 +11,12 @@ use wgpu::{BindGroup, BindGroupLayout};
use wgpu::util::DeviceExt;
use winit::window::Window;
use hecs::{World, Entity};
use hecs::Entity;
use crate::ecs::components::camera::CameraComponent;
use crate::ecs::components::mesh::MeshComponent;
use crate::ecs::components::transform::TransformComponent;
use crate::ecs::world::World;
use crate::math::{Transform, Angle};
use crate::resources;