feate: separate on crates
This commit is contained in:
@@ -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
|
||||
+4115
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"
|
||||
Binary file not shown.
@@ -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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+85
@@ -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
|
||||
@@ -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 {}
|
||||
// }
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod resource;
|
||||
pub mod plugin;
|
||||
pub mod contract;
|
||||
mod util;
|
||||
|
||||
pub use util::*;
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user