feate: separate on crates

This commit is contained in:
2024-02-15 18:49:29 +01:00
parent 8335cb4750
commit 3f08d92e3a
22 changed files with 35 additions and 35 deletions
+31
View File
@@ -0,0 +1,31 @@
[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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
[package]
name = "basic"
version = "0.1.0"
edition = "2021"
[features]
default_font = []
x11 = ["bevy/x11"]
wayland = ["bevy/wayland"]
windows = ["bevy/bevy_winit"]
[dependencies]
bevy = "0.12.1"
bevy_controls = { path = "./../../" }
strum = "0.25.0"
strum_macros = "0.25.3"
@@ -0,0 +1,93 @@
Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,67 @@
Fira Code Variable Font
=======================
This download contains Fira Code as both a variable font and static fonts.
Fira Code is a variable font with this axis:
wght
This means all the styles are contained in a single file:
FiraCode-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that aren’t available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Fira Code:
static/FiraCode-Light.ttf
static/FiraCode-Regular.ttf
static/FiraCode-Medium.ttf
static/FiraCode-SemiBold.ttf
static/FiraCode-Bold.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects – print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.
+85
View File
@@ -0,0 +1,85 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1707092692,
"narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "faf912b086576fd1a15fca610166c98d47bc667e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1707358215,
"narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
@@ -0,0 +1,52 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
flake-utils.lib.eachDefaultSystem
(system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
rustToolchain = (pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml);
nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ];
buildInputs = with pkgs; [
openssl
alsa-lib
vulkan-loader
xorg.libX11
xorg.libXrandr
xorg.libXcursor
xorg.libXi
wayland
libxkbcommon
udev
shellcheck
file
];
in
with pkgs;
{
devShells.default = mkShell {
inherit buildInputs nativeBuildInputs;
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
};
}
);
}
@@ -0,0 +1,2 @@
[toolchain]
channel = '1.72.0'
@@ -0,0 +1,233 @@
//! This example illustrates how to create
//!
//! It displays
//!
//! Yeah I know, now it's a bit ugly
use bevy::{
ecs::schedule::States,
prelude::*,
};
use bevy_controls::{
contract::{
Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner,
},
plugin::ControlsPlugin,
resource::{Binding, ButtonCombination, Controls, InputType, InputValue, Inputs, PlayerInputs},
};
use strum_macros::EnumIter;
const FIRA_CODE_PATH: &str = "font/FiraCode/FiraCode-VariableFont_wght.ttf";
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug)]
enum MyAction {
Forward,
Back,
Left,
Right,
Up,
}
// TODO: I realy need proc macro
impl Action for MyAction {}
impl ActionInner for MyAction {}
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
enum MyGameState {
#[default]
InGame,
}
impl GameState for MyGameState {}
impl GameStateInner for MyGameState {}
#[derive(Resource, Default, Clone, Debug)]
struct MyInputsContainer {
// When the game does not provide multiplayer, one field is enough
player_inputs: PlayerInputs<MyAction>,
}
impl InputsContainer<MyAction> for MyInputsContainer {}
impl InputsContainerInner<MyAction> for MyInputsContainer {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<MyAction>> + 'a> {
todo!()
}
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<MyAction>> {
Some(& self.player_inputs)
}
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<MyAction>> {
Some(&mut self.player_inputs)
}
}
#[derive(Resource, Default, Deref, DerefMut)]
struct JumpsCount(i32);
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin {
file_path: "asset".into(),
..default()
}),
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(),
))
.init_resource::<JumpsCount>()
.add_systems(Startup, (add_bindings, setup))
.add_systems(Update, text_update_system)
.run();
}
// TODO: default bingings...
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
controls.force_push(
MyAction::Left,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::A))),
);
controls.force_push(
MyAction::Left,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::H))),
);
controls.force_push(
MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::S))),
);
controls.force_push(
MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::J))),
);
controls.force_push(
MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))),
);
controls.force_push(
MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::K))),
);
controls.force_push(
MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::D))),
);
controls.force_push(
MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::L))),
);
controls.force_push(
MyAction::Up,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::Space))),
);
}
#[derive(Component)]
struct TextBlock;
const TEXT_SIZE: f32 = 30.;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
commands.spawn((
TextBundle::from_sections([
TextSection::new(
"\nAction: ",
TextStyle {
font: asset_server.load(FIRA_CODE_PATH),
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Left (H/A) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Back (J/S) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Forward (K/W) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Right (L/D) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Up (Space)",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
]),
TextBlock,
));
}
// text sections indexes
const MOVE_LEFT_INDEX: usize = 1;
const MOVE_BACK_INDEX: usize = 2;
const MOVE_FORWARD_INDEX: usize = 3;
const MOVE_RIGHT_INDEX: usize = 4;
const JUMP_INDEX: usize = 5;
fn text_update_system(
mut query: Query<&mut Text, With<TextBlock>>,
inputs_container: Res<MyInputsContainer>,
mut jumps_count: ResMut<JumpsCount>,
) {
for text in query.iter_mut() {
let player_inputs = inputs_container.me().expect("This is bad");
let mut text = update_text(
text,
player_inputs.get(MyAction::Left),
MOVE_LEFT_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::Back),
MOVE_BACK_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::Forward),
MOVE_FORWARD_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::Right),
MOVE_RIGHT_INDEX);
if player_inputs.get_just_pressed(MyAction::Up).unwrap_or(false) {
**jumps_count += 1;
text.sections[JUMP_INDEX].value = format!("Up (Space) - {}", **jumps_count);
}
}
}
fn update_text(mut text: Mut<Text>, input_value: InputValue, index: usize) -> Mut<Text>{
if input_value.is_boolean() && input_value.to_boolean() {
text.sections[index].style.color = Color::GOLD;
} else {
text.sections[index].style.color = Color::WHITE;
}
text
}
@@ -0,0 +1,77 @@
#!/bin/bash
CURDIR="$(realpath "$(dirname "$0")")"
. "${CURDIR}/../utils/sh/log.sh"
log "enable logging"
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
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\]' $1 -A99999999 | grep '^\[' -m 2 -B99999999 | grep -v '^\[' | cut -d '=' -f 1 | grep -v 'default')
generate_combinations() {
local items=($1)
local size=$2
local current=$3
local combination=$4
if [ $current -eq $size ]; then
echo "$combination"
return
fi
for ((i = 0; i < ${#items[@]}; ++i)); do
new_combination="$combination ${items[$i]}"
remaining_items=("${items[@]:0:$i}" "${items[@]:$((i + 1))}")
generate_combinations "$(IFS=" "; echo "${remaining_items[*]}")" $size $((current + 1)) "$new_combination"
done
}
for ((i = 1; i <= $(wc -w <<< "$features"); ++i)); do
echo "Generating combinations of size $i"
combos=$(generate_combinations "$features" $i 0 "")
echo "$combos" | while read line
do
echo "Running clippy with features: $line"
combo=$(echo $combo | xargs) # Trim
echo "Running clippy with features: $combo"
if ! cargo clippy --features "$combo"; then
echo "Error found in combination: $combo"
exit 1
fi
done
done
+245
View File
@@ -0,0 +1,245 @@
use std::hash::Hash;
use bevy_ecs::{schedule::States, system::Resource};
#[cfg(feature = "inspector-egui")]
use bevy_inspector_egui::inspector_options::InspectorOptionsType;
use strum::IntoEnumIterator;
// #[cfg(all(feature = "reflect", feature = "serialize"))]
// use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "reflect")]
use bevy_reflect::{FromReflect, Reflect, TypePath};
#[cfg(feature = "serialize")]
use serde::{de::DeserializeOwned, Serialize};
use crate::resource::PlayerInputs;
pub trait ActionInner:
'static
+ std::marker::Sync
+ std::marker::Send
+ PartialEq
+ Eq
+ Hash
+ IntoEnumIterator
+ Clone
+ Copy
+ std::fmt::Debug
{
}
pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States + Default + std::fmt::Debug {}
pub trait InputsContainerInner<A: Action>:
'static + std::marker::Sync + std::marker::Send + Resource + Default + std::fmt::Debug
{
/// 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>(&'a self) -> Option<&'a PlayerInputs<A>>;
/// The method returning an mutable player inputs of this client.
fn me_mut<'a>(&'a mut 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
@@ -0,0 +1,6 @@
pub mod resource;
pub mod plugin;
pub mod contract;
mod util;
pub use util::*;
+260
View File
@@ -0,0 +1,260 @@
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<A, Ic, Gs> {
_action: std::marker::PhantomData<A>,
_inputs_container: std::marker::PhantomData<Ic>,
_game_state: std::marker::PhantomData<Gs>,
}
impl<A, Ic, Gs> Default for ControlsPlugin<A, Ic, Gs> {
fn default() -> Self {
Self {
_action: std::marker::PhantomData,
_inputs_container: std::marker::PhantomData,
_game_state: std::marker::PhantomData,
}
}
}
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>>()
.init_resource::<Ic>()
.add_state::<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>>,
mut inputs_container: ResMut<Ic>,
controls: Res<Controls<A, Gs>>,
game_state: Res<State<Gs>>,
) {
if let Some(inputs) = inputs_container.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;
}
}
}
}
log::trace!("action binding {:?} {:?} in condition", action, binding);
match &binding.input {
ButtonCombination::Single(button) => match button {
InputType::Keyboard(key) => {
log::trace!("presed?: {:?}", keyboard_input.pressed(*key));
log::trace!("active");
if keyboard_input.pressed(*key) {
inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another
}
inputs.forced_set(*action, false); // FIXME: this is happening on every pass so too frequently
}
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!();
}
}
}
}
}
log::trace!("active");
// TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type
inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another
}
}
}
}
} 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);
// }
// }
+566
View File
@@ -0,0 +1,566 @@
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()
}
// TODO
// /// Returns current input value for action
// ///
// /// in any case faster that `get_many` method
// pub fn all_active(&self) -> InputValue {
// *self.current.iter().filter(|a| {a.})
// }
/// 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")
}
// TODO this is must be binding option for non axis inputs
/// 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
///
/// notice: not recomended to use if you do not know what do you do
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()`
///
/// notice: not recomended to use if you do not know what do you do
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
///
/// notice: not recomended to use if you do not know what do you do
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
///
/// notice: not recomended to use if you do not know what do you do
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,
}
#[derive(Clone, Debug)]
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 to_boolean(&self) -> bool {
match self {
InputValue::Boolean(value) => *value,
InputValue::Float(value) => *value > 0.0,
InputValue::Empty => false,
}
}
}
common_traits_conditions! {
#[derive(Clone, Debug)]
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 force_push(&mut self, binding: Binding<Gs>) {
self.list.push(binding);
}
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, Clone, Debug)]
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, Clone)]
#[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 mut controls = HashMap::new();
for key in A::iter() {
controls.insert(key, BindingConfig::default());
}
// 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));
Self(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)
}
/// Forced push new binding for action
pub fn force_push(&mut self, action: A, binding: Binding<Gs>) {
// TODO: warning if config is not exist yet
// TODO: interface for [`ActivationOptions`]
self.0
.entry(action)
.or_insert_with(BindingConfig::default)
.bindings
.force_push(binding);
}
/// Push new binding for action
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
// TODO: warning if config is not exist yet
// TODO: interface for [`ActivationOptions`]
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
@@ -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()
}
}