feat!: no error without features
This commit is contained in:
Generated
+75
-8
@@ -447,11 +447,13 @@ name = "bevy_controls"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
|
"bevy_app",
|
||||||
"bevy_derive",
|
"bevy_derive",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
"bevy_input",
|
"bevy_input",
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
"bevy_utils",
|
"bevy_utils",
|
||||||
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
@@ -1333,6 +1335,19 @@ dependencies = [
|
|||||||
"syn 2.0.48",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
@@ -1363,6 +1378,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-code"
|
name = "error-code"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@@ -1672,6 +1697,12 @@ version = "0.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hexasphere"
|
name = "hexasphere"
|
||||||
version = "9.1.0"
|
version = "9.1.0"
|
||||||
@@ -1697,6 +1728,12 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1754,6 +1791,17 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -1854,6 +1902,12 @@ dependencies = [
|
|||||||
"redox_syscall 0.4.1",
|
"redox_syscall 0.4.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
@@ -2331,9 +2385,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.75"
|
version = "1.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -2469,6 +2523,19 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
@@ -2492,18 +2559,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.194"
|
version = "1.0.195"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
|
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.194"
|
version = "1.0.195"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
|
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3432,9 +3499,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.32"
|
version = "0.5.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
|
checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|||||||
+3
-22
@@ -1,22 +1,3 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "bevy_controls"
|
resolver = "2"
|
||||||
version = "0.1.0"
|
members = ["crate/*"]
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
serialize = ["bevy_input/serialize", "serde"]
|
|
||||||
reflect = ["dep:bevy_reflect"]
|
|
||||||
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bevy-inspector-egui = { version = "0.22.1", optional = true }
|
|
||||||
bevy_derive = "0.12.1"
|
|
||||||
bevy_ecs = "0.12.1"
|
|
||||||
bevy_input = "0.12.1"
|
|
||||||
bevy_reflect = { version = "0.12.1", optional = true }
|
|
||||||
bevy_utils = "0.12.1"
|
|
||||||
log = "0.4.20"
|
|
||||||
serde = { version = "1.0.194", optional = true }
|
|
||||||
strum = "0.25.0"
|
|
||||||
strum_macros = "0.25.3"
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "bevy_controls"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
serialize = ["bevy_input/serialize", "serde"]
|
||||||
|
reflect = ["dep:bevy_reflect"]
|
||||||
|
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy-inspector-egui = { version = "0.22.1", optional = true }
|
||||||
|
bevy_app = "0.12.1"
|
||||||
|
bevy_derive = "0.12.1"
|
||||||
|
bevy_ecs = "0.12.1"
|
||||||
|
bevy_input = "0.12.1"
|
||||||
|
bevy_reflect = { version = "0.12.1", optional = true }
|
||||||
|
bevy_utils = "0.12.1"
|
||||||
|
log = "0.4.20"
|
||||||
|
serde = { version = "1.0.194", optional = true }
|
||||||
|
strum = "0.25.0"
|
||||||
|
strum_macros = "0.25.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.10.1"
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use bevy_ecs::{system::Resource, schedule::States};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
use crate::resource::PlayerInputs;
|
||||||
|
|
||||||
|
pub trait InputsContainer<A: Action>: 'static + std::marker::Sync + std::marker::Send + Resource {
|
||||||
|
/// The method returning an iterator over references to the PlayerInputs.
|
||||||
|
/// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime.
|
||||||
|
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<A>> + 'a>;
|
||||||
|
/// The method returning an player inputs of this client.
|
||||||
|
fn me<'a>(&self) -> Option<&'a PlayerInputs<A>>;
|
||||||
|
/// The method returning an mutable player inputs of this client.
|
||||||
|
fn me_mut<'a>(&self) -> Option<&'a mut PlayerInputs<A>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))]
|
||||||
|
pub trait Action: 'static + std::marker::Sync + std::marker::Send + PartialEq + Eq + Hash + IntoEnumIterator + Clone + Copy { }
|
||||||
|
|
||||||
|
#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))]
|
||||||
|
pub trait Action<'a>: 'static + std::marker::Sync + std::marker::Send + PartialEq + Eq + Hash + IntoEnumIterator + Clone + Copy + Serialize + Deserialize<'a> { }
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))]
|
||||||
|
pub trait Action: 'static + std::marker::Sync + std::marker::Send + PartialEq + Eq + Hash + IntoEnumIterator + Clone + Copy + Serialize + Deserialize + Reflect { }
|
||||||
|
|
||||||
|
|
||||||
|
pub trait GameState: 'static + std::marker::Sync + std::marker::Send + States {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use bevy_ecs::{entity::Entity, schedule::States};
|
||||||
|
use bevy_utils::{HashMap, prelude::default};
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
#[cfg(feature = "serialize")]
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::{hashmap, resource::PlayerInputs, common_traits_conditions};
|
||||||
|
|
||||||
|
common_traits_conditions! {
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
||||||
|
pub struct Lobby {
|
||||||
|
pub me: PlayerId,
|
||||||
|
pub players: HashMap<PlayerId, Player>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Lobby {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
me: PlayerId::Host,
|
||||||
|
players: hashmap! {
|
||||||
|
PlayerId::Host => Player::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common_traits_conditions! {
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub enum PlayerId {
|
||||||
|
/// Host or alone
|
||||||
|
Host,
|
||||||
|
/// Client
|
||||||
|
Client(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Clone)]
|
||||||
|
pub struct Player {
|
||||||
|
/// Client do not need to know about other clients
|
||||||
|
#[cfg_attr(feature = "serialize", serde(skip))]
|
||||||
|
pub inputs: PlayerInputs<Action>,
|
||||||
|
#[cfg_attr(feature = "serialize", serde(skip))]
|
||||||
|
pub entity: Option<Entity>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, States, Default, Hash, Eq)]
|
||||||
|
pub enum GameState {
|
||||||
|
#[default]
|
||||||
|
LevelEditor,
|
||||||
|
InGame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::GameState for GameState {}
|
||||||
|
|
||||||
|
// TODO: it is just plug, it must be a trait
|
||||||
|
/// Actions that can be performed by player
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, EnumIter)]
|
||||||
|
pub enum Action {
|
||||||
|
/// Move forward
|
||||||
|
LeverEditorForward,
|
||||||
|
/// Move backward
|
||||||
|
LevelEditorBackward,
|
||||||
|
/// Move left
|
||||||
|
LevelEditorLeft,
|
||||||
|
/// Move right
|
||||||
|
LevelEditorRight,
|
||||||
|
LevelEditorFly,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Action for Action {}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
pub mod resource;
|
pub mod resource;
|
||||||
|
pub mod plugin;
|
||||||
|
pub mod contract;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
use bevy_app::{Plugin, App, Update};
|
||||||
|
use bevy_ecs::{system::{Res, ResMut, Resource}, schedule::{State, States}};
|
||||||
|
use bevy_input::{Input, keyboard::KeyCode, mouse::MouseButton};
|
||||||
|
|
||||||
|
use crate::{resource::*, contract::{Action, GameState, InputsContainer}};
|
||||||
|
|
||||||
|
pub struct ControlsPlugin<Action, InputsContainer, GameState> {
|
||||||
|
_action: std::marker::PhantomData<Action>,
|
||||||
|
_inputs_container: std::marker::PhantomData<InputsContainer>,
|
||||||
|
_game_state: std::marker::PhantomData<GameState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
A: Action,
|
||||||
|
Ic: InputsContainer<A>,
|
||||||
|
Gs: GameState
|
||||||
|
> Plugin for ControlsPlugin<A, Ic, Gs> {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.init_resource::<Controls<A, Gs>>()
|
||||||
|
.add_systems(Update, Self::save_input);
|
||||||
|
|
||||||
|
#[cfg(feature = "reflect")]
|
||||||
|
app.register_type::<Controls<A>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
A: Action,
|
||||||
|
Ic: InputsContainer<A>,
|
||||||
|
Gs: GameState
|
||||||
|
> ControlsPlugin<A, Ic, Gs> {
|
||||||
|
/// Process all hard inputs and bindings to update [`PlayerInputs`]
|
||||||
|
fn save_input(
|
||||||
|
keyboard_input: Res<Input<KeyCode>>,
|
||||||
|
mouse_input: Res<Input<MouseButton>>,
|
||||||
|
lobby: ResMut<Ic>,
|
||||||
|
controls: Res<Controls<A, Gs>>,
|
||||||
|
game_state: Res<State<Gs>>,
|
||||||
|
) {
|
||||||
|
if let Some(inputs) = lobby.me_mut() {
|
||||||
|
for (action, config) in controls.iter() {
|
||||||
|
'bindings_loop: for binding in config.bindings.iter() {
|
||||||
|
for condition in &binding.conditions {
|
||||||
|
match condition {
|
||||||
|
BindingCondition::InGameState(state) => {
|
||||||
|
if *state != *game_state.get() {
|
||||||
|
continue 'bindings_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BindingCondition::DuringPauseMenu(_value) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
BindingCondition::ListeningForText(_value) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &binding.input {
|
||||||
|
ButtonCombination::Single(button) => match button {
|
||||||
|
InputType::Keyboard(key) => {
|
||||||
|
inputs.forced_set(*action, keyboard_input.pressed(*key));
|
||||||
|
}
|
||||||
|
InputType::Mouse(input) => {
|
||||||
|
match input {
|
||||||
|
MouseInput::Axis(_axis) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
MouseInput::Button(button) => {
|
||||||
|
if !mouse_input
|
||||||
|
.get_pressed()
|
||||||
|
.any(|b| b == button)
|
||||||
|
{
|
||||||
|
continue 'bindings_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseInput::Wheel(_axis) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ButtonCombination::Chord(buttons) => {
|
||||||
|
// If any button in chord is not pressed we skip this `binding`
|
||||||
|
for button in buttons {
|
||||||
|
match button {
|
||||||
|
InputType::Keyboard(key) => {
|
||||||
|
if !keyboard_input.pressed(*key) {
|
||||||
|
continue 'bindings_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputType::Mouse(input) => {
|
||||||
|
match input {
|
||||||
|
MouseInput::Axis(_axis) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
MouseInput::Button(button) => {
|
||||||
|
if !mouse_input
|
||||||
|
.get_pressed()
|
||||||
|
.any(|b| b == button)
|
||||||
|
{
|
||||||
|
continue 'bindings_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseInput::Wheel(_axis) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type
|
||||||
|
inputs.forced_set(*action, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("You like [`Player`] is not in lobby")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod performance_test {
|
||||||
|
// use crate::{ util::test::{enable_loggings, measure_time, Times}, resource::PlayerInputs};
|
||||||
|
|
||||||
|
// use super::Controls;
|
||||||
|
// use std::time::Duration;
|
||||||
|
|
||||||
|
// /// Test for execution time for [`Controls`] get
|
||||||
|
// ///
|
||||||
|
// /// Example:
|
||||||
|
// /// ```
|
||||||
|
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::controls_get --exact --nocapture
|
||||||
|
// /// ```
|
||||||
|
// #[test]
|
||||||
|
// fn controls_get() {
|
||||||
|
// enable_loggings();
|
||||||
|
|
||||||
|
// let controls = Controls::default();
|
||||||
|
|
||||||
|
// let duration = measure_time(
|
||||||
|
// || {
|
||||||
|
// controls.get(Action::LeverEditorForward);
|
||||||
|
// controls.get(Action::LevelEditorBackward);
|
||||||
|
// controls.get(Action::LevelEditorLeft);
|
||||||
|
// controls.get(Action::LevelEditorRight);
|
||||||
|
// },
|
||||||
|
// Times::default(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// log::info!("time: {:?}", duration);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Test for execution speed for [`PlayerInputs`] get
|
||||||
|
// fn player_inputs_get() -> Duration {
|
||||||
|
// enable_loggings();
|
||||||
|
|
||||||
|
// let inputs = PlayerInputs::default();
|
||||||
|
|
||||||
|
// let duration = measure_time(
|
||||||
|
// || {
|
||||||
|
// inputs.get(Action::LeverEditorForward);
|
||||||
|
// inputs.get(Action::LevelEditorBackward);
|
||||||
|
// inputs.get(Action::LevelEditorLeft);
|
||||||
|
// inputs.get(Action::LevelEditorRight);
|
||||||
|
// },
|
||||||
|
// Times::default(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// duration
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Test for execution speed for [`PlayerInputs`] get_many
|
||||||
|
// fn player_inputs_get_many() -> Duration {
|
||||||
|
// enable_loggings();
|
||||||
|
|
||||||
|
// let inputs = PlayerInputs::default();
|
||||||
|
|
||||||
|
// let duration = measure_time(
|
||||||
|
// || {
|
||||||
|
// inputs.get_many(vec![
|
||||||
|
// Action::LeverEditorForward,
|
||||||
|
// Action::LevelEditorBackward,
|
||||||
|
// Action::LevelEditorLeft,
|
||||||
|
// Action::LevelEditorRight,
|
||||||
|
// ]);
|
||||||
|
// },
|
||||||
|
// Times::default(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// duration
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Test for execution speed for [`PlayerInputs`] get and get_many
|
||||||
|
// ///
|
||||||
|
// /// Example:
|
||||||
|
// /// ```
|
||||||
|
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::compare_player_inputs_get_and_get_many --exact --nocapture
|
||||||
|
// /// ```
|
||||||
|
// #[test]
|
||||||
|
// fn compare_player_inputs_get_and_get_many() {
|
||||||
|
// let get = player_inputs_get();
|
||||||
|
// let get_many = player_inputs_get_many();
|
||||||
|
|
||||||
|
// log::info!("get: {:?}", get);
|
||||||
|
// log::info!("get_many: {:?}", get_many);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Test for execution speed for [`InputValue`] casting
|
||||||
|
// #[test]
|
||||||
|
// fn action_casting() {
|
||||||
|
// enable_loggings();
|
||||||
|
|
||||||
|
// let inputs = PlayerInputs::default();
|
||||||
|
|
||||||
|
// let duration = measure_time(
|
||||||
|
// || {
|
||||||
|
// let _ = (inputs.get(Action::LeverEditorForward).as_boolean() as i8
|
||||||
|
// - inputs.get(Action::LevelEditorBackward).as_boolean() as i8)
|
||||||
|
// as f32;
|
||||||
|
// },
|
||||||
|
// 10000000.into(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// log::info!("with casting: {:?}", duration);
|
||||||
|
|
||||||
|
// let inputs = PlayerInputs::default();
|
||||||
|
|
||||||
|
// let duration = measure_time(
|
||||||
|
// || {
|
||||||
|
// let _ = inputs.get(Action::LeverEditorForward);
|
||||||
|
// let _ = inputs.get(Action::LevelEditorBackward);
|
||||||
|
// },
|
||||||
|
// 10000000.into(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// log::info!("without casting: {:?}", duration);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -17,7 +17,7 @@ use strum_macros::EnumIter;
|
|||||||
#[cfg(feature = "reflect")]
|
#[cfg(feature = "reflect")]
|
||||||
use {bevy_ecs::prelude::ReflectResource, bevy_reflect::Reflect};
|
use {bevy_ecs::prelude::ReflectResource, bevy_reflect::Reflect};
|
||||||
|
|
||||||
use crate::{common_traits_conditions, hashmap};
|
use crate::{common_traits_conditions, hashmap, contract::{Action, GameState}};
|
||||||
|
|
||||||
/// Validate that all enum variants are in the hash map
|
/// Validate that all enum variants are in the hash map
|
||||||
pub fn validate_hash_map<K, V>(hash_map: &HashMap<K, V>) -> bool
|
pub fn validate_hash_map<K, V>(hash_map: &HashMap<K, V>) -> bool
|
||||||
@@ -42,26 +42,26 @@ where
|
|||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
/// Struct that contains user's inputs corresponding to actions
|
/// Struct that contains user's inputs corresponding to actions
|
||||||
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
|
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
|
||||||
pub struct Inputs(HashMap<Action, InputValue>);
|
pub struct Inputs<A: Action>(HashMap<A, InputValue>);
|
||||||
|
|
||||||
/// Resource that contains current user inputs
|
/// Resource that contains current user inputs
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct PlayerInputs {
|
pub struct PlayerInputs<A: Action> {
|
||||||
current: Inputs,
|
current: Inputs<A>,
|
||||||
previous: Inputs,
|
previous: Inputs<A>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlayerInputs {
|
impl<A: Action> Default for PlayerInputs<A> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PlayerInputs {
|
PlayerInputs {
|
||||||
current: Inputs(
|
current: Inputs(
|
||||||
Action::iter()
|
A::iter()
|
||||||
.map(|action| (action, InputValue::Empty))
|
.map(|action| (action, InputValue::Empty))
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
previous: Inputs(
|
previous: Inputs(
|
||||||
Action::iter()
|
A::iter()
|
||||||
.map(|action| (action, InputValue::Empty))
|
.map(|action| (action, InputValue::Empty))
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
@@ -69,18 +69,18 @@ impl Default for PlayerInputs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerInputs {
|
impl<A: Action> PlayerInputs<A> {
|
||||||
/// Returns current input value for action
|
/// Returns current input value for action
|
||||||
///
|
///
|
||||||
/// in any case faster that `get_many` method
|
/// in any case faster that `get_many` method
|
||||||
pub fn get(&self, action: Action) -> InputValue {
|
pub fn get(&self, action: A) -> InputValue {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
*self.current.get(&action).unwrap()
|
*self.current.get(&action).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns current input values for actions
|
/// Returns current input values for actions
|
||||||
pub fn get_many(&self, actions: Vec<Action>) -> Vec<InputValue> {
|
pub fn get_many(&self, actions: Vec<A>) -> Vec<InputValue> {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
actions
|
actions
|
||||||
@@ -91,7 +91,7 @@ impl PlayerInputs {
|
|||||||
|
|
||||||
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
||||||
/// If input has not same type as `InputValue` on previous frame call `panic`
|
/// If input has not same type as `InputValue` on previous frame call `panic`
|
||||||
pub fn just_pressed(&self, action: Action) -> bool {
|
pub fn just_pressed(&self, action: A) -> bool {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
||||||
@@ -105,7 +105,7 @@ impl PlayerInputs {
|
|||||||
|
|
||||||
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
||||||
/// If input has not same type as `InputValue` on previous frame return `Error()`
|
/// If input has not same type as `InputValue` on previous frame return `Error()`
|
||||||
pub fn get_just_pressed(&self, action: Action) -> Result<bool, Box<dyn error::Error>> {
|
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
||||||
@@ -118,7 +118,7 @@ impl PlayerInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set input value to new
|
/// Set input value to new
|
||||||
pub fn forced_set(&mut self, action: Action, value: impl Into<InputValue>) {
|
pub fn forced_set(&mut self, action: A, value: impl Into<InputValue>) {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
let current_input = self.current.get_mut(&action).unwrap();
|
let current_input = self.current.get_mut(&action).unwrap();
|
||||||
@@ -130,7 +130,7 @@ impl PlayerInputs {
|
|||||||
/// if it is not the same InputValue type return `Error()`
|
/// if it is not the same InputValue type return `Error()`
|
||||||
pub fn set(
|
pub fn set(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: Action,
|
action: A,
|
||||||
value: impl Into<InputValue>,
|
value: impl Into<InputValue>,
|
||||||
) -> Result<(), Box<dyn error::Error>> {
|
) -> Result<(), Box<dyn error::Error>> {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
@@ -149,7 +149,7 @@ impl PlayerInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update current inputs
|
/// Update current inputs
|
||||||
pub fn forced_update(&mut self, inputs: Inputs) {
|
pub fn forced_update(&mut self, inputs: Inputs<A>) {
|
||||||
*self.previous = self.current.clone().0;
|
*self.previous = self.current.clone().0;
|
||||||
*self.current = inputs.0;
|
*self.current = inputs.0;
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ impl PlayerInputs {
|
|||||||
/// Update current inputs
|
/// Update current inputs
|
||||||
/// if any input is not the same InputValue type return `Error()`
|
/// if any input is not the same InputValue type return `Error()`
|
||||||
/// ! remember that it work safely but slow
|
/// ! remember that it work safely but slow
|
||||||
pub fn update(&mut self, inputs: Inputs) -> Result<(), Box<dyn error::Error>> {
|
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
for (action, value) in inputs.0.iter() {
|
for (action, value) in inputs.0.iter() {
|
||||||
@@ -176,13 +176,6 @@ impl PlayerInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
// TODO: it must be a trait
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum GameState {
|
|
||||||
LevelEditor,
|
|
||||||
InGame,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum InputType {
|
pub enum InputType {
|
||||||
Keyboard(KeyCode),
|
Keyboard(KeyCode),
|
||||||
@@ -274,39 +267,24 @@ common_traits_conditions! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum BindingCondition {
|
pub enum BindingCondition<Gs: GameState> {
|
||||||
/// During specific game state
|
/// During specific game state
|
||||||
InGameState(GameState),
|
InGameState(Gs),
|
||||||
/// Binding is active only if player is in pause menu
|
/// Binding is active only if player is in pause menu
|
||||||
DuringPauseMenu(bool),
|
DuringPauseMenu(bool),
|
||||||
/// Binding is active only if player types text
|
/// Binding is active only if player types text
|
||||||
ListeningForText(bool),
|
ListeningForText(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: it is just plug, it must be a trait
|
|
||||||
/// Actions that can be performed by player
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, EnumIter)]
|
|
||||||
pub enum Action {
|
|
||||||
/// Move forward
|
|
||||||
LeverEditorForward,
|
|
||||||
/// Move backward
|
|
||||||
LevelEditorBackward,
|
|
||||||
/// Move left
|
|
||||||
LevelEditorLeft,
|
|
||||||
/// Move right
|
|
||||||
LevelEditorRight,
|
|
||||||
LevelEditorFly,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Binding is a combination of buttons that triggers action
|
/// Binding is a combination of buttons that triggers action
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Binding {
|
pub struct Binding<Gs: GameState> {
|
||||||
pub input: ButtonCombination,
|
pub input: ButtonCombination,
|
||||||
pub conditions: Vec<BindingCondition>,
|
pub conditions: Vec<BindingCondition<Gs>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ButtonCombination> for Binding {
|
impl<Gs: GameState> From<ButtonCombination> for Binding<Gs> {
|
||||||
fn from(input: ButtonCombination) -> Self {
|
fn from(input: ButtonCombination) -> Self {
|
||||||
Binding {
|
Binding {
|
||||||
input,
|
input,
|
||||||
@@ -315,7 +293,7 @@ impl From<ButtonCombination> for Binding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Binding {
|
impl<Gs: GameState> Binding<Gs> {
|
||||||
pub fn new(input: ButtonCombination) -> Self {
|
pub fn new(input: ButtonCombination) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input,
|
input,
|
||||||
@@ -323,7 +301,7 @@ impl Binding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_condition(mut self, condition: BindingCondition) -> Self {
|
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
|
||||||
self.conditions.push(condition);
|
self.conditions.push(condition);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -393,15 +371,15 @@ impl InputValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
pub struct Bindings {
|
pub struct Bindings<Gs: GameState> {
|
||||||
/// List of bindings for action
|
/// List of bindings for action
|
||||||
list: Vec<Binding>,
|
list: Vec<Binding<Gs>>,
|
||||||
/// Possibility to change this binding
|
/// Possibility to change this binding
|
||||||
options: OptionsMode,
|
options: OptionsMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Bindings {
|
impl<Gs: GameState> Default for Bindings<Gs> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list: Vec::new(),
|
list: Vec::new(),
|
||||||
@@ -410,15 +388,15 @@ impl Default for Bindings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bindings {
|
impl<Gs: GameState> Bindings<Gs> {
|
||||||
pub fn new(bindings: Vec<Binding>, options: OptionsMode) -> Self {
|
pub fn new(bindings: Vec<Binding<Gs>>, options: OptionsMode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
list: bindings,
|
list: bindings,
|
||||||
options,
|
options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, binding: Binding) {
|
pub fn push(&mut self, binding: Binding<Gs>) {
|
||||||
if self.options == OptionsMode::Immutable {
|
if self.options == OptionsMode::Immutable {
|
||||||
warn!("You try to push binding to immutable bindings");
|
warn!("You try to push binding to immutable bindings");
|
||||||
return;
|
return;
|
||||||
@@ -436,7 +414,7 @@ impl Bindings {
|
|||||||
|
|
||||||
pub fn retain<F>(&mut self, f: F)
|
pub fn retain<F>(&mut self, f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&Binding) -> bool,
|
F: FnMut(&Binding<Gs>) -> bool,
|
||||||
{
|
{
|
||||||
if self.options == OptionsMode::Immutable {
|
if self.options == OptionsMode::Immutable {
|
||||||
warn!("You try to retain immutable bindings");
|
warn!("You try to retain immutable bindings");
|
||||||
@@ -445,13 +423,13 @@ impl Bindings {
|
|||||||
self.list.retain(f);
|
self.list.retain(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Binding> {
|
pub fn iter(&self) -> impl Iterator<Item = &Binding<Gs>> {
|
||||||
self.list.iter()
|
self.list.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(
|
pub fn iter_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<impl Iterator<Item = &mut Binding>, Box<dyn error::Error>> {
|
) -> Result<impl Iterator<Item = &mut Binding<Gs>>, Box<dyn error::Error>> {
|
||||||
if self.options == OptionsMode::Immutable {
|
if self.options == OptionsMode::Immutable {
|
||||||
warn!("You try to iterate over immutable bindings");
|
warn!("You try to iterate over immutable bindings");
|
||||||
return Err("You try to iterate over immutable bindings".into());
|
return Err("You try to iterate over immutable bindings".into());
|
||||||
@@ -472,15 +450,15 @@ common_traits_conditions! {
|
|||||||
/// Contains all bindings for action
|
/// Contains all bindings for action
|
||||||
/// and different activation options
|
/// and different activation options
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BindingConfig {
|
pub struct BindingConfig<Gs: GameState> {
|
||||||
pub bindings: Bindings,
|
pub bindings: Bindings<Gs>,
|
||||||
#[cfg_attr(feature = "serialize", serde(skip))]
|
#[cfg_attr(feature = "serialize", serde(skip))]
|
||||||
pub activation: ActivationOptions,
|
pub activation: ActivationOptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BindingConfig {
|
impl<Gs: GameState> BindingConfig<Gs> {
|
||||||
pub fn new(bindings: Bindings, activation: ActivationOptions) -> Self {
|
pub fn new(bindings: Bindings<Gs>, activation: ActivationOptions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bindings,
|
bindings,
|
||||||
activation,
|
activation,
|
||||||
@@ -493,49 +471,13 @@ common_traits_conditions! {
|
|||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
||||||
// TODO: Add delay for input
|
// TODO: Add delay for input
|
||||||
pub struct Controls(HashMap<Action, BindingConfig>); // TODO: change HashMap to Vec for faster iteration
|
pub struct Controls<A: Action, Gs: GameState>(HashMap<A, BindingConfig<Gs>>); // TODO: change HashMap to Vec for faster iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Controls {
|
impl<A: Action, Gs: GameState> Default for Controls<A, Gs> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// TODO: default must create empty controls or fill empty / default BindingConfig on any actions
|
// TODO: default must create empty controls or fill empty / default BindingConfig on any actions
|
||||||
let controls = Self(hashmap! {
|
let controls = Self(HashMap::new());
|
||||||
Action::LeverEditorForward => BindingConfig::new(
|
|
||||||
Bindings::new(
|
|
||||||
vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))],
|
|
||||||
OptionsMode::Customizable,
|
|
||||||
),
|
|
||||||
ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable),
|
|
||||||
),
|
|
||||||
Action::LevelEditorBackward => BindingConfig::new(
|
|
||||||
Bindings::new(
|
|
||||||
vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::S))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))],
|
|
||||||
OptionsMode::Customizable,
|
|
||||||
),
|
|
||||||
ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable),
|
|
||||||
),
|
|
||||||
Action::LevelEditorLeft => BindingConfig::new(
|
|
||||||
Bindings::new(
|
|
||||||
vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::A))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))],
|
|
||||||
OptionsMode::Customizable,
|
|
||||||
),
|
|
||||||
ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable),
|
|
||||||
),
|
|
||||||
Action::LevelEditorRight => BindingConfig::new(
|
|
||||||
Bindings::new(
|
|
||||||
vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::D))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))],
|
|
||||||
OptionsMode::Customizable,
|
|
||||||
),
|
|
||||||
ActivationOptions::new(ActivationMode::Hold, OptionsMode::Immutable),
|
|
||||||
),
|
|
||||||
Action::LevelEditorFly => BindingConfig::new(
|
|
||||||
Bindings::new(
|
|
||||||
vec![Binding::from(ButtonCombination::Single(InputType::Keyboard(KeyCode::Space))).with_condition(BindingCondition::InGameState(GameState::LevelEditor))],
|
|
||||||
OptionsMode::Customizable,
|
|
||||||
),
|
|
||||||
ActivationOptions::new(ActivationMode::Hold, OptionsMode::Customizable),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// If you see this error, you may add new action in menu_actions
|
// If you see this error, you may add new action in menu_actions
|
||||||
// or make sure that you have only one MenuAction with the same name in the MenuActions
|
// or make sure that you have only one MenuAction with the same name in the MenuActions
|
||||||
@@ -545,14 +487,14 @@ impl Default for Controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controls {
|
impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||||
/// Returns bindings for action
|
/// Returns bindings for action
|
||||||
pub fn get(&self, action: Action) -> Option<&BindingConfig> {
|
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
|
||||||
self.0.get(&action)
|
self.0.get(&action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push new binding for action
|
/// Push new binding for action
|
||||||
pub fn push(&mut self, action: Action, binding: Binding) {
|
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
|
||||||
// TODO: warning if config is not exist yet
|
// TODO: warning if config is not exist yet
|
||||||
self.0
|
self.0
|
||||||
.entry(action)
|
.entry(action)
|
||||||
@@ -562,7 +504,7 @@ impl Controls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all bindings for action
|
/// Remove all bindings for action
|
||||||
pub fn remove(&mut self, action: Action) {
|
pub fn remove(&mut self, action: A) {
|
||||||
// TODO: warning if config is not exist yet
|
// TODO: warning if config is not exist yet
|
||||||
self.0
|
self.0
|
||||||
.entry(action)
|
.entry(action)
|
||||||
@@ -571,13 +513,13 @@ impl Controls {
|
|||||||
.clear();
|
.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_binding(&mut self, action: Action, binding: Binding) {
|
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
|
||||||
if let Some(config) = self.0.get_mut(&action) {
|
if let Some(config) = self.0.get_mut(&action) {
|
||||||
config.bindings.retain(|b| *b == binding);
|
config.bindings.retain(|b| *b == binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&Action, &BindingConfig)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&A, &BindingConfig<Gs>)> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/// Creates a [`HashMap`](bevy_utils::HashMap) from a list of key-value pairs.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```ignore
|
||||||
|
/// let map = hashmap! {
|
||||||
|
/// "key1" => "value1",
|
||||||
|
/// "key2" => "value2"
|
||||||
|
/// };
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! hashmap {
|
||||||
|
($( $key: expr => $val: expr ),*) => {{
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
$(
|
||||||
|
map.insert($key, $val);
|
||||||
|
)*
|
||||||
|
map
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies common traits conditionally to items.
|
||||||
|
///
|
||||||
|
/// This macro is designed to reduce repetition and maintenance overhead by
|
||||||
|
/// automatically deriving specified traits for each item passed to it based
|
||||||
|
/// on the feature flags. It's intended for use with almost all types in this module.
|
||||||
|
///
|
||||||
|
/// # Features:
|
||||||
|
/// - `serialize`: Derives `Serialize` and `Deserialize` traits and applies
|
||||||
|
/// reflection for serialization if the `serialize` feature is enabled.
|
||||||
|
/// - `reflect`: Derives the `Reflect` trait if the `reflect` feature is enabled.
|
||||||
|
/// - `inspector-egui`: Derives `InspectorOptions` trait and applies reflection
|
||||||
|
/// for the inspector if the `inspector-egui` feature is enabled.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
/// To use this macro, wrap any item(s) you want to apply the common traits to.
|
||||||
|
/// Each item can be a struct or enum that you want to derive traits for based
|
||||||
|
/// on the feature flags.
|
||||||
|
///
|
||||||
|
/// # Example:
|
||||||
|
/// ```ignore
|
||||||
|
/// common_traits_conditions! {
|
||||||
|
/// struct MyStruct {
|
||||||
|
/// // fields
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// enum MyEnum {
|
||||||
|
/// // variants
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! common_traits_conditions {
|
||||||
|
($($item:item)*) => {
|
||||||
|
$(
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(Serialize, Deserialize),
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "reflect",
|
||||||
|
derive(Reflect),
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(feature = "reflect", feature = "serialize"),
|
||||||
|
reflect(Serialize, Deserialize),
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "inspector-egui",
|
||||||
|
derive(InspectorOptions),
|
||||||
|
reflect(InspectorOptions),
|
||||||
|
)]
|
||||||
|
$item
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test {
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use log::Level;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deref, DerefMut)]
|
||||||
|
pub struct Times(u64);
|
||||||
|
|
||||||
|
impl Into<u64> for Times {
|
||||||
|
fn into(self) -> u64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<usize> for Times {
|
||||||
|
fn into(self) -> usize {
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u32> for Times {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
self.0 as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<i32> for Times {
|
||||||
|
fn into(self) -> i32 {
|
||||||
|
self.0 as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for Times {
|
||||||
|
fn from(times: u64) -> Self {
|
||||||
|
Self(times)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for Times {
|
||||||
|
fn from(times: usize) -> Self {
|
||||||
|
Self(times as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Times {
|
||||||
|
fn from(times: u32) -> Self {
|
||||||
|
Self(times as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for Times {
|
||||||
|
fn from(times: i32) -> Self {
|
||||||
|
Self(times as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Times {
|
||||||
|
/// Value that may be enough for most cases
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(100000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable logging for debug
|
||||||
|
pub fn enable_loggings() {
|
||||||
|
use std::env;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
let _ = env::set_var("RUST_LOG", "debug");
|
||||||
|
// FIXME: colorize logs
|
||||||
|
// TODO: colorize thorwed args
|
||||||
|
let _ = env_logger::builder()
|
||||||
|
.is_test(true)
|
||||||
|
.format(|buf, record| {
|
||||||
|
let mut style = buf.style();
|
||||||
|
let level = record.level();
|
||||||
|
match level {
|
||||||
|
Level::Trace => style.set_color(env_logger::fmt::Color::Magenta),
|
||||||
|
Level::Debug => style.set_color(env_logger::fmt::Color::Blue),
|
||||||
|
Level::Info => style.set_color(env_logger::fmt::Color::Green),
|
||||||
|
Level::Warn => style.set_color(env_logger::fmt::Color::Yellow),
|
||||||
|
Level::Error => style.set_color(env_logger::fmt::Color::Red),
|
||||||
|
};
|
||||||
|
|
||||||
|
writeln!(buf, "{}: {}", style.value(level), record.args())
|
||||||
|
})
|
||||||
|
.try_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure time of predicate
|
||||||
|
pub fn measure_time<F: Copy>(predicate: F, times: Times) -> Duration
|
||||||
|
where
|
||||||
|
F: FnOnce() -> (),
|
||||||
|
{
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..times.clone().into() {
|
||||||
|
predicate();
|
||||||
|
}
|
||||||
|
let global_duration = start.elapsed();
|
||||||
|
global_duration / times.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
/// Creates a [`HashMap`](bevy_utils::HashMap) from a list of key-value pairs.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```ignore
|
|
||||||
/// let map = hashmap! {
|
|
||||||
/// "key1" => "value1",
|
|
||||||
/// "key2" => "value2"
|
|
||||||
/// };
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! hashmap {
|
|
||||||
($( $key: expr => $val: expr ),*) => {{
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
$(
|
|
||||||
map.insert($key, $val);
|
|
||||||
)*
|
|
||||||
map
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies common traits conditionally to items.
|
|
||||||
///
|
|
||||||
/// This macro is designed to reduce repetition and maintenance overhead by
|
|
||||||
/// automatically deriving specified traits for each item passed to it based
|
|
||||||
/// on the feature flags. It's intended for use with almost all types in this module.
|
|
||||||
///
|
|
||||||
/// # Features:
|
|
||||||
/// - `serialize`: Derives `Serialize` and `Deserialize` traits and applies
|
|
||||||
/// reflection for serialization if the `serialize` feature is enabled.
|
|
||||||
/// - `reflect`: Derives the `Reflect` trait if the `reflect` feature is enabled.
|
|
||||||
/// - `inspector-egui`: Derives `InspectorOptions` trait and applies reflection
|
|
||||||
/// for the inspector if the `inspector-egui` feature is enabled.
|
|
||||||
///
|
|
||||||
/// # Usage
|
|
||||||
/// To use this macro, wrap any item(s) you want to apply the common traits to.
|
|
||||||
/// Each item can be a struct or enum that you want to derive traits for based
|
|
||||||
/// on the feature flags.
|
|
||||||
///
|
|
||||||
/// # Example:
|
|
||||||
/// ```ignore
|
|
||||||
/// common_traits_conditions! {
|
|
||||||
/// struct MyStruct {
|
|
||||||
/// // fields
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// enum MyEnum {
|
|
||||||
/// // variants
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! common_traits_conditions {
|
|
||||||
($($item:item)*) => {
|
|
||||||
$(
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "serialize",
|
|
||||||
derive(Serialize, Deserialize),
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "reflect",
|
|
||||||
derive(Reflect),
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
all(feature = "reflect", feature = "serialize"),
|
|
||||||
reflect(Serialize, Deserialize),
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "inspector-egui",
|
|
||||||
derive(InspectorOptions),
|
|
||||||
reflect(InspectorOptions),
|
|
||||||
)]
|
|
||||||
$item
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user