178 lines
5.4 KiB
Rust
178 lines
5.4 KiB
Rust
use std::any::TypeId;
|
|
use std::collections::HashMap;
|
|
|
|
use crate::entity::EntityHandle;
|
|
use crate::world::{Storage, World};
|
|
|
|
pub trait PlayerState
|
|
{
|
|
fn tick_time(&mut self, _delta: f32) {}
|
|
fn on_enter(&mut self, _world: &mut World, _entity: EntityHandle) {}
|
|
fn on_exit(&mut self, _world: &mut World, _entity: EntityHandle) {}
|
|
fn on_update(&mut self, _world: &mut World, _entity: EntityHandle, _delta: f32) {}
|
|
fn on_physics_update(&mut self, _world: &mut World, _entity: EntityHandle, _delta: f32) {}
|
|
}
|
|
|
|
struct StateOps
|
|
{
|
|
name: &'static str,
|
|
remove: Box<dyn Fn(&mut World, EntityHandle)>,
|
|
insert_default: Box<dyn Fn(&mut World, EntityHandle)>,
|
|
on_enter: Box<dyn Fn(&mut World, EntityHandle)>,
|
|
on_exit: Box<dyn Fn(&mut World, EntityHandle)>,
|
|
tick: Box<dyn Fn(&mut World, EntityHandle, f32)>,
|
|
physics_tick: Box<dyn Fn(&mut World, EntityHandle, f32)>,
|
|
}
|
|
|
|
struct StateTransition
|
|
{
|
|
to_state_id: TypeId,
|
|
condition: Box<dyn Fn(&World) -> bool>,
|
|
}
|
|
|
|
pub struct StateMachine
|
|
{
|
|
state_ops: HashMap<TypeId, StateOps>,
|
|
state_transitions: HashMap<TypeId, Vec<StateTransition>>,
|
|
current_state_id: TypeId,
|
|
}
|
|
|
|
fn short_type_name<T: 'static>() -> &'static str
|
|
{
|
|
let full = std::any::type_name::<T>();
|
|
full.rsplit("::").next().unwrap_or(full)
|
|
}
|
|
|
|
impl StateMachine
|
|
{
|
|
pub fn new<S: 'static>() -> Self
|
|
{
|
|
Self {
|
|
state_ops: HashMap::new(),
|
|
state_transitions: HashMap::new(),
|
|
current_state_id: TypeId::of::<S>(),
|
|
}
|
|
}
|
|
|
|
pub fn register_state<S: PlayerState + Default + 'static>(
|
|
&mut self,
|
|
storage: fn(&mut World) -> &mut Storage<S>,
|
|
)
|
|
{
|
|
let ops = StateOps {
|
|
name: short_type_name::<S>(),
|
|
remove: Box::new(move |world, entity| {
|
|
storage(world).components.remove(&entity);
|
|
}),
|
|
insert_default: Box::new(move |world, entity| {
|
|
storage(world).insert(entity, S::default());
|
|
}),
|
|
on_enter: Box::new(move |world, entity| {
|
|
if let Some(mut state) = storage(world).components.remove(&entity)
|
|
{
|
|
state.on_enter(world, entity);
|
|
storage(world).insert(entity, state);
|
|
}
|
|
}),
|
|
on_exit: Box::new(move |world, entity| {
|
|
if let Some(mut state) = storage(world).components.remove(&entity)
|
|
{
|
|
state.on_exit(world, entity);
|
|
storage(world).insert(entity, state);
|
|
}
|
|
}),
|
|
tick: Box::new(move |world, entity, delta| {
|
|
if let Some(mut state) = storage(world).components.remove(&entity)
|
|
{
|
|
state.tick_time(delta);
|
|
state.on_update(world, entity, delta);
|
|
storage(world).insert(entity, state);
|
|
}
|
|
}),
|
|
physics_tick: Box::new(move |world, entity, delta| {
|
|
if let Some(mut state) = storage(world).components.remove(&entity)
|
|
{
|
|
state.on_physics_update(world, entity, delta);
|
|
storage(world).insert(entity, state);
|
|
}
|
|
}),
|
|
};
|
|
self.state_ops.insert(TypeId::of::<S>(), ops);
|
|
}
|
|
|
|
pub fn update(&mut self, world: &mut World, entity: EntityHandle, delta: f32)
|
|
{
|
|
if let Some(next_state_id) = self.get_transition_state_id(world)
|
|
{
|
|
self.apply_transition(world, entity, next_state_id);
|
|
}
|
|
|
|
if let Some(ops) = self.state_ops.get(&self.current_state_id)
|
|
{
|
|
(ops.tick)(world, entity, delta);
|
|
}
|
|
}
|
|
|
|
pub fn physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32)
|
|
{
|
|
if let Some(ops) = self.state_ops.get(&self.current_state_id)
|
|
{
|
|
(ops.physics_tick)(world, entity, delta);
|
|
}
|
|
}
|
|
|
|
fn get_transition_state_id(&self, world: &World) -> Option<TypeId>
|
|
{
|
|
if let Some(transitions) = self.state_transitions.get(&self.current_state_id)
|
|
{
|
|
for transition in transitions
|
|
{
|
|
if (transition.condition)(world)
|
|
{
|
|
return Some(transition.to_state_id);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn apply_transition(&mut self, world: &mut World, entity: EntityHandle, next_id: TypeId)
|
|
{
|
|
if let Some(ops) = self.state_ops.get(&self.current_state_id)
|
|
{
|
|
(ops.on_exit)(world, entity);
|
|
(ops.remove)(world, entity);
|
|
}
|
|
|
|
self.current_state_id = next_id;
|
|
|
|
if let Some(ops) = self.state_ops.get(&next_id)
|
|
{
|
|
(ops.insert_default)(world, entity);
|
|
(ops.on_enter)(world, entity);
|
|
}
|
|
}
|
|
|
|
pub fn add_transition<TFrom: 'static, TTo: 'static>(
|
|
&mut self,
|
|
condition: impl Fn(&World) -> bool + 'static,
|
|
)
|
|
{
|
|
let from_id = TypeId::of::<TFrom>();
|
|
let to_id = TypeId::of::<TTo>();
|
|
let transitions = self.state_transitions.entry(from_id).or_default();
|
|
transitions.push(StateTransition {
|
|
to_state_id: to_id,
|
|
condition: Box::new(condition),
|
|
});
|
|
}
|
|
|
|
pub fn get_current_state_name(&self) -> &'static str
|
|
{
|
|
self.state_ops
|
|
.get(&self.current_state_id)
|
|
.map(|ops| ops.name)
|
|
.unwrap_or("Unknown")
|
|
}
|
|
}
|