use std::collections::HashMap;
use std::fmt;
use serde::de::{self, DeserializeSeed, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
use crate::{
ActionDecl, ActionKind, ActionMap, ActionMapError, Binding, BindingParseError, Device,
DoubleTap, GAMEPLAY_CONTEXT, Hold, Pattern, Tap,
};
pub const DEVICE_LISTS: [Device; 4] = [
Device::Keyboard,
Device::Pointer,
Device::Gamepad,
Device::Touch,
];
#[must_use]
pub const fn list_name(device: Device) -> &'static str {
match device {
Device::Keyboard => "keyboard",
Device::Pointer => "mouse",
Device::Gamepad => "gamepad",
Device::Touch => "touch",
}
}
const MS_PER_SECOND: f32 = 1000.0;
impl ActionMap {
pub fn from_ron(text: &str) -> Result<Self, BindingAssetError> {
let headers: Vec<Header> = ron::from_str(text).map_err(BindingAssetError::parse)?;
let mut kinds = HashMap::with_capacity(headers.len());
for header in headers {
kinds.entry(header.action).or_insert(header.kind);
}
let mut map = Self::new();
let mut refusal = None;
let read = ron::Options::default().from_str_seed(
text,
Records {
kinds: &kinds,
map: &mut map,
refusal: &mut refusal,
},
);
match read {
Ok(()) => Ok(map),
Err(error) => Err(match refusal {
Some(refusal) => BindingAssetError::at(&error, refusal),
None => BindingAssetError::parse(error),
}),
}
}
pub fn to_ron(&self) -> Result<String, AssetWriteError> {
let records = self
.slots
.iter()
.map(|slot| self.record(slot))
.collect::<Result<Vec<_>, _>>()?;
let pretty = ron::ser::PrettyConfig::new()
.new_line("\n")
.indentor(" ")
.depth_limit(2);
let mut text = ron::ser::to_string_pretty(&records, pretty)
.expect("a binding asset has no serializer path that can fail");
text.push('\n');
Ok(text)
}
fn record(&self, slot: &crate::ActionSlot) -> Result<ActionFile, AssetWriteError> {
let name = &slot.decl.name;
if slot.repeat.is_some() {
return Err(AssetWriteError::Repeat(name.clone()));
}
let mut file = ActionFile {
action: name.clone(),
kind: slot.decl.kind.into(),
context: self.contexts[slot.context].clone(),
keyboard: Vec::new(),
mouse: Vec::new(),
gamepad: Vec::new(),
touch: Vec::new(),
patterns: Vec::new(),
pad_chords_outrank: slot.pad_chords_outrank,
};
for binding in &slot.defaults {
file.list_mut(binding.device()).push(binding.to_string());
}
let read_back = DEVICE_LISTS
.iter()
.flat_map(|device| file.list(*device))
.map(String::as_str);
if !read_back.eq(slot.defaults.iter().map(ToString::to_string)) {
return Err(AssetWriteError::MixedLists(name.clone()));
}
let emits = |pattern| {
self.emits(name, pattern)
.map(str::to_owned)
.ok_or_else(|| AssetWriteError::NoEmit {
action: name.clone(),
pattern,
})
};
let ms = |seconds, pattern| {
whole_ms(seconds).ok_or_else(|| AssetWriteError::NotWholeMilliseconds {
action: name.clone(),
pattern,
})
};
if let Some(tap) = self.tap(name) {
let time = ms(tap.time(), Pattern::Tap)?;
file.patterns
.push(PatternFile::Tap(time, emits(Pattern::Tap)?));
}
if let Some(hold) = self.hold(name) {
let time = ms(hold.time(), Pattern::Hold)?;
file.patterns
.push(PatternFile::Hold(time, emits(Pattern::Hold)?));
}
if let Some(double) = self.double_tap(name) {
let tap_time = ms(double.tap_time(), Pattern::DoubleTap)?;
let window = ms(double.window(), Pattern::DoubleTap)?;
let target = emits(Pattern::DoubleTap)?;
file.patterns.push(if double.fires_on_release() {
PatternFile::DoubleTapOnRelease(tap_time, window, target)
} else {
PatternFile::DoubleTap(tap_time, window, target)
});
}
Ok(file)
}
}
fn whole_ms(seconds: f32) -> Option<u16> {
let ms = (seconds * MS_PER_SECOND).round() as u16;
(f32::from(ms) / MS_PER_SECOND == seconds).then_some(ms)
}
const fn pattern_name(pattern: Pattern) -> &'static str {
match pattern {
Pattern::Tap => "Tap",
Pattern::Hold => "Hold",
Pattern::DoubleTap => "DoubleTap",
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "Action", deny_unknown_fields)]
struct ActionFile {
action: String,
kind: KindFile,
#[serde(default = "gameplay", skip_serializing_if = "is_gameplay")]
context: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
keyboard: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
mouse: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
gamepad: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
touch: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
patterns: Vec<PatternFile>,
#[serde(default, skip_serializing_if = "is_false")]
pad_chords_outrank: bool,
}
impl ActionFile {
fn list(&self, device: Device) -> &[String] {
match device {
Device::Keyboard => &self.keyboard,
Device::Pointer => &self.mouse,
Device::Gamepad => &self.gamepad,
Device::Touch => &self.touch,
}
}
fn list_mut(&mut self, device: Device) -> &mut Vec<String> {
match device {
Device::Keyboard => &mut self.keyboard,
Device::Pointer => &mut self.mouse,
Device::Gamepad => &mut self.gamepad,
Device::Touch => &mut self.touch,
}
}
}
fn gameplay() -> String {
GAMEPLAY_CONTEXT.to_owned()
}
fn is_gameplay(context: &str) -> bool {
context == GAMEPLAY_CONTEXT
}
fn is_false(flag: &bool) -> bool {
!flag
}
#[derive(Deserialize)]
#[serde(rename = "Action")]
struct Header {
action: String,
kind: KindFile,
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Kind")]
enum KindFile {
Button,
Axis1,
Axis2,
}
impl From<ActionKind> for KindFile {
fn from(kind: ActionKind) -> Self {
match kind {
ActionKind::Button => Self::Button,
ActionKind::Axis1 => Self::Axis1,
ActionKind::Axis2 => Self::Axis2,
}
}
}
impl From<KindFile> for ActionKind {
fn from(kind: KindFile) -> Self {
match kind {
KindFile::Button => Self::Button,
KindFile::Axis1 => Self::Axis1,
KindFile::Axis2 => Self::Axis2,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "Pattern")]
enum PatternFile {
Tap(u16, String),
Hold(u16, String),
DoubleTap(u16, u16, String),
DoubleTapOnRelease(u16, u16, String),
}
impl PatternFile {
const fn pattern(&self) -> Pattern {
match self {
Self::Tap(..) => Pattern::Tap,
Self::Hold(..) => Pattern::Hold,
Self::DoubleTap(..) | Self::DoubleTapOnRelease(..) => Pattern::DoubleTap,
}
}
fn emits(&self) -> &str {
match self {
Self::Tap(_, emits)
| Self::Hold(_, emits)
| Self::DoubleTap(_, _, emits)
| Self::DoubleTapOnRelease(_, _, emits) => emits,
}
}
fn timed(&self) -> Option<Timed> {
let seconds = |ms: u16| f32::from(ms) / MS_PER_SECOND;
Some(match *self {
Self::Tap(ms, _) => Timed::Tap(Tap::new(seconds(ms))?),
Self::Hold(ms, _) => Timed::Hold(Hold::new(seconds(ms))?),
Self::DoubleTap(tap, window, _) => {
Timed::DoubleTap(DoubleTap::new(seconds(tap), seconds(window))?)
}
Self::DoubleTapOnRelease(tap, window, _) => {
Timed::DoubleTap(DoubleTap::new(seconds(tap), seconds(window))?.on_release())
}
})
}
}
enum Timed {
Tap(Tap),
Hold(Hold),
DoubleTap(DoubleTap),
}
impl Timed {
fn attach(self, map: &mut ActionMap, action: &str, emits: &str) -> Result<(), ActionMapError> {
let pattern = match self {
Self::Tap(tap) => {
map.set_tap(action, Some(tap))?;
Pattern::Tap
}
Self::Hold(hold) => {
map.set_hold(action, Some(hold))?;
Pattern::Hold
}
Self::DoubleTap(double) => {
map.set_double_tap(action, Some(double))?;
Pattern::DoubleTap
}
};
map.set_emits(action, pattern, Some(emits))
}
}
struct Records<'a> {
kinds: &'a HashMap<String, KindFile>,
map: &'a mut ActionMap,
refusal: &'a mut Option<AssetRefusal>,
}
impl Records<'_> {
fn declare(&mut self, record: ActionFile) -> Result<Vec<(Timed, String)>, AssetRefusal> {
let action = record.action.clone();
let mut bindings = Vec::new();
for device in DEVICE_LISTS {
for text in record.list(device) {
let binding =
text.parse::<Binding>()
.map_err(|error| AssetRefusal::BadBinding {
action: action.clone(),
error,
})?;
if binding.device() != device {
return Err(AssetRefusal::WrongList {
action,
binding: text.clone(),
list: device,
});
}
bindings.push(binding);
}
}
let mut seen = [false; Pattern::ALL.len()];
let mut timed = Vec::with_capacity(record.patterns.len());
for pattern in &record.patterns {
let kind = pattern.pattern();
let emits = pattern.emits();
let Some(times) = pattern.timed() else {
return Err(AssetRefusal::BadTime {
action,
pattern: kind,
});
};
timed.push((times, emits.to_owned()));
if std::mem::replace(&mut seen[kind.index()], true) {
return Err(AssetRefusal::DuplicatePattern {
action,
pattern: kind,
});
}
if emits == action {
return Err(AssetRefusal::EmitsItself {
action,
pattern: kind,
});
}
match self.kinds.get(emits) {
Some(KindFile::Button) => {}
Some(_) => {
return Err(AssetRefusal::EmitNotAButton {
action,
pattern: kind,
emits: emits.to_owned(),
});
}
None => {
return Err(AssetRefusal::UnknownEmit {
action,
pattern: kind,
emits: emits.to_owned(),
});
}
}
}
self.map
.try_declare_in(
&record.context,
ActionDecl {
name: record.action,
kind: record.kind.into(),
bindings,
},
)
.and_then(|()| {
self.map
.set_pad_chords_outrank(&action, record.pad_chords_outrank)
})
.map_err(AssetRefusal::Declare)?;
Ok(timed)
}
fn refuse<E: de::Error>(&mut self, refusal: AssetRefusal) -> E {
let error = E::custom(&refusal);
*self.refusal = Some(refusal);
error
}
}
impl<'de> DeserializeSeed<'de> for Records<'_> {
type Value = ();
fn deserialize<D: de::Deserializer<'de>>(self, deserializer: D) -> Result<(), D::Error> {
deserializer.deserialize_seq(self)
}
}
impl<'de> Visitor<'de> for Records<'_> {
type Value = ();
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a list of action records")
}
fn visit_seq<A: SeqAccess<'de>>(mut self, mut seq: A) -> Result<(), A::Error> {
let mut patterns = Vec::new();
while let Some(record) = seq.next_element::<ActionFile>()? {
let action = record.action.clone();
match self.declare(record) {
Ok(timed) => patterns.push((action, timed)),
Err(refusal) => return Err(self.refuse(refusal)),
}
}
for (action, timed) in patterns {
for (pattern, emits) in timed {
if let Err(error) = pattern.attach(self.map, &action, &emits) {
return Err(self.refuse(AssetRefusal::Declare(error)));
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BindingAssetError {
line: usize,
column: usize,
refusal: AssetRefusal,
}
impl BindingAssetError {
#[must_use]
pub const fn line(&self) -> usize {
self.line
}
#[must_use]
pub const fn column(&self) -> usize {
self.column
}
#[must_use]
pub const fn refusal(&self) -> &AssetRefusal {
&self.refusal
}
fn at(error: &ron::error::SpannedError, refusal: AssetRefusal) -> Self {
Self {
line: error.span.start.line,
column: error.span.start.col,
refusal,
}
}
fn parse(error: ron::error::SpannedError) -> Self {
let message = error.code.to_string();
Self::at(&error, AssetRefusal::Parse(message))
}
}
impl fmt::Display for BindingAssetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"line {}, column {}: {}",
self.line, self.column, self.refusal
)
}
}
impl std::error::Error for BindingAssetError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssetRefusal {
Parse(String),
BadBinding {
action: String,
error: BindingParseError,
},
WrongList {
action: String,
binding: String,
list: Device,
},
DuplicatePattern {
action: String,
pattern: Pattern,
},
BadTime {
action: String,
pattern: Pattern,
},
UnknownEmit {
action: String,
pattern: Pattern,
emits: String,
},
EmitNotAButton {
action: String,
pattern: Pattern,
emits: String,
},
EmitsItself {
action: String,
pattern: Pattern,
},
Declare(ActionMapError),
}
impl fmt::Display for AssetRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(message) => f.write_str(message),
Self::BadBinding { action, error } => write!(f, "action {action:?}: {error}"),
Self::WrongList {
action,
binding,
list,
} => write!(
f,
"action {action:?}: {binding:?} is not a {} binding",
list_name(*list)
),
Self::DuplicatePattern { action, pattern } => {
let pattern = pattern_name(*pattern);
write!(f, "action {action:?}: more than one {pattern}")
}
Self::BadTime { action, pattern } => {
let pattern = pattern_name(*pattern);
write!(f, "action {action:?}: a {pattern} of zero milliseconds")
}
Self::UnknownEmit {
action,
pattern,
emits,
} => {
let pattern = pattern_name(*pattern);
write!(
f,
"action {action:?}: {pattern} emits {emits:?}, which is not declared"
)
}
Self::EmitNotAButton {
action,
pattern,
emits,
} => {
let pattern = pattern_name(*pattern);
write!(
f,
"action {action:?}: {pattern} emits {emits:?}, which is not a Button"
)
}
Self::EmitsItself { action, pattern } => {
let pattern = pattern_name(*pattern);
write!(f, "action {action:?}: {pattern} emits its own action")
}
Self::Declare(error) => error.fmt(f),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssetWriteError {
MixedLists(String),
NoEmit {
action: String,
pattern: Pattern,
},
NotWholeMilliseconds {
action: String,
pattern: Pattern,
},
Repeat(String),
}
impl fmt::Display for AssetWriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MixedLists(action) => {
write!(f, "action {action:?}: its bindings interleave devices")
}
Self::NoEmit { action, pattern } => {
let pattern = pattern_name(*pattern);
write!(f, "action {action:?}: its {pattern} emits no action")
}
Self::NotWholeMilliseconds { action, pattern } => {
let pattern = pattern_name(*pattern);
write!(
f,
"action {action:?}: its {pattern} is not a whole number of milliseconds"
)
}
Self::Repeat(action) => write!(f, "action {action:?}: a repeat has no asset form"),
}
}
}
impl std::error::Error for AssetWriteError {}
#[cfg(test)]
mod tests;