Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added basic serialization #4

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ edition = "2018"

[dependencies]
bevy = { version = "0.5", default-features = false }
ron = { version = "*", optional = true }
serde = { version = "*", optional = true }

[dev-dependencies]
bevy = { version = "0.5", default-features = false, features = ["bevy_winit", "x11"] }

[features]
serialize = ["ron", "bevy/serialize", "serde"]
65 changes: 65 additions & 0 deletions examples/hello_world_serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use bevy::prelude::*;
use bevy_input_actionmap::*;
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(ActionPlugin::<Action>::default())
.add_startup_system(setup.system())
.add_system(run_commands.system())
.run();
}

#[derive(Hash, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
enum Action {
Select,
SuperSelect,
AwesomeSuperSelect,
}

#[cfg(feature = "serialize")]
fn setup(mut input: ResMut<InputMap<Action>>) {
if let Err(_) = input.load_from_path("keybinds.config") {
{ println!("no keybind config found creating default setup"); //just to show the path it took
input
.bind(Action::Select, KeyCode::Return)
.bind(Action::Select, GamepadButtonType::South)
.bind(Action::SuperSelect, vec![KeyCode::LAlt, KeyCode::Return])
.bind(Action::SuperSelect, vec![KeyCode::RAlt, KeyCode::Return])
// This should bind left/right control and left/right alt, but the combos would get ridiculous so hopefully this is sufficient.
.bind(
Action::AwesomeSuperSelect,
vec![KeyCode::LAlt, KeyCode::LControl, KeyCode::Return],
);
input.save_to_path("keybinds.config").unwrap()}
} else {//if it loaded custom keybinds dont add new ones
println!("keybinds loaded from local file") //just to show the path it took
}

}
#[cfg(not(feature = "serialize"))]
fn setup(mut input: ResMut<InputMap<Action>>){
println!("serialize feature is off so this is the same as hello_world_enum; Why just Why");
input
.bind(Action::Select, KeyCode::Return)
.bind(Action::Select, GamepadButtonType::South)
.bind(Action::SuperSelect, vec![KeyCode::LAlt, KeyCode::Return])
.bind(Action::SuperSelect, vec![KeyCode::RAlt, KeyCode::Return])
// This should bind left/right control and left/right alt, but the combos would get ridiculous so hopefully this is sufficient.
.bind(
Action::AwesomeSuperSelect,
vec![KeyCode::LAlt, KeyCode::LControl, KeyCode::Return],
);
}

fn run_commands(input: Res<InputMap<Action>>) {
if input.just_active(Action::Select) {
println!("Selected");
}
if input.just_active(Action::SuperSelect) {
println!("Super selected");
}
if input.just_active(Action::AwesomeSuperSelect) {
println!("Awesome super selected");
}
}
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ use bevy::{
prelude::*,
};

#[cfg(feature = "serialize")]
pub mod serialize;
#[cfg(feature = "serialize")]
pub use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct Binding {
keys: HashSet<KeyCode>,
gamepad_buttons: HashSet<GamepadButtonType>,
Expand Down Expand Up @@ -66,6 +72,7 @@ impl From<Vec<GamepadButtonType>> for Binding {
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub enum GamepadAxisDirection {
LeftStickXPositive,
LeftStickXNegative,
Expand Down Expand Up @@ -130,6 +137,7 @@ impl Binding {
}

#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct Action {
bindings: Vec<Binding>,
}
Expand Down
103 changes: 103 additions & 0 deletions src/serialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use super::*;
use serde::{Serialize, de::DeserializeOwned};

#[cfg(test)]
mod test{
const ACTION_SELECT: &str = "SELECT";
const ACTION_SUPER_SELECT: &str = "SUPER_SELECT";
const ACTION_AWESOME_SUPER_SELECT: &str = "AWESOME_SUPER_SELECT";
const TESTPATH : &str = "test.keymap";
use bevy::prelude::*;
use super::*;
#[derive(Hash, PartialEq, Eq, Clone, Serialize, serde::Deserialize, Debug)]
enum TestAction {
Select,
SuperSelect,
AwesomeSuperSelect,
}
fn test_binding<T>(to_test : T) where T : Into<Binding>{
let binding : Binding = to_test.into();
let serialized = ron::to_string(&binding).expect("Failed to serialize binding");
let deserialized : Binding = ron::from_str(&serialized).expect("Failed to deserialize binding");
assert_eq!(binding, deserialized);
}


fn test_action(action : Action){
let serialized = ron::to_string(&action).expect("Failed to serialize action");
let deserialized : Action = ron::from_str(&serialized).expect("Failed to deserialize action");
assert_eq!(action, deserialized)
}
#[test]
fn test_inputmap_string(){
let mut map = super::InputMap::<String>::default();
map.bind(ACTION_SELECT, KeyCode::Return)
.bind(ACTION_SELECT, GamepadButtonType::South)
.bind(ACTION_SUPER_SELECT, vec![KeyCode::LAlt, KeyCode::Return])
.bind(ACTION_SUPER_SELECT, vec![KeyCode::RAlt, KeyCode::Return])
// This should bind left/right control and left/right alt, but the combos would get ridiculous so hopefully this is sufficient.
.bind(
ACTION_AWESOME_SUPER_SELECT,
vec![KeyCode::LAlt, KeyCode::LControl, KeyCode::Return],
);
map.save_to_path(TESTPATH).expect("Failed to save string InputMap");
let new_map = InputMap::<String>::new_from_path(TESTPATH).expect("Failed to load string InputMap");
assert_eq!(map.actions, new_map.actions);
map.load_from_path(TESTPATH).expect("Failed to Load from path");
assert_eq!(map.actions, new_map.actions);
}

fn test_inputmap_enum(){
let mut map = super::InputMap::<TestAction>::default();
map.bind(TestAction::Select, KeyCode::Return)
.bind(TestAction::Select, GamepadButtonType::South)
.bind(TestAction::SuperSelect, vec![KeyCode::LAlt, KeyCode::Return])
.bind(TestAction::SuperSelect, vec![KeyCode::RAlt, KeyCode::Return])
// This should bind left/right control and left/right alt, but the combos would get ridiculous so hopefully this is sufficient.
.bind(
TestAction::AwesomeSuperSelect,
vec![KeyCode::LAlt, KeyCode::LControl, KeyCode::Return],
);
map.save_to_path(TESTPATH).expect("Failed to save enum InputMap");
let new_map = InputMap::<TestAction>::new_from_path(TESTPATH).expect("Failed to load enum InputMap");
assert_eq!(map.actions, new_map.actions);
map.load_from_path(TESTPATH).expect("Failed to Load from path");
assert_eq!(map.actions, new_map.actions);
}

#[test]
fn it_works(){
test_binding(KeyCode::Space);
test_binding(vec![KeyCode::Space, KeyCode::LControl]);
test_binding(GamepadButtonType::North);
test_binding(vec![GamepadButtonType::North, GamepadButtonType::LeftThumb]);
test_action(Action{
bindings : vec![KeyCode::Space.into(), KeyCode::J.into()]
});
test_inputmap_string();
test_inputmap_enum();
std::fs::remove_file(TESTPATH).expect("Failed to remove testsave file");
}
}

impl<T : Eq + Hash> InputMap<T> {
pub fn save_to_path(&self, path : &str) -> std::io::Result<()> where T : Serialize{
let contents = ron::ser::to_string_pretty(&self.actions, ron::ser::PrettyConfig::default()).expect("There was an error making the string");
std::fs::write(path, contents)
}
pub fn load_from_path(&mut self, path : &str) -> std::io::Result<()> where T : DeserializeOwned{
let ron_string = std::fs::read_to_string(path)?;
let actions = ron::from_str(&ron_string).expect("Failed to get actions from ron string");
self.actions = actions;
//may need to clear self here but i dont really know what that does
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're good but I'll validate. IIRC clear only clears the active state of keybindings to work around bevyengine/bevy#1700.

Ok(())
}
pub fn new_from_path(path : &str) -> std::io::Result<InputMap<T>> where T : DeserializeOwned{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use actual Paths here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be new_from_path<T: Into<Path>>(path T) but I don't know how to do that, because when I tried it told me Path is a private type; maybe I was just getting the wrong Path Type.

let ron_string = std::fs::read_to_string(path)?;
let actions = ron::from_str(&ron_string).expect("Failed to get actions from ron string");
Ok(InputMap{
actions,
..Default::default()
})
}
}