Documentation comments

This commit is contained in:
AdamWhitehurst 2019-12-29 08:36:59 -08:00
parent ca44497258
commit 7a4cdfb7a4
5 changed files with 124 additions and 103 deletions

View File

@ -79,6 +79,8 @@ impl ActionState {
_ => true,
}
}
/// Returns the current `equip_delay` if in `WieldState`, otherwise `Duration::default()`
pub fn get_delay(&self) -> Duration {
match self {
Wield(WieldState { equip_delay }) => *equip_delay,
@ -123,6 +125,9 @@ impl ActionState {
}
}
/// __A concurrent state machine that allows for spearate `ActionState`s and `MoveState`s.__
///
/// _Each state can optionally override the other through `*_disabled` flag_
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct CharacterState {
/// __How the character is currently moving, e.g. Running, Standing, Falling.__
@ -196,21 +201,3 @@ pub struct OverrideMove;
impl Component for OverrideMove {
type Storage = FlaggedStorage<Self, NullStorage<Self>>;
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub enum StartAction {
Primary,
Secondary,
Tertiary,
Four,
Five,
}
impl Default for StartAction {
fn default() -> Self {
Self::Primary
}
}
impl Component for StartAction {
type Storage = FlaggedStorage<Self, NullStorage<Self>>;
}

View File

@ -17,9 +17,10 @@ impl StateHandle for IdleState {
};
// Try to wield
if ecs_data.inputs.toggle_wield.is_pressed()
|| ecs_data.inputs.primary.is_pressed()
if ecs_data.inputs.primary.is_pressed()
|| ecs_data.inputs.secondary.is_pressed()
|| (ecs_data.inputs.toggle_wield.is_just_pressed()
&& update.character.action_state.is_equip_finished())
{
if let Some(Tool { .. }) = ecs_data.stats.equipment.main.as_ref().map(|i| &i.kind) {
update.character.action_state = Wield(WieldState {

View File

@ -50,16 +50,31 @@ pub const GLIDE_ANTIGRAV: f32 = crate::sys::phys::GRAVITY * 0.96;
pub const CLIMB_SPEED: f32 = 5.0;
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
// Public interface, wires character states to their handlers.
use super::{
ActionState, ActionState::*, AttackKind::*, BlockKind::*, DodgeKind::*, EcsStateData,
MoveState, MoveState::*, StateUpdate,
};
/// #### A trait for implementing state `handle()`ing logic.
/// _Mimics the typical OOP style state machine pattern, but remains performant and consistent
/// with ECS data-behavior-separation constraint since trait fn's are syntactic sugar for
/// static fn's that accept their implementor's object type as its first parameter. This allows
/// for several benefits over implementing each state's behavior within the `CharacterState` update `System`
/// itself:_
///
/// 1. Less cognitive overhead: State's handling logic is next to the its data, and component (inside the state's .rs file).
/// 2. Separation of concerns (between states): all logic within a state's `handle()` is relevant only to that state.
/// States can be added/editted without concerns of affecting other state's logic.
/// 3. Clearly defined API and pattern: All states accept the same `EcsStateData` struct, which can be added to as necessary,
/// without the need for updating every state's implementation. All states return the same `StateUpdate` component.
/// `CharacterState` update `System` passes `EcsStateData` to `ActionState`/`MoveState` `handle()` which matches the character's
/// current state to its `handle()` fn, hiding the implementation details, since the System is only concerned with
/// how the update flow occurs and is in charge of updating the ECS components.
pub trait StateHandle {
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate;
}
// Public interface that passes EcsStateData to `StateHandle`s `handle()` fn
impl StateHandle for ActionState {
/// Passes handle to variant or subvariant handlers
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
@ -82,6 +97,7 @@ impl StateHandle for ActionState {
}
}
/// Public interface that passes EcsStateData to `StateHandle`s `handle()` fn
impl StateHandle for MoveState {
/// Passes handle to variant handlers
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {

View File

@ -20,7 +20,7 @@ impl StateHandle for WieldState {
// Only act once equip_delay has expired
if self.equip_delay == Duration::default() {
// Toggle Weapons
if ecs_data.inputs.toggle_wield.is_pressed()
if ecs_data.inputs.toggle_wield.is_just_pressed()
&& ecs_data.character.action_state.is_equip_finished()
{
update.character.action_state = Idle(IdleState);

View File

@ -8,11 +8,22 @@ use crate::{
state::DeltaTime,
};
use specs::{Entities, Join, LazyUpdate, Read, ReadStorage, System, WriteStorage};
use rayon::prelude::*;
use specs::{Entities, LazyUpdate, ParJoin, Read, ReadStorage, System, WriteStorage};
use sphynx::{Uid, UidAllocator};
/// # Character StateHandle System
/// #### Updates then detemrines next Character States based on ControllerInputs
/// # Character State System
/// #### Updates tuples of ( `CharacterState`, `Pos`, `Vel`, and `Ori` ) in parallel.
/// _Each update for a single character involves first passing an `EcsStateData` struct of ECS components
/// to the character's `MoveState`, then the character's `ActionState`. State update logic is
/// is encapsulated in state's `handle()` fn, impl'd by the `StateHandle` trait. `handle()` fn's
/// return a `StateUpdate` tuple containing new ( `CharacterState`, `Pos`, `Vel`, and `Ori` ) components.
/// Since `handle()` accepts readonly components, component updates are contained within this system and ECS
/// behavior constraints are satisfied._
///
/// _This mimics the typical OOP style state machine pattern, but remains performant
/// under ECS since trait fn's are syntactic sugar for static fn's that accept their implementor's
/// object type as its first parameter. See `StateHandle` for more information._
pub struct Sys;
impl<'a> System<'a> for Sys {
@ -61,22 +72,11 @@ impl<'a> System<'a> for Sys {
action_overrides,
): Self::SystemData,
) {
for (
entity,
uid,
mut character,
pos,
vel,
ori,
controller,
stats,
body,
physics,
maybe_mount,
maybe_move_override,
maybe_action_override,
(),
) in (
// Parallel joining behaves similarly to normal `join()`ing
// with the difference that iteration can potentially be
// executed in parallel by a thread pool.
// https://specs.amethyst.rs/docs/tutorials/09_parallel_join.html
(
&entities,
&uids,
&mut character_states,
@ -92,77 +92,94 @@ impl<'a> System<'a> for Sys {
action_overrides.maybe(),
!&state_overrides,
)
.join()
{
let inputs = &controller.inputs;
// Being dead overrides all other states
if stats.is_dead {
// Only options: click respawn
// prevent instant-respawns (i.e. player was holding attack)
// by disallowing while input is held down
if inputs.respawn.is_pressed() && !inputs.respawn.is_held_down() {
server_bus.emitter().emit(ServerEvent::Respawn(entity));
}
// Or do nothing
continue;
}
// If mounted, character state is controlled by mount
// TODO: Make mounting a state
if maybe_mount.is_some() {
character.move_state = Sit(SitState);
continue;
}
// Determine new move state if can move
if !maybe_move_override.is_some() && !character.move_disabled {
let state_update = character.move_state.handle(&EcsStateData {
entity: &entity,
.par_join()
.for_each(
|(
entity,
uid,
character,
mut character,
pos,
vel,
ori,
dt: &dt,
inputs,
controller,
stats,
body,
physics,
updater: &updater,
server_bus: &server_bus,
local_bus: &local_bus,
});
maybe_mount,
maybe_move_override,
maybe_action_override,
(),
)| {
let inputs = &controller.inputs;
*character = state_update.character;
*pos = state_update.pos;
*vel = state_update.vel;
*ori = state_update.ori;
}
// Being dead overrides all other states
if stats.is_dead {
// Only options: click respawn
// prevent instant-respawns (i.e. player was holding attack)
// by disallowing while input is held down
if inputs.respawn.is_pressed() && !inputs.respawn.is_held_down() {
server_bus.emitter().emit(ServerEvent::Respawn(entity));
}
// Or do nothing
continue;
}
// If mounted, character state is controlled by mount
// TODO: Make mounting a state
if maybe_mount.is_some() {
character.move_state = Sit(SitState);
continue;
}
// Determine new action if can_act
if !maybe_action_override.is_some() && !character.action_disabled {
let state_update = character.action_state.handle(&EcsStateData {
entity: &entity,
uid,
character,
pos,
vel,
ori,
dt: &dt,
inputs,
stats,
body,
physics,
updater: &updater,
server_bus: &server_bus,
local_bus: &local_bus,
});
// Determine new move state if character can move
if !maybe_move_override.is_some() && !character.move_disabled {
let state_update = character.move_state.handle(&EcsStateData {
entity: &entity,
uid,
character,
pos,
vel,
ori,
dt: &dt,
inputs,
stats,
body,
physics,
updater: &updater,
server_bus: &server_bus,
local_bus: &local_bus,
});
*character = state_update.character;
*pos = state_update.pos;
*vel = state_update.vel;
*ori = state_update.ori;
}
}
*character = state_update.character;
*pos = state_update.pos;
*vel = state_update.vel;
*ori = state_update.ori;
}
// Determine new action if character can act
if !maybe_action_override.is_some() && !character.action_disabled {
let state_update = character.action_state.handle(&EcsStateData {
entity: &entity,
uid,
character,
pos,
vel,
ori,
dt: &dt,
inputs,
stats,
body,
physics,
updater: &updater,
server_bus: &server_bus,
local_bus: &local_bus,
});
*character = state_update.character;
*pos = state_update.pos;
*vel = state_update.vel;
*ori = state_update.ori;
}
},
);
}
}