use super::{InputData, InputHandlerHandler, UnknownInputMethod};
use rustc_hash::FxHashSet;
use std::{fmt::Debug, iter::FromIterator, mem::swap, sync::Arc};
pub trait InputActionState: Sized + Clone + Send + Sync + 'static {}
impl<T: Sized + Clone + Send + Sync + 'static> InputActionState for T {}
pub type ActiveCondition<S> = fn(&InputData, state: &S) -> bool;
pub trait InputAction<S: InputActionState> {
fn base(&self) -> &BaseInputAction<S>;
fn base_mut(&mut self) -> &mut BaseInputAction<S>;
fn type_erase(&mut self) -> &mut dyn InputAction<S>
where
Self: Sized,
{
self as &mut dyn InputAction<S>
}
}
#[derive(Clone)]
pub struct BaseInputAction<S: InputActionState> {
pub capture_on_trigger: bool,
pub active_condition: ActiveCondition<S>,
pub started_acting: FxHashSet<Arc<InputData>>,
pub actively_acting: FxHashSet<Arc<InputData>>,
pub stopped_acting: FxHashSet<Arc<InputData>>,
queued_inputs: FxHashSet<Arc<InputData>>,
}
impl<S: InputActionState> BaseInputAction<S> {
pub fn new(capture_on_trigger: bool, active_condition: ActiveCondition<S>) -> Self {
Self {
capture_on_trigger,
active_condition,
started_acting: FxHashSet::default(),
actively_acting: FxHashSet::default(),
stopped_acting: FxHashSet::default(),
queued_inputs: FxHashSet::default(),
}
}
fn update(&mut self, external: &mut BaseInputAction<S>) {
self.started_acting = FxHashSet::from_iter(
self.queued_inputs
.difference(&self.actively_acting)
.cloned(),
);
self.stopped_acting = FxHashSet::from_iter(
self.actively_acting
.difference(&self.queued_inputs)
.cloned(),
);
swap(&mut self.actively_acting, &mut self.queued_inputs);
self.queued_inputs.clear();
external.started_acting = self.started_acting.clone();
external.actively_acting = self.actively_acting.clone();
external.stopped_acting = self.stopped_acting.clone();
external.started_acting = self.started_acting.clone();
self.capture_on_trigger = external.capture_on_trigger;
self.active_condition = external.active_condition;
}
fn input_event(&mut self, data: &Arc<InputData>, state: &S) -> bool {
if (self.active_condition)(data, state) {
self.queued_inputs.insert(data.clone());
true
} else {
false
}
}
}
impl<S: InputActionState> InputAction<S> for BaseInputAction<S> {
fn base(&self) -> &BaseInputAction<S> {
self
}
fn base_mut(&mut self) -> &mut BaseInputAction<S> {
self
}
}
impl<S: InputActionState> PartialEq for BaseInputAction<S> {
fn eq(&self, other: &Self) -> bool {
self.capture_on_trigger == other.capture_on_trigger
&& self.active_condition as usize == other.active_condition as usize
}
}
impl<S: InputActionState> Debug for BaseInputAction<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InputAction")
.field("capture_on_trigger", &self.capture_on_trigger)
.field("started_acting", &self.started_acting)
.field("actively_acting", &self.actively_acting)
.field("stopped_acting", &self.stopped_acting)
.field("queued_inputs", &self.queued_inputs)
.finish()
}
}
#[derive(Debug, Default)]
pub struct InputActionHandler<S: InputActionState> {
actions: Vec<BaseInputAction<S>>,
state: S,
back_state: S,
}
impl<S: InputActionState> InputActionHandler<S> {
pub fn new(state: S) -> Self {
Self {
actions: Vec::new(),
back_state: state.clone(),
state,
}
}
pub fn update_actions<'a>(
&mut self,
actions: impl IntoIterator<Item = &'a mut (dyn InputAction<S> + 'a)>,
) {
self.back_state = self.state.clone();
self.actions = actions
.into_iter()
.map(|action| {
if let Some(internal_action) = self
.actions
.iter_mut()
.find(|internal_action| **internal_action == *action.base())
{
internal_action.update(action.base_mut());
}
action.base().clone()
})
.collect();
}
pub fn update_state(&mut self, state: S) {
self.state = state;
}
}
impl<S: InputActionState> InputHandlerHandler for InputActionHandler<S> {
fn input(&mut self, input: UnknownInputMethod, data: InputData) {
let data = Arc::new(data);
let capture = self
.actions
.iter_mut()
.map(|action| action.input_event(&data, &self.state) && action.capture_on_trigger)
.reduce(|a, b| a || b)
.unwrap_or_default();
if capture {
let _ = input.capture();
}
}
}
#[tokio::test]
async fn fusion_input_action_handler() {
color_eyre::install().unwrap();
use crate::{client::Client, fields::SphereField, input::InputHandler};
use stardust_xr::values::Transform;
let (client, event_loop) = Client::connect_with_async_loop()
.await
.expect("Couldn't connect");
struct InputActionHandlerTest {
field: SphereField,
input_handler: crate::HandlerWrapper<InputHandler, InputActionHandler<f32>>,
hover_action: BaseInputAction<f32>,
fancy_action: FancyInputAction<f32>,
}
let field = SphereField::create(client.get_root(), mint::Vector3::from([0.0; 3]), 0.1).unwrap();
let input_action_test = InputActionHandlerTest {
input_handler: InputHandler::create(client.get_root(), Transform::default(), &field)
.unwrap()
.wrap(InputActionHandler::new(0.05))
.unwrap(),
hover_action: BaseInputAction::new(false, |input_data, max_distance| {
dbg!(input_data);
input_data.distance < *max_distance
}),
fancy_action: FancyInputAction::default(),
field,
};
struct FancyInputAction<S: InputActionState> {
action: BaseInputAction<S>,
}
impl<S: InputActionState> Default for FancyInputAction<S> {
fn default() -> Self {
Self {
action: BaseInputAction::new(false, |_, _| true),
}
}
}
impl<S: InputActionState> InputAction<S> for FancyInputAction<S> {
fn base(&self) -> &BaseInputAction<S> {
&self.action
}
fn base_mut(&mut self) -> &mut BaseInputAction<S> {
&mut self.action
}
}
impl crate::client::RootHandler for InputActionHandlerTest {
fn frame(&mut self, info: crate::client::FrameInfo) {
println!("Life cycle step {}s", info.elapsed);
self.input_handler.lock_wrapped().update_actions(
[
self.hover_action.type_erase(),
self.fancy_action.type_erase(),
]
.into_iter(),
);
}
}
let _root = client.wrap_root(input_action_test).unwrap();
tokio::select! {
biased;
_ = tokio::signal::ctrl_c() => (),
e = event_loop => e.unwrap().unwrap(),
};
}