ci: some submodule magic

This commit is contained in:
2024-02-08 15:23:18 +01:00
parent 6143d92ae5
commit 5fc1382ceb
9 changed files with 38 additions and 34 deletions
-31
View File
@@ -1,31 +0,0 @@
[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"
[[example]]
name = "basic"
path = "example/basic/src/main.rs"
doc-scrape-examples = true
@@ -1,79 +0,0 @@
#!/bin/sh
while [ "$#" -gt 0 ]; do
case $1 in
-h|-?|--help|help)
HELP=1
;;
-*)
echo "Error: Unsupported flag $1" >&2
return 1
;;
*) # No more options
break
;;
esac
shift
done
if [ "$HELP" ]; then
printf "\
usage %s <path/to/Cargoctoml>\n
-?|-h|--help\tthis message
" "$(basename "$0")"
exit
fi
generate_combinations() {
items="$1"
size=$2
current=$3
combination="$4"
if [ "$current" -eq "$size" ]; then
echo "$combination"
return
fi
# Split items into positional parameters
set -- $items
while [ $# -gt 0 ]; do
item=$1
shift
new_combination="$combination $item"
remaining_items="$@"
generate_combinations "$remaining_items" $size $(($current + 1)) "$new_combination"
set -- "$item" $@
done
}
CARGO_PATH="$1"
if [ -z "$CARGO_PATH" ]; then
printf "please provide path to Cargo.toml\n"
exit
fi
if [ -d "$CARGO_PATH" ]; then
CARGO_PATH="${CARGO_PATH}/Cargo.toml"
fi
features=$(grep '^\[features\]' "$CARGO_PATH" -A99999999 | grep '^\[' -m 2 -B99999999 | grep -v '^\[' | cut -d '=' -f 1 | grep -v 'default')
num_features=$(echo "$features" | wc -w | tr -d ' ')
i=1
while [ $i -le $num_features ]; do
echo "Generating combinations of size $i"
combos=$(generate_combinations "$features" $i 0 "")
echo "$combos" | while IFS= read -r line; do
combo=$(echo $line | xargs) # Trim
echo "Running clippy with features: $combo"
if ! cargo clippy --features "$combo"; then
echo "Error found in combination: $combo"
exit 1
fi
done
i=$((i + 1))
done
-176
View File
@@ -1,176 +0,0 @@
use std::hash::Hash;
use bevy_ecs::{system::Resource, schedule::States};
use strum::IntoEnumIterator;
#[cfg(feature = "inspector-egui")]
use bevy_inspector_egui::inspector_options::InspectorOptionsType;
// #[cfg(all(feature = "reflect", feature = "serialize"))]
// use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "serialize")]
use serde::{Serialize, de::DeserializeOwned};
#[cfg(feature = "reflect")]
use bevy_reflect::{Reflect, TypePath, FromReflect};
use crate::resource::PlayerInputs;
pub trait ActionInner: 'static + std::marker::Sync + std::marker::Send + PartialEq + Eq + Hash + IntoEnumIterator + Clone + Copy { }
pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States { }
pub trait InputsContainerInner<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(feature = "serialize")]
pub trait SerializeImpl: Serialize + DeserializeOwned { }
#[cfg(feature = "reflect")]
pub trait ReflectImpl: Reflect + TypePath + FromReflect { }
#[cfg(feature = "inspector-egui")]
pub trait InspectorEguiImpl: InspectorOptionsType { }
// ===== Action =====
// SAFATY: inspector-egui includes reflect
#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait Action: ActionInner {}
#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait Action: ActionInner + SerializeImpl {}
#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))]
pub trait Action: ActionInner + ReflectImpl {}
#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))]
pub trait Action: ActionInner + SerializeImpl + ReflectImpl{}
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait Action: ActionInner + ReflectImpl + InspectorEguiImpl {}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait Action: ActionInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
// ===== GameState =====
// SAFATY: inspector-egui includes reflect
#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait GameState: GameStateInner {}
#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait GameState: GameStateInner + SerializeImpl {}
#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))]
pub trait GameState: GameStateInner + ReflectImpl {}
#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))]
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl{}
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
// ===== InputsContainer =====
// SAFATY: inspector-egui includes reflect
#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> {}
#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + SerializeImpl {}
#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + ReflectImpl {}
#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + SerializeImpl + ReflectImpl{}
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + ReflectImpl + InspectorEguiImpl {}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
// #[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 {}
// }
-6
View File
@@ -1,6 +0,0 @@
pub mod resource;
pub mod plugin;
pub mod contract;
mod util;
pub use util::*;
-238
View File
@@ -1,238 +0,0 @@
use bevy_app::{Plugin, App, Update};
use bevy_ecs::{system::{Res, ResMut}, schedule::State};
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, Gs>>();
}
}
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;
}
}
}
}
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!("cannot find me in inputs container")
}
}
}
// #[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);
// }
// }
-527
View File
@@ -1,527 +0,0 @@
use std::error;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::system::Resource;
use bevy_input::{keyboard::KeyCode, mouse::MouseButton};
#[cfg(feature = "inspector-egui")]
use bevy_inspector_egui::prelude::*;
#[cfg(all(feature = "reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::HashMap;
use log::warn;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::mem::discriminant;
use strum::IntoEnumIterator;
#[cfg(feature = "reflect")]
use {bevy_ecs::prelude::ReflectResource, bevy_reflect::Reflect};
use crate::{common_traits_conditions, contract::{Action, GameState}};
/// Validate that all enum variants are in the hash map
pub fn validate_hash_map<K, V>(hash_map: &HashMap<K, V>) -> bool
where
K: Eq + std::hash::Hash + Copy + IntoEnumIterator,
K::Iterator: Iterator<Item = K>,
{
let all_keys = K::iter().collect::<Vec<_>>();
if hash_map.len() != all_keys.len() {
return false;
}
for key in all_keys {
if !hash_map.contains_key(&key) {
return false;
}
}
true
}
common_traits_conditions! {
/// Struct that contains user's inputs corresponding to actions
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
pub struct Inputs<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
/// Resource that contains current user inputs
#[derive(Debug, PartialEq, Clone)]
pub struct PlayerInputs<A: Action> {
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
current: Inputs<A>,
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
previous: Inputs<A>,
}
}
impl<A: Action> Default for PlayerInputs<A> {
fn default() -> Self {
PlayerInputs {
current: Inputs(
A::iter()
.map(|action| (action, InputValue::Empty))
.collect(),
),
previous: Inputs(
A::iter()
.map(|action| (action, InputValue::Empty))
.collect(),
),
}
}
}
impl<A: Action> PlayerInputs<A> {
/// Returns current input value for action
///
/// in any case faster that `get_many` method
pub fn get(&self, action: A) -> InputValue {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
*self.current.get(&action).unwrap()
}
/// Returns current input values for actions
pub fn get_many(&self, actions: Vec<A>) -> Vec<InputValue> {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
actions
.iter()
.map(|action| *self.current.get(action).unwrap())
.collect()
}
/// 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`
pub fn just_pressed(&self, action: A) -> bool {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() {
return current && !previous;
}
panic!("Previous input is not boolean");
}
panic!("This input is not boolean")
}
/// 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()`
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() {
return Ok(current && !previous);
}
return Err("Previous input is not boolean".into());
}
Err("This input is not boolean".into())
}
/// Set input value to new
pub fn forced_set(&mut self, action: A, value: impl Into<InputValue>) {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
let current_input = self.current.get_mut(&action).unwrap();
*self.previous.get_mut(&action).unwrap() = *current_input;
*current_input = value.into();
}
/// Set input value to new
/// if it is not the same InputValue type return `Error()`
pub fn set(
&mut self,
action: A,
value: impl Into<InputValue>,
) -> Result<(), Box<dyn error::Error>> {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
let current_input = self.current.get_mut(&action).unwrap();
let previos_input = self.previous.get_mut(&action).unwrap();
// check if enum type is the same ignoring value
if discriminant(current_input) == discriminant(previos_input) {
*previos_input = *current_input;
*current_input = value.into();
Ok(())
} else {
Err("Input type is not the same".into())
}
}
/// Update current inputs
pub fn forced_update(&mut self, inputs: Inputs<A>) {
*self.previous = self.current.clone().0;
*self.current = inputs.0;
}
/// Update current inputs
/// if any input is not the same InputValue type return `Error()`
/// ! remember that it work safely but slow
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
for (action, value) in inputs.0.iter() {
let current_input = self.current.get_mut(action).unwrap();
let previos_input = self.previous.get_mut(action).unwrap();
// check if enum type is not the same ignoring value
if discriminant(current_input) != discriminant(previos_input) {
return Err("Input type is not the same".into());
}
*previos_input = *current_input;
*current_input = *value;
}
Ok(())
}
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone)]
pub enum InputType {
Keyboard(KeyCode),
Mouse(MouseInput),
// TODO: Gamepad(GamepadButtonType),
// TODO: Touch screen
}
#[derive(Debug, PartialEq, Clone)]
pub enum MouseInput {
Button(MouseButton),
Axis(AxisName),
Wheel(AxisName)
// TODO: CursorPosition
// TODO: TouchPad inputs
}
/// Name of axis
#[derive(Debug, PartialEq, Clone)]
pub enum AxisName {
Horizontal,
Vertical,
}
/// Represents a binding that can be changed by the player
#[derive(Debug, PartialEq, Clone)]
pub enum OptionsMode {
/// Represents a binding that cannot be changed as it is crucial to the game logic
Immutable,
/// Represents a binding that the player can customize
Customizable,
}
/// Represents the manner in which input is registered
#[derive(Debug, PartialEq, Clone)]
pub enum ActivationMode {
/// for<'a> Action<'a> is triggered when the button is pressed
Hold,
/// for<'a> Action<'a> is triggered when the button is pressed
/// and cannot be triggered again until the button is released
/// you don't need to release all buttons in chord
/// you can release only last pressed button
Tap,
}
pub struct ActivationOptions {
/// Manner in which input is registered
pub mode: ActivationMode,
/// Possibility to change this activation mode
pub option: OptionsMode,
/// Delay between activation
pub delay: f32,
}
}
impl Default for ActivationOptions {
fn default() -> Self {
Self {
mode: ActivationMode::Tap, // Because it is more safe
option: OptionsMode::Immutable,
delay: 0.05, // 20 times per second; 14 - world record?
}
}
}
impl ActivationOptions {
pub fn new(mode: ActivationMode, option: OptionsMode) -> Self {
Self {
mode,
option,
..Default::default()
}
}
pub fn with_delay(mut self, delay: f32) -> Self {
self.delay = delay;
self
}
}
common_traits_conditions! {
/// Represents a combination of buttons that triggers an action
#[derive(Debug, PartialEq, Clone)]
pub enum ButtonCombination {
/// Single button press
Single(InputType),
/// Chord is a combination of buttons that must be pressed at the same time
Chord(Vec<InputType>),
}
#[derive(Debug, PartialEq, Clone)]
pub enum BindingCondition<Gs: GameState> {
/// During specific game state
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
InGameState(Gs),
// TODO: Custom condition that have a predicate that returns bool
}
/// Binding is a combination of buttons that triggers action
#[derive(Debug, PartialEq, Clone)]
pub struct Binding<Gs: GameState> {
pub input: ButtonCombination,
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
pub conditions: Vec<BindingCondition<Gs>>,
}
}
impl<Gs: GameState> From<ButtonCombination> for Binding<Gs> {
fn from(input: ButtonCombination) -> Self {
Binding {
input,
conditions: Vec::new(),
}
}
}
impl<Gs: GameState> Binding<Gs> {
pub fn new(input: ButtonCombination) -> Self {
Self {
input,
conditions: Vec::new(),
}
}
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
self.conditions.push(condition);
self
}
}
common_traits_conditions! {
/// Input value that can be used in game logic
#[derive(Default, Clone, Copy, PartialEq, Debug)]
pub enum InputValue {
#[default]
Empty,
Boolean(bool),
Float(f32),
}
}
impl From<bool> for InputValue {
fn from(value: bool) -> Self {
InputValue::Boolean(value)
}
}
impl From<f32> for InputValue {
fn from(value: f32) -> Self {
InputValue::Float(value)
}
}
impl From<InputValue> for bool {
fn from(value: InputValue) -> Self {
match value {
InputValue::Boolean(value) => value,
_ => panic!("InputValue is not boolean"),
}
}
}
impl From<InputValue> for f32 {
fn from(value: InputValue) -> Self {
match value {
InputValue::Float(value) => value,
_ => panic!("InputValue is not float"),
}
}
}
impl InputValue {
pub fn is_empty(&self) -> bool {
matches!(self, InputValue::Empty)
}
pub fn is_boolean(&self) -> bool {
matches!(self, InputValue::Boolean(_))
}
pub fn is_float(&self) -> bool {
matches!(self, InputValue::Float(_))
}
pub fn as_boolean(&self) -> bool {
match self {
InputValue::Boolean(value) => *value,
InputValue::Float(value) => *value > 0.0,
InputValue::Empty => false,
}
}
}
common_traits_conditions! {
pub struct Bindings<Gs: GameState> {
/// List of bindings for action
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
list: Vec<Binding<Gs>>,
/// Possibility to change this binding
options: OptionsMode,
}
}
impl<Gs: GameState> Default for Bindings<Gs> {
fn default() -> Self {
Self {
list: Vec::new(),
options: OptionsMode::Immutable,
}
}
}
impl<Gs: GameState> Bindings<Gs> {
pub fn new(bindings: Vec<Binding<Gs>>, options: OptionsMode) -> Self {
Self {
list: bindings,
options,
}
}
pub fn push(&mut self, binding: Binding<Gs>) {
if self.options == OptionsMode::Immutable {
warn!("You try to push binding to immutable bindings");
return;
}
self.list.push(binding);
}
pub fn clear(&mut self) {
if self.options == OptionsMode::Immutable {
warn!("You try to clear immutable bindings");
return;
}
self.list.clear();
}
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&Binding<Gs>) -> bool,
{
if self.options == OptionsMode::Immutable {
warn!("You try to retain immutable bindings");
return;
}
self.list.retain(f);
}
pub fn iter(&self) -> impl Iterator<Item = &Binding<Gs>> {
self.list.iter()
}
pub fn iter_mut(
&mut self,
) -> Result<impl Iterator<Item = &mut Binding<Gs>>, Box<dyn error::Error>> {
if self.options == OptionsMode::Immutable {
warn!("You try to iterate over immutable bindings");
return Err("You try to iterate over immutable bindings".into());
}
Ok(self.list.iter_mut())
}
pub fn is_customizable(&self) -> bool {
self.options == OptionsMode::Customizable
}
pub fn is_immutable(&self) -> bool {
self.options == OptionsMode::Immutable
}
}
common_traits_conditions! {
/// Contains all bindings for action
/// and different activation options
#[derive(Default)]
pub struct BindingConfig<Gs: GameState> {
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
pub bindings: Bindings<Gs>,
#[cfg_attr(feature = "serialize", serde(skip))]
pub activation: ActivationOptions,
}
}
impl<Gs: GameState> BindingConfig<Gs> {
pub fn new(bindings: Bindings<Gs>, activation: ActivationOptions) -> Self {
Self {
bindings,
activation,
}
}
}
common_traits_conditions! {
/// Contains all bindings for actions
#[derive(Resource)]
#[cfg_attr(feature = "reflect", reflect(Resource))]
// TODO: Add delay for input
pub struct Controls<A: Action, Gs: GameState>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, BindingConfig<Gs>>); // TODO: change HashMap to Vec for faster iteration
}
impl<A: Action, Gs: GameState> Default for Controls<A, Gs> {
fn default() -> Self {
// TODO: default must create empty controls or fill empty / default BindingConfig on any actions
let controls = Self(HashMap::new());
// If you see this error, you may add new action in menu_actions
// or make sure that you have only one Menufor<'a> Action<'a> with the same name in the Menufor<'a> Action<'a>s
assert!(validate_hash_map(&controls.0));
controls
}
}
impl<A: Action, Gs: GameState> Controls<A, Gs> {
/// Returns bindings for action
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
self.0.get(&action)
}
/// Push new binding for action
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
// TODO: warning if config is not exist yet
self.0
.entry(action)
.or_insert_with(BindingConfig::default)
.bindings
.push(binding);
}
/// Remove all bindings for action
pub fn remove(&mut self, action: A) {
// TODO: warning if config is not exist yet
self.0
.entry(action)
.or_insert_with(BindingConfig::default)
.bindings
.clear();
}
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
if let Some(config) = self.0.get_mut(&action) {
config.bindings.retain(|b| *b == binding);
}
}
pub fn iter(&self) -> impl Iterator<Item = (&A, &BindingConfig<Gs>)> {
self.0.iter()
}
}
-179
View File
@@ -1,179 +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
)*
};
}
#[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()
}
}